001: /*
002: * HA-JDBC: High-Availability JDBC
003: * Copyright (c) 2004-2007 Paul Ferraro
004: *
005: * This library is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU Lesser General Public License as published by the
007: * Free Software Foundation; either version 2.1 of the License, or (at your
008: * option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful, but WITHOUT
011: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
012: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
013: * for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public License
016: * along with this library; if not, write to the Free Software Foundation,
017: * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *
019: * Contact: ferraro@users.sourceforge.net
020: */
021: package net.sf.hajdbc.dialect;
022:
023: import java.sql.Connection;
024: import java.sql.DatabaseMetaData;
025: import java.sql.ResultSet;
026: import java.sql.SQLException;
027: import java.sql.Statement;
028: import java.text.MessageFormat;
029: import java.util.Collection;
030: import java.util.Collections;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.regex.Matcher;
034: import java.util.regex.Pattern;
035:
036: import net.sf.hajdbc.ColumnProperties;
037: import net.sf.hajdbc.Dialect;
038: import net.sf.hajdbc.ForeignKeyConstraint;
039: import net.sf.hajdbc.QualifiedName;
040: import net.sf.hajdbc.SequenceProperties;
041: import net.sf.hajdbc.TableProperties;
042: import net.sf.hajdbc.UniqueConstraint;
043: import net.sf.hajdbc.util.Strings;
044:
045: /**
046: * @author Paul Ferraro
047: * @since 1.1
048: */
049: @SuppressWarnings("nls")
050: public class StandardDialect implements Dialect {
051: private Pattern selectForUpdatePattern = this .compile(this
052: .selectForUpdatePattern());
053: private Pattern insertIntoTablePattern = this .compile(this
054: .insertIntoTablePattern());
055: private Pattern sequencePattern = this .compile(this
056: .sequencePattern());
057: private Pattern currentTimestampPattern = this .compile(this
058: .currentTimestampPattern());
059: private Pattern currentDatePattern = this .compile(this
060: .currentDatePattern());
061: private Pattern currentTimePattern = this .compile(this
062: .currentTimePattern());
063: private Pattern randomPattern = this .compile(this .randomPattern());
064:
065: private Pattern compile(String pattern) {
066: return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
067: }
068:
069: protected String selectForUpdatePattern() {
070: return "SELECT\\s+.+\\s+FOR\\s+UPDATE";
071: }
072:
073: protected String insertIntoTablePattern() {
074: return "INSERT\\s+(?:INTO\\s+)?'?([^'\\s\\(]+)";
075: }
076:
077: protected String sequencePattern() {
078: return "NEXT\\s+VALUE\\s+FOR\\s+'?([^',\\s\\(\\)]+)";
079: }
080:
081: protected String currentDatePattern() {
082: return "(?<=\\W)CURRENT_DATE(?=\\W)";
083: }
084:
085: protected String currentTimePattern() {
086: return "(?<=\\W)CURRENT_TIME(?:\\s*\\(\\s*\\d+\\s*\\))?(?=\\W)|(?<=\\W)LOCALTIME(?:\\s*\\(\\s*\\d+\\s*\\))?(?=\\W)";
087: }
088:
089: protected String currentTimestampPattern() {
090: return "(?<=\\W)CURRENT_TIMESTAMP(?:\\s*\\(\\s*\\d+\\s*\\))?(?=\\W)|(?<=\\W)LOCALTIMESTAMP(?:\\s*\\(\\s*\\d+\\s*\\))?(?=\\W)";
091: }
092:
093: protected String randomPattern() {
094: return "(?<=\\W)RAND\\s*\\(\\s*\\)";
095: }
096:
097: /**
098: * @see net.sf.hajdbc.Dialect#getSimpleSQL()
099: */
100: @Override
101: public String getSimpleSQL() {
102: return this .executeFunctionSQL(this .currentTimestampFunction());
103: }
104:
105: protected String executeFunctionFormat() {
106: StringBuilder builder = new StringBuilder("SELECT {0}");
107:
108: String dummyTable = this .dummyTable();
109:
110: if (dummyTable != null) {
111: builder.append(" FROM ").append(dummyTable);
112: }
113:
114: return builder.toString();
115: }
116:
117: protected String executeFunctionSQL(String function) {
118: return MessageFormat.format(this .executeFunctionFormat(),
119: function);
120: }
121:
122: protected String currentTimestampFunction() {
123: return "CURRENT_TIMESTAMP";
124: }
125:
126: protected String dummyTable() {
127: return null;
128: }
129:
130: /**
131: * @see net.sf.hajdbc.Dialect#getTruncateTableSQL(net.sf.hajdbc.TableProperties)
132: */
133: @Override
134: public String getTruncateTableSQL(TableProperties properties) {
135: return MessageFormat.format(this .truncateTableFormat(),
136: properties.getName());
137: }
138:
139: protected String truncateTableFormat() {
140: return "DELETE FROM {0}";
141: }
142:
143: /**
144: * @see net.sf.hajdbc.Dialect#getCreateForeignKeyConstraintSQL(net.sf.hajdbc.ForeignKeyConstraint)
145: */
146: @Override
147: public String getCreateForeignKeyConstraintSQL(
148: ForeignKeyConstraint key) {
149: return MessageFormat.format(this
150: .createForeignKeyConstraintFormat(), key.getName(), key
151: .getTable(), Strings.join(key.getColumnList(),
152: Strings.PADDED_COMMA), key.getForeignTable(),
153: Strings.join(key.getForeignColumnList(),
154: Strings.PADDED_COMMA), key.getDeleteRule(), key
155: .getUpdateRule(), key.getDeferrability());
156: }
157:
158: protected String createForeignKeyConstraintFormat() {
159: return "ALTER TABLE {1} ADD CONSTRAINT {0} FOREIGN KEY ({2}) REFERENCES {3} ({4}) ON DELETE {5,choice,0#CASCADE|1#RESTRICT|2#SET NULL|3#NO ACTION|4#SET DEFAULT} ON UPDATE {6,choice,0#CASCADE|1#RESTRICT|2#SET NULL|3#NO ACTION|4#SET DEFAULT} {7,choice,5#DEFERRABLE INITIALLY DEFERRED|6#DEFERRABLE INITIALLY IMMEDIATE|7#NOT DEFERRABLE}";
160: }
161:
162: /**
163: * @see net.sf.hajdbc.Dialect#getDropForeignKeyConstraintSQL(net.sf.hajdbc.ForeignKeyConstraint)
164: */
165: @Override
166: public String getDropForeignKeyConstraintSQL(
167: ForeignKeyConstraint key) {
168: return MessageFormat.format(this
169: .dropForeignKeyConstraintFormat(), key.getName(), key
170: .getTable());
171: }
172:
173: protected String dropForeignKeyConstraintFormat() {
174: return this .dropConstraintFormat();
175: }
176:
177: protected String dropConstraintFormat() {
178: return "ALTER TABLE {1} DROP CONSTRAINT {0}";
179: }
180:
181: /**
182: * @see net.sf.hajdbc.Dialect#getCreateUniqueConstraintSQL(net.sf.hajdbc.UniqueConstraint)
183: */
184: @Override
185: public String getCreateUniqueConstraintSQL(
186: UniqueConstraint constraint) {
187: return MessageFormat.format(
188: this .createUniqueConstraintFormat(), constraint
189: .getName(), constraint.getTable(), Strings
190: .join(constraint.getColumnList(),
191: Strings.PADDED_COMMA));
192: }
193:
194: protected String createUniqueConstraintFormat() {
195: return "ALTER TABLE {1} ADD CONSTRAINT {0} UNIQUE ({2})";
196: }
197:
198: /**
199: * @see net.sf.hajdbc.Dialect#getDropUniqueConstraintSQL(net.sf.hajdbc.UniqueConstraint)
200: */
201: @Override
202: public String getDropUniqueConstraintSQL(UniqueConstraint constraint) {
203: return MessageFormat.format(this .dropUniqueConstraintFormat(),
204: constraint.getName(), constraint.getTable());
205: }
206:
207: protected String dropUniqueConstraintFormat() {
208: return this .dropConstraintFormat();
209: }
210:
211: /**
212: * @see net.sf.hajdbc.Dialect#isIdentity(net.sf.hajdbc.ColumnProperties)
213: */
214: @Override
215: public boolean isIdentity(ColumnProperties properties) {
216: String remarks = properties.getRemarks();
217:
218: return (remarks != null)
219: && remarks.contains("GENERATED BY DEFAULT AS IDENTITY");
220: }
221:
222: /**
223: * @see net.sf.hajdbc.Dialect#isSelectForUpdate(java.lang.String)
224: */
225: @Override
226: public boolean isSelectForUpdate(String sql) {
227: return this .selectForUpdatePattern.matcher(sql).find();
228: }
229:
230: /**
231: * @see net.sf.hajdbc.Dialect#parseInsertTable(java.lang.String)
232: */
233: @Override
234: public String parseInsertTable(String sql) {
235: return this .parse(this .insertIntoTablePattern, sql);
236: }
237:
238: /**
239: * @see net.sf.hajdbc.Dialect#getDefaultSchemas(java.sql.DatabaseMetaData)
240: */
241: @Override
242: public List<String> getDefaultSchemas(DatabaseMetaData metaData)
243: throws SQLException {
244: return Collections.singletonList(metaData.getUserName());
245: }
246:
247: protected String executeFunction(Connection connection,
248: String function) throws SQLException {
249: Statement statement = connection.createStatement();
250:
251: ResultSet resultSet = statement.executeQuery(this
252: .executeFunctionSQL(function));
253:
254: resultSet.next();
255:
256: String value = resultSet.getString(1);
257:
258: resultSet.close();
259: statement.close();
260:
261: return value;
262: }
263:
264: protected List<String> executeQuery(Connection connection,
265: String sql) throws SQLException {
266: List<String> resultList = new LinkedList<String>();
267:
268: Statement statement = connection.createStatement();
269:
270: ResultSet resultSet = statement.executeQuery(sql);
271:
272: while (resultSet.next()) {
273: resultList.add(resultSet.getString(1));
274: }
275:
276: resultSet.close();
277: statement.close();
278:
279: return resultList;
280: }
281:
282: /**
283: * @see net.sf.hajdbc.Dialect#parseSequence(java.lang.String)
284: */
285: @Override
286: public String parseSequence(String sql) {
287: return this .parse(this .sequencePattern, sql);
288: }
289:
290: /**
291: * @see net.sf.hajdbc.Dialect#getColumnType(net.sf.hajdbc.ColumnProperties)
292: */
293: @Override
294: public int getColumnType(ColumnProperties properties) {
295: return properties.getType();
296: }
297:
298: /**
299: * @see net.sf.hajdbc.Dialect#getSequences(java.sql.DatabaseMetaData)
300: */
301: @Override
302: public Collection<QualifiedName> getSequences(
303: DatabaseMetaData metaData) throws SQLException {
304: List<QualifiedName> sequenceList = new LinkedList<QualifiedName>();
305:
306: ResultSet resultSet = metaData.getTables(Strings.EMPTY, null,
307: Strings.ANY, new String[] { this .sequenceTableType() });
308:
309: while (resultSet.next()) {
310: sequenceList.add(new QualifiedName(resultSet
311: .getString("TABLE_SCHEM"), resultSet
312: .getString("TABLE_NAME")));
313: }
314:
315: resultSet.close();
316:
317: return sequenceList;
318: }
319:
320: protected String sequenceTableType() {
321: return "SEQUENCE";
322: }
323:
324: /**
325: * @see net.sf.hajdbc.Dialect#getNextSequenceValueSQL(net.sf.hajdbc.SequenceProperties)
326: */
327: @Override
328: public String getNextSequenceValueSQL(SequenceProperties sequence) {
329: return this .executeFunctionSQL(MessageFormat.format(this
330: .nextSequenceValueFormat(), sequence.getName()));
331: }
332:
333: protected String nextSequenceValueFormat() {
334: return "NEXT VALUE FOR {0}";
335: }
336:
337: /**
338: * @see net.sf.hajdbc.Dialect#getAlterSequenceSQL(net.sf.hajdbc.SequenceProperties, long)
339: */
340: @Override
341: public String getAlterSequenceSQL(SequenceProperties sequence,
342: long value) {
343: return MessageFormat.format(this .alterSequenceFormat(),
344: sequence.getName(), String.valueOf(value));
345: }
346:
347: protected String alterSequenceFormat() {
348: return "ALTER SEQUENCE {0} RESTART WITH {1}";
349: }
350:
351: @Override
352: public String getAlterIdentityColumnSQL(TableProperties table,
353: ColumnProperties column, long value) throws SQLException {
354: return MessageFormat.format(this .alterIdentityColumnFormat(),
355: table.getName(), column.getName(), String
356: .valueOf(value));
357: }
358:
359: protected String alterIdentityColumnFormat() {
360: return "ALTER TABLE {0} ALTER COLUMN {1} RESTART WITH {2}";
361: }
362:
363: /**
364: * @see net.sf.hajdbc.Dialect#getIdentifierPattern(java.sql.DatabaseMetaData)
365: */
366: @Override
367: public Pattern getIdentifierPattern(DatabaseMetaData metaData)
368: throws SQLException {
369: return Pattern.compile(MessageFormat.format("[\\w{0}]+",
370: Pattern.quote(metaData.getExtraNameCharacters())));
371: }
372:
373: protected String parse(Pattern pattern, String string) {
374: Matcher matcher = pattern.matcher(string);
375:
376: return matcher.find() ? matcher.group(1) : null;
377: }
378:
379: /**
380: * @see net.sf.hajdbc.Dialect#evaluateCurrentDate(java.lang.String, java.sql.Date)
381: */
382: @Override
383: public String evaluateCurrentDate(String sql, java.sql.Date date) {
384: return this .evaluateTemporal(sql, this .currentDatePattern,
385: date, this .dateLiteralFormat());
386: }
387:
388: protected String dateLiteralFormat() {
389: return "DATE ''{0}''";
390: }
391:
392: /**
393: * @see net.sf.hajdbc.Dialect#evaluateCurrentTime(java.lang.String, java.sql.Time)
394: */
395: @Override
396: public String evaluateCurrentTime(String sql, java.sql.Time time) {
397: return this .evaluateTemporal(sql, this .currentTimePattern,
398: time, this .timeLiteralFormat());
399: }
400:
401: protected String timeLiteralFormat() {
402: return "TIME ''{0}''";
403: }
404:
405: /**
406: * @see net.sf.hajdbc.Dialect#evaluateCurrentTimestamp(java.lang.String, java.sql.Timestamp)
407: */
408: @Override
409: public String evaluateCurrentTimestamp(String sql,
410: java.sql.Timestamp timestamp) {
411: return this .evaluateTemporal(sql, this .currentTimestampPattern,
412: timestamp, this .timestampLiteralFormat());
413: }
414:
415: protected String timestampLiteralFormat() {
416: return "TIMESTAMP ''{0}''";
417: }
418:
419: private String evaluateTemporal(String sql, Pattern pattern,
420: java.util.Date date, String format) {
421: return pattern.matcher(sql).replaceAll(
422: MessageFormat.format(format, date.toString()));
423: }
424:
425: /**
426: * @see net.sf.hajdbc.Dialect#evaluateRand(java.lang.String)
427: */
428: @Override
429: public String evaluateRand(String sql) {
430: StringBuffer buffer = new StringBuffer();
431: Matcher matcher = this.randomPattern.matcher(sql);
432:
433: while (matcher.find()) {
434: matcher.appendReplacement(buffer, Double.toString(Math
435: .random()));
436: }
437:
438: return matcher.appendTail(buffer).toString();
439: }
440: }
|