/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.dao.db; import com.liferay.counter.kernel.service.CounterLocalServiceUtil; import com.liferay.portal.dao.orm.common.SQLTransformer; import com.liferay.portal.kernel.configuration.Filter; import com.liferay.portal.kernel.dao.db.DB; import com.liferay.portal.kernel.dao.db.DBType; import com.liferay.portal.kernel.dao.db.Index; import com.liferay.portal.kernel.dao.db.IndexMetadata; import com.liferay.portal.kernel.dao.db.IndexMetadataFactoryUtil; import com.liferay.portal.kernel.dao.jdbc.DataAccess; import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader; import com.liferay.portal.kernel.io.unsync.UnsyncStringReader; import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.template.StringTemplateResource; import com.liferay.portal.kernel.template.Template; import com.liferay.portal.kernel.template.TemplateConstants; import com.liferay.portal.kernel.template.TemplateManagerUtil; import com.liferay.portal.kernel.util.ClassLoaderUtil; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.PropsUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.kernel.uuid.PortalUUIDUtil; import com.liferay.util.SimpleCounter; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.NamingException; /** * @author Alexander Chow * @author Ganesh Ram * @author Brian Wing Shun Chan * @author Daniel Kocsis */ public abstract class BaseDB implements DB { @Override public void addIndexes( Connection con, String indexesSQL, Set<String> validIndexNames) throws IOException { if (_log.isInfoEnabled()) { _log.info("Adding indexes"); } UnsyncBufferedReader bufferedReader = new UnsyncBufferedReader( new UnsyncStringReader(indexesSQL)); String sql = null; while ((sql = bufferedReader.readLine()) != null) { if (Validator.isNull(sql)) { continue; } int y = sql.indexOf(" on "); int x = sql.lastIndexOf(" ", y - 1); String indexName = sql.substring(x + 1, y); if (validIndexNames.contains(indexName)) { continue; } if (_log.isInfoEnabled()) { _log.info(sql); } try { runSQL(con, sql); } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn(e.getMessage() + ": " + sql); } } } } @Override public void buildCreateFile(String sqlDir, String databaseName) throws IOException { buildCreateFile(sqlDir, databaseName, BARE); buildCreateFile(sqlDir, databaseName, DEFAULT); } @Override public void buildCreateFile( String sqlDir, String databaseName, int population) throws IOException { String suffix = getSuffix(population); File file = new File( sqlDir + "/create" + suffix + "/create" + suffix + "-" + getServerName() + ".sql"); String content = buildCreateFileContent( sqlDir, databaseName, population); if (content != null) { FileUtil.write(file, content); } } @Override public abstract String buildSQL(String template) throws IOException; @Override public void buildSQLFile(String sqlDir, String fileName) throws IOException { String template = buildTemplate(sqlDir, fileName); if (Validator.isNull(template)) { return; } template = buildSQL(template); FileUtil.write( sqlDir + "/" + fileName + "/" + fileName + "-" + getServerName() + ".sql", template); } @Override public DBType getDBType() { return _dbType; } @Override @SuppressWarnings("unused") public List<Index> getIndexes(Connection con) throws SQLException { return Collections.emptyList(); } @Override public int getMajorVersion() { return _majorVersion; } @Override public int getMinorVersion() { return _minorVersion; } @Override public String getTemplateBlob() { return getTemplate()[5]; } @Override public String getTemplateFalse() { return getTemplate()[2]; } @Override public String getTemplateTrue() { return getTemplate()[1]; } @Override public String getVersionString() { return _majorVersion + StringPool.PERIOD + _minorVersion; } @Override public long increment() { return CounterLocalServiceUtil.increment(); } @Override public long increment(String name) { return CounterLocalServiceUtil.increment(name); } @Override public long increment(String name, int size) { return CounterLocalServiceUtil.increment(name, size); } @Override public boolean isSupportsAlterColumnName() { return _SUPPORTS_ALTER_COLUMN_NAME; } @Override public boolean isSupportsAlterColumnType() { return _SUPPORTS_ALTER_COLUMN_TYPE; } @Override public boolean isSupportsInlineDistinct() { return _SUPPORTS_INLINE_DISTINCT; } @Override public boolean isSupportsQueryingAfterException() { return _SUPPORTS_QUERYING_AFTER_EXCEPTION; } @Override public boolean isSupportsScrollableResults() { return _SUPPORTS_SCROLLABLE_RESULTS; } @Override public boolean isSupportsStringCaseSensitiveQuery() { return _supportsStringCaseSensitiveQuery; } @Override public boolean isSupportsUpdateWithInnerJoin() { return _SUPPORTS_UPDATE_WITH_INNER_JOIN; } @Override public void runSQL(Connection con, String sql) throws IOException, SQLException { runSQL(con, new String[] {sql}); } @Override public void runSQL(Connection con, String[] sqls) throws IOException, SQLException { Statement s = null; try { s = con.createStatement(); for (int i = 0; i < sqls.length; i++) { String sql = buildSQL( applyMaxStringIndexLengthLimitation(sqls[i])); sql = SQLTransformer.transform(sql.trim()); if (sql.endsWith(";")) { sql = sql.substring(0, sql.length() - 1); } if (sql.endsWith("\ngo")) { sql = sql.substring(0, sql.length() - 3); } if (_log.isDebugEnabled()) { _log.debug(sql); } try { s.executeUpdate(sql); } catch (SQLException sqle) { handleSQLException(sql, sqle); } } } finally { DataAccess.cleanUp(s); } } @Override public void runSQL(String sql) throws IOException, SQLException { runSQL(new String[] {sql}); } @Override public void runSQL(String[] sqls) throws IOException, SQLException { Connection con = DataAccess.getConnection(); try { runSQL(con, sqls); } finally { DataAccess.cleanUp(con); } } @Override public void runSQLTemplate(String path) throws IOException, NamingException, SQLException { runSQLTemplate(path, true); } @Override public void runSQLTemplate(String path, boolean failOnError) throws IOException, NamingException, SQLException { ClassLoader classLoader = ClassLoaderUtil.getContextClassLoader(); InputStream is = classLoader.getResourceAsStream( "com/liferay/portal/tools/sql/dependencies/" + path); if (is == null) { is = classLoader.getResourceAsStream(path); } if (is == null) { _log.error("Invalid path " + path); if (failOnError) { throw new IOException("Invalid path " + path); } else { return; } } String template = StringUtil.read(is); boolean evaluate = path.endsWith(".vm"); runSQLTemplateString(template, evaluate, failOnError); } @Override public void runSQLTemplateString( Connection connection, String template, boolean evaluate, boolean failOnError) throws IOException, NamingException, SQLException { template = applyMaxStringIndexLengthLimitation(template); if (evaluate) { try { template = evaluateVM(template.hashCode() + "", template); } catch (Exception e) { _log.error(e, e); } } try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new UnsyncStringReader(template))) { StringBundler sb = new StringBundler(); String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { if (line.isEmpty() || line.startsWith("##")) { continue; } if (line.startsWith("@include ")) { int pos = line.indexOf(" "); String includeFileName = line.substring(pos + 1); ClassLoader classLoader = ClassLoaderUtil.getContextClassLoader(); InputStream is = classLoader.getResourceAsStream( "com/liferay/portal/tools/sql/dependencies/" + includeFileName); if (is == null) { is = classLoader.getResourceAsStream(includeFileName); } String include = StringUtil.read(is); if (includeFileName.endsWith(".vm")) { try { include = evaluateVM(includeFileName, include); } catch (Exception e) { _log.error(e, e); } } include = convertTimestamp(include); include = replaceTemplate(include, getTemplate()); runSQLTemplateString(include, false, true); } else { sb.append(line); sb.append(StringPool.NEW_LINE); if (line.endsWith(";")) { String sql = sb.toString(); sb.setIndex(0); try { if (!sql.equals("COMMIT_TRANSACTION;\n")) { runSQL(connection, sql); } else { if (_log.isDebugEnabled()) { _log.debug("Skip commit sql"); } } } catch (IOException ioe) { if (failOnError) { throw ioe; } else if (_log.isWarnEnabled()) { _log.warn(ioe.getMessage()); } } catch (SecurityException se) { if (failOnError) { throw se; } else if (_log.isWarnEnabled()) { _log.warn(se.getMessage()); } } catch (SQLException sqle) { if (failOnError) { throw sqle; } String message = GetterUtil.getString( sqle.getMessage()); if (!message.startsWith("Duplicate key name") && _log.isWarnEnabled()) { _log.warn(message + ": " + buildSQL(sql)); } if (message.startsWith("Duplicate entry") || message.startsWith( "Specified key was too long")) { _log.error(line); } } } } } } } @Override public void runSQLTemplateString( String template, boolean evaluate, boolean failOnError) throws IOException, NamingException, SQLException { try (Connection connection = DataAccess.getConnection()) { runSQLTemplateString(connection, template, evaluate, failOnError); } } @Override public void setSupportsStringCaseSensitiveQuery( boolean supportsStringCaseSensitiveQuery) { if (_log.isInfoEnabled()) { if (supportsStringCaseSensitiveQuery) { _log.info("Database supports case sensitive queries"); } else { _log.info("Database does not support case sensitive queries"); } } _supportsStringCaseSensitiveQuery = supportsStringCaseSensitiveQuery; } @Override public void updateIndexes( Connection con, String tablesSQL, String indexesSQL, boolean dropIndexes) throws IOException, SQLException { List<Index> indexes = getIndexes(con); Set<String> validIndexNames = null; if (dropIndexes) { validIndexNames = dropIndexes(con, tablesSQL, indexesSQL, indexes); } else { validIndexNames = new HashSet<>(); for (Index index : indexes) { String indexName = StringUtil.toUpperCase(index.getIndexName()); validIndexNames.add(indexName); } } indexesSQL = applyMaxStringIndexLengthLimitation(indexesSQL); addIndexes(con, indexesSQL, validIndexNames); } protected BaseDB(DBType dbType, int majorVersion, int minorVersion) { _dbType = dbType; _majorVersion = majorVersion; _minorVersion = minorVersion; String[] actual = getTemplate(); for (int i = 0; i < TEMPLATE.length; i++) { _templateMap.put(TEMPLATE[i], actual[i]); } } protected String applyMaxStringIndexLengthLimitation(String template) { if (!template.contains("[$COLUMN_LENGTH:")) { return template; } DBType dbType = getDBType(); int stringIndexMaxLength = GetterUtil.getInteger( PropsUtil.get( PropsKeys.DATABASE_STRING_INDEX_MAX_LENGTH, new Filter(dbType.getName())), -1); String replacement = "\\(" + stringIndexMaxLength + "\\)"; Matcher matcher = _columnLengthPattern.matcher(template); if (stringIndexMaxLength < 0) { if (dbType.equals(DBType.SYBASE)) { replacement = StringPool.BLANK; } else { return matcher.replaceAll(StringPool.BLANK); } } boolean remove = false; StringBuffer sb = new StringBuffer(); while (matcher.find()) { int length = Integer.valueOf(matcher.group(1)); if (dbType.equals(DBType.SYBASE) && (length > 1250)) { matcher.appendReplacement(sb, "%%REMOVE%%"); remove = true; } else if (length > stringIndexMaxLength) { matcher.appendReplacement(sb, replacement); } else { matcher.appendReplacement(sb, StringPool.BLANK); } } matcher.appendTail(sb); String string = sb.toString(); if (dbType.equals(DBType.SYBASE) && remove) { String[] strings = StringUtil.split(string, StringPool.NEW_LINE); for (int i = 0; i < strings.length; i++) { if (strings[i].contains("%%REMOVE%%")) { strings[i] = StringPool.BLANK; } } return StringUtil.merge(strings, StringPool.NEW_LINE); } return string; } protected String[] buildColumnNameTokens(String line) { String[] words = StringUtil.split(line, ' '); String nullable = ""; if (words.length == 7) { nullable = "not null;"; } String[] template = {words[1], words[2], words[3], words[4], nullable}; return template; } protected String[] buildColumnTypeTokens(String line) { String[] words = StringUtil.split(line, ' '); String nullable = ""; if (words.length == 6) { nullable = "not null;"; } else if (words.length == 5) { nullable = words[4]; } else if (words.length == 4) { nullable = "not null;"; if (words[3].endsWith(";")) { words[3] = words[3].substring(0, words[3].length() - 1); } } String[] template = {words[1], words[2], "", words[3], nullable}; return template; } protected abstract String buildCreateFileContent( String sqlDir, String databaseName, int population) throws IOException; protected String[] buildTableNameTokens(String line) { String[] words = StringUtil.split(line, StringPool.SPACE); return new String[] {words[1], words[2]}; } protected String buildTemplate(String sqlDir, String fileName) throws IOException { String template = readFile(sqlDir + "/" + fileName + ".sql"); if (fileName.equals("portal") || fileName.equals("update-5.0.1-5.1.0")) { StringBundler sb = new StringBundler(); try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader( new UnsyncStringReader(template))) { String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { if (line.startsWith("@include ")) { int pos = line.indexOf(" "); String includeFileName = line.substring(pos + 1); File includeFile = new File( sqlDir + "/" + includeFileName); if (!includeFile.exists()) { continue; } String include = FileUtil.read(includeFile); if (includeFileName.endsWith(".vm")) { try { include = evaluateVM(includeFileName, include); } catch (Exception e) { _log.error(e, e); } } include = convertTimestamp(include); include = replaceTemplate(include, getTemplate()); sb.append(include); sb.append("\n\n"); } else { sb.append(line); sb.append("\n"); } } } template = sb.toString(); } if (fileName.equals("indexes")) { template = applyMaxStringIndexLengthLimitation(template); if (getDBType() == DBType.SYBASE) { template = removeBooleanIndexes(sqlDir, template); } } return template; } protected String convertTimestamp(String data) { String s = null; if (this instanceof MySQLDB) { s = StringUtil.replace(data, "SPECIFIC_TIMESTAMP_", ""); } else { Matcher matcher = _timestampPattern.matcher(data); s = matcher.replaceAll("CURRENT_TIMESTAMP"); } return s; } protected Set<String> dropIndexes( Connection con, String tablesSQL, String indexesSQL, List<Index> indexes) throws IOException, SQLException { if (_log.isInfoEnabled()) { _log.info("Dropping stale indexes"); } Set<String> validIndexNames = new HashSet<>(); if (indexes.isEmpty()) { return validIndexNames; } String tablesSQLLowerCase = StringUtil.toLowerCase(tablesSQL); String indexesSQLLowerCase = StringUtil.toLowerCase(indexesSQL); String[] lines = StringUtil.splitLines(indexesSQL); Set<String> indexNames = new HashSet<>(); for (String line : lines) { if (Validator.isNull(line)) { continue; } IndexMetadata indexMetadata = IndexMetadataFactoryUtil.createIndexMetadata(line); indexNames.add( StringUtil.toLowerCase(indexMetadata.getIndexName())); } for (Index index : indexes) { String indexNameUpperCase = StringUtil.toUpperCase( index.getIndexName()); String indexNameLowerCase = StringUtil.toLowerCase( indexNameUpperCase); String tableName = index.getTableName(); String tableNameLowerCase = StringUtil.toLowerCase(tableName); boolean unique = index.isUnique(); validIndexNames.add(indexNameUpperCase); if (indexNames.contains(indexNameLowerCase)) { if (unique && indexesSQLLowerCase.contains( "create unique index " + indexNameLowerCase + " ")) { continue; } if (!unique && indexesSQLLowerCase.contains( "create index " + indexNameLowerCase + " ")) { continue; } } else if (!tablesSQLLowerCase.contains( CREATE_TABLE + tableNameLowerCase + " (")) { continue; } validIndexNames.remove(indexNameUpperCase); String sql = "drop index " + indexNameUpperCase + " on " + tableName; if (_log.isInfoEnabled()) { _log.info(sql); } runSQL(con, sql); } return validIndexNames; } protected String evaluateVM(String templateId, String templateContent) throws Exception { if (Validator.isNull(templateContent)) { return StringPool.BLANK; } ClassLoader classLoader = ClassLoaderUtil.getContextClassLoader(); UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter(); try { ClassLoaderUtil.setContextClassLoader( ClassLoaderUtil.getPortalClassLoader()); StringTemplateResource stringTemplateResource = new StringTemplateResource(templateId, templateContent); Template template = TemplateManagerUtil.getTemplate( TemplateConstants.LANG_TYPE_VM, stringTemplateResource, false); template.put("counter", new SimpleCounter()); template.put("portalUUIDUtil", PortalUUIDUtil.class); template.processTemplate(unsyncStringWriter); } finally { ClassLoaderUtil.setContextClassLoader(classLoader); } // Trim insert statements because it breaks MySQL Query Browser StringBundler sb = new StringBundler(); try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader( new UnsyncStringReader(unsyncStringWriter.toString()))) { String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { line = line.trim(); sb.append(line); sb.append("\n"); } } templateContent = sb.toString(); templateContent = StringUtil.replace(templateContent, "\n\n\n", "\n\n"); return templateContent; } protected String getCreateTablesContent(String sqlDir, String suffix) throws IOException { StringBundler sb = new StringBundler(8); sb.append(sqlDir); if (!sqlDir.endsWith("/WEB-INF/sql")) { sb.append("/portal"); sb.append(suffix); sb.append("/portal"); } else { sb.append("/tables"); sb.append(suffix); sb.append("/tables"); } sb.append(suffix); sb.append(StringPool.DASH); sb.append(getServerName()); sb.append(".sql"); return readFile(sb.toString()); } protected abstract String getServerName(); protected String getSuffix(int type) { if (type == BARE) { return "-bare"; } else { return StringPool.BLANK; } } protected abstract String[] getTemplate(); protected void handleSQLException(String sql, SQLException sqle) throws SQLException { if (_log.isDebugEnabled()) { StringBundler sb = new StringBundler(10); sb.append("SQL: "); sb.append(sql); sb.append("\nSQL state: "); sb.append(sqle.getSQLState()); sb.append("\nVendor: "); sb.append(getDBType()); sb.append("\nVendor error code: "); sb.append(sqle.getErrorCode()); sb.append("\nVendor error message: "); sb.append(sqle.getMessage()); _log.debug(sb.toString()); } throw sqle; } protected String readFile(String fileName) throws IOException { if (FileUtil.exists(fileName)) { return FileUtil.read(fileName); } else { return StringPool.BLANK; } } protected String readSQL(String fileName, String comments, String eol) throws IOException { if (!FileUtil.exists(fileName)) { return StringPool.BLANK; } try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new FileReader(new File(fileName)))) { StringBundler sb = new StringBundler(); String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { if (!line.startsWith(comments)) { line = StringUtil.removeChars(line, '\n', '\t'); if (line.endsWith(";")) { sb.append(line.substring(0, line.length() - 1)); sb.append(eol); } else { sb.append(line); } } } return sb.toString(); } } protected String removeBooleanIndexes(String sqlDir, String data) throws IOException { String portalData = readFile(sqlDir + "/portal-tables.sql"); if (Validator.isNull(portalData)) { return StringPool.BLANK; } try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new UnsyncStringReader(data))) { StringBundler sb = new StringBundler(); String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { boolean append = true; int x = line.indexOf(" on "); if (x != -1) { int y = line.indexOf(" (", x); String table = line.substring(x + 4, y); x = y + 2; y = line.indexOf(")", x); String[] columns = StringUtil.split(line.substring(x, y)); x = portalData.indexOf(CREATE_TABLE + table + " ("); y = portalData.indexOf(");", x); String portalTableData = portalData.substring(x, y); for (int i = 0; i < columns.length; i++) { if (portalTableData.contains( columns[i].trim() + " BOOLEAN")) { append = false; break; } } } if (append) { sb.append(line); sb.append("\n"); } } return sb.toString(); } } protected String removeInserts(String data) throws IOException { try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new UnsyncStringReader(data))) { StringBundler sb = new StringBundler(); String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { if (!line.startsWith("insert into ") && !line.startsWith("update ")) { sb.append(line); sb.append("\n"); } } return sb.toString(); } } protected String removeLongInserts(String data) throws IOException { try (UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new UnsyncStringReader(data))) { StringBundler sb = new StringBundler(); String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { if (!line.startsWith("insert into Image (") && !line.startsWith("insert into JournalArticle (")) { sb.append(line); sb.append("\n"); } } return sb.toString(); } } protected String removeNull(String content) { content = StringUtil.replace(content, " = null", " = NULL"); content = StringUtil.replace(content, " is null", " IS NULL"); content = StringUtil.replace(content, " not null", " not_null"); content = StringUtil.replace(content, " null", ""); content = StringUtil.replace(content, " not_null", " not null"); return content; } protected String replaceTemplate(String template, String[] actual) { if ((template == null) || (TEMPLATE == null) || (actual == null)) { return null; } if (TEMPLATE.length != actual.length) { return template; } StringBundler sb = null; int endIndex = 0; Matcher matcher = _templatePattern.matcher(template); while (matcher.find()) { int startIndex = matcher.start(); if (sb == null) { sb = new StringBundler(); } sb.append(template.substring(endIndex, startIndex)); endIndex = matcher.end(); String matched = template.substring(startIndex, endIndex); sb.append(_templateMap.get(matched)); } if (sb == null) { return template; } if (template.length() > endIndex) { sb.append(template.substring(endIndex)); } return sb.toString(); } protected abstract String reword(String data) throws IOException; protected static final String ALTER_COLUMN_NAME = "alter_column_name "; protected static final String ALTER_COLUMN_TYPE = "alter_column_type "; protected static final String ALTER_TABLE_NAME = "alter_table_name "; protected static final String CREATE_TABLE = "create table "; protected static final String DROP_INDEX = "drop index"; protected static final String DROP_PRIMARY_KEY = "drop primary key"; protected static final String[] RENAME_TABLE_TEMPLATE = { "@old-table@", "@new-table@" }; protected static final String[] REWORD_TEMPLATE = { "@table@", "@old-column@", "@new-column@", "@type@", "@nullable@" }; protected static final String[] TEMPLATE = { "##", "TRUE", "FALSE", "'01/01/1970'", "CURRENT_TIMESTAMP", " BLOB", " SBLOB", " BOOLEAN", " DATE", " DOUBLE", " INTEGER", " LONG", " STRING", " TEXT", " VARCHAR", " IDENTITY", "COMMIT_TRANSACTION" }; private static final boolean _SUPPORTS_ALTER_COLUMN_NAME = true; private static final boolean _SUPPORTS_ALTER_COLUMN_TYPE = true; private static final boolean _SUPPORTS_INLINE_DISTINCT = true; private static final boolean _SUPPORTS_QUERYING_AFTER_EXCEPTION = true; private static final boolean _SUPPORTS_SCROLLABLE_RESULTS = true; private static final boolean _SUPPORTS_UPDATE_WITH_INNER_JOIN = true; private static final Log _log = LogFactoryUtil.getLog(BaseDB.class); private static final Pattern _columnLengthPattern = Pattern.compile( "\\[\\$COLUMN_LENGTH:(\\d+)\\$\\]"); private static final Pattern _templatePattern; private static final Pattern _timestampPattern = Pattern.compile( "SPECIFIC_TIMESTAMP_\\d+"); static { StringBundler sb = new StringBundler(TEMPLATE.length * 5 - 6); for (int i = 0; i < TEMPLATE.length; i++) { String variable = TEMPLATE[i]; if (variable.equals("##") || variable.equals("'01/01/1970'")) { sb.append(variable); } else { sb.append("(?<!\\[\\$)"); sb.append(variable); sb.append("(?!\\$\\])"); sb.append("\\b"); } sb.append(StringPool.PIPE); } sb.setIndex(sb.index() - 1); _templatePattern = Pattern.compile(sb.toString()); } private final DBType _dbType; private final int _majorVersion; private final int _minorVersion; private boolean _supportsStringCaseSensitiveQuery; private final Map<String, String> _templateMap = new HashMap<>(); }