blob: 4aacd7b463470546c49ef788785294dba0eb3a94 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.empire.db;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.apache.empire.commons.DateUtils;
import org.apache.empire.commons.ObjectUtils;
import org.apache.empire.commons.StringUtils;
import org.apache.empire.data.DataType;
import org.apache.empire.db.exceptions.EmpireSQLException;
import org.apache.empire.exceptions.InvalidArgumentException;
import org.apache.empire.exceptions.NotImplementedException;
import org.apache.empire.exceptions.NotSupportedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The DBDatabaseDriver class is an abstract base class for all database drivers.
* Its purpose is to handle everything that is - or might be - database vendor specific.
*/
public abstract class DBDatabaseDriver implements Serializable
{
private final static long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DBDatabaseDriver.class);
// sql-phrases
public static final int SQL_NULL_VALUE = 1; // Oracle: null
public static final int SQL_PARAMETER = 2; // Oracle: ?
public static final int SQL_RENAME_TABLE = 3; // Oracle: AS
public static final int SQL_RENAME_COLUMN = 4; // Oracle: AS
public static final int SQL_DATABASE_LINK = 5; // Oracle: @
public static final int SQL_QUOTES_OPEN = 6; // Oracle: "; MSSQL: [
public static final int SQL_QUOTES_CLOSE = 7; // Oracle: "; MSSQL: ]
public static final int SQL_CONCAT_EXPR = 8; // Oracle: ||
public static final int SQL_PSEUDO_TABLE = 9; // Oracle: "DUAL"
// data types
public static final int SQL_BOOLEAN_TRUE = 10; // Oracle: "'Y'"; MSSQL: "1"
public static final int SQL_BOOLEAN_FALSE = 11; // Oracle: "'N'"; MSSQL: "0"
public static final int SQL_CURRENT_DATE = 20; // Oracle: "sysdate"
public static final int SQL_DATE_PATTERN = 21; // "yyyy-MM-dd" // SimpleDateFormat
public static final int SQL_DATE_TEMPLATE = 22; // Oracle: "TO_DATE('{0}', 'YYYY-MM-DD')"
public static final int SQL_DATETIME_PATTERN = 23; // "yyyy-MM-dd HH:mm:ss.SSS" // SimpleDateFormat
public static final int SQL_DATETIME_TEMPLATE = 24; // Oracle: "TO_DATE('{0}', 'YYYY-MM-DD HH24:MI:SS')"
public static final int SQL_CURRENT_TIMESTAMP = 25; // Oracle: "systimestamp"
public static final int SQL_TIMESTAMP_PATTERN = 26; // "yyyy-MM-dd HH:mm:ss.SSS" // SimpleDateFormat
public static final int SQL_TIMESTAMP_TEMPLATE= 27; // Oracle: "TO_TIMESTAMP('{0}', 'YYYY.MM.DD HH24:MI:SS.FF')";
// functions
public static final int SQL_FUNC_COALESCE = 100; // Oracle: nvl(?, {0})
public static final int SQL_FUNC_SUBSTRING = 101; // Oracle: substr(?,{0})
public static final int SQL_FUNC_SUBSTRINGEX = 102; // Oracle: substr(?,{0},{1})
public static final int SQL_FUNC_REPLACE = 103; // Oracle: replace(?,{0},{1})
public static final int SQL_FUNC_REVERSE = 104; // Oracle: reverse(?)
public static final int SQL_FUNC_STRINDEX = 105; // Oracle: instr(?, {0})
public static final int SQL_FUNC_STRINDEXFROM= 106; // Oracle: instr(?, {0}, {1})
public static final int SQL_FUNC_LENGTH = 107; // Oracle: length(?,{0})
public static final int SQL_FUNC_UPPER = 110; // Oracle: upper(?)
public static final int SQL_FUNC_LOWER = 111; // Oracle: lower(?)
public static final int SQL_FUNC_TRIM = 112; // Oracle: trim(?)
public static final int SQL_FUNC_LTRIM = 113; // Oracle: ltrim(?)
public static final int SQL_FUNC_RTRIM = 114; // Oracle: rtrim(?)
public static final int SQL_FUNC_ESCAPE = 119; // Oracle: ? escape '{0}'
// Numeric
public static final int SQL_FUNC_ABS = 120; // Oracle: abs(?,{0})
public static final int SQL_FUNC_ROUND = 121; // Oracle: round(?, {0})
public static final int SQL_FUNC_TRUNC = 122; // Oracle: trunc(?, {0})
public static final int SQL_FUNC_FLOOR = 123; // Oracle: floor(?)
public static final int SQL_FUNC_CEILING = 124; // Oracle: ceil(?)
public static final int SQL_FUNC_MODULO = 125; // Oracle: mod(?)
public static final int SQL_FUNC_FORMAT = 126; // Oracle: TO_CHAR(?)
// Date
public static final int SQL_FUNC_DAY = 132; // MSSQL: month(?)
public static final int SQL_FUNC_MONTH = 133; // MSSQL: month(?)
public static final int SQL_FUNC_YEAR = 134; // MSSQL: year (?)
// Aggregation
public static final int SQL_FUNC_SUM = 140; // Oracle: sum(?)
public static final int SQL_FUNC_MAX = 142; // Oracle: max(?)
public static final int SQL_FUNC_MIN = 143; // Oracle: min(?)
public static final int SQL_FUNC_AVG = 144; // Oracle: avg(?)
// Decode
public static final int SQL_FUNC_DECODE = 150; // Oracle: "decode(? {0})" SQL: "case ?{0} end"
public static final int SQL_FUNC_DECODE_SEP = 151; // Oracle: "," SQL: " "
public static final int SQL_FUNC_DECODE_PART = 152; // Oracle: "{0}, {1}" SQL: "when {0} then {1}"
public static final int SQL_FUNC_DECODE_ELSE = 153; // Oracle: "{0}" SQL: "else {0}"
// Flag whether or not to set column defaults when crating DDL statements
protected boolean ddlColumnDefaults = false;
// Illegal name chars and reserved SQL keywords
protected static final char[] ILLEGAL_NAME_CHARS = new char[] { '@', '?', '>', '=', '<', ';', ':',
'/', '.', '-', ',', '+', '*', ')', '(',
'\'', '&', '%', '!', ' '
};
protected static final String[] GENERAL_SQL_KEYWORDS = new String[] { "user", "group",
"table", "column", "view", "index", "constraint",
"select", "udpate", "insert", "alter", "delete",
"order" };
protected final Set<String> reservedSQLKeywords;
/**
* This interface is used to set the auto generated keys when executing insert statements.
*/
public interface DBSetGenKeys
{
void set(Object value);
}
/**
* This class is used to emulate sequences by using a sequence table.
* It is used with the executeSQL function and only required for insert statements
*/
public static class DBSeqTable extends DBTable
{
private final static long serialVersionUID = 1L;
public DBColumn C_SEQNAME;
public DBColumn C_SEQVALUE;
public DBColumn C_TIMESTAMP;
/**
* Constructor
*
* @param tableName the table name
* @param db the database object
*/
public DBSeqTable(String tableName, DBDatabase db)
{
super(tableName, db);
// Add all Colums
C_SEQNAME = addColumn("SeqName", DataType.VARCHAR, 40, true);
C_SEQVALUE = addColumn("SeqValue", DataType.INTEGER, 0, true);
C_TIMESTAMP = addColumn("SeqTime", DataType.DATETIME, 0, true);
// Primary Key
setPrimaryKey(new DBColumn[] { C_SEQNAME });
}
// Overrideable
public Object getNextValue(String SeqName, long minValue, Connection conn)
{
DBDatabaseDriver driver = db.getDriver();
// Create a Command
PreparedStatement stmt = null;
try
{ // The select Statement
DBCommand cmd = driver.createCommand(db);
DBCmdParam nameParam = cmd.addParam(SeqName);
cmd.select(C_SEQVALUE);
cmd.select(C_TIMESTAMP);
cmd.where (C_SEQNAME.is(nameParam));
String selectCmd = cmd.getSelect();
// Get the next Value
long seqValue = 0;
while (seqValue == 0)
{
// stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
stmt = conn.prepareStatement(selectCmd, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
stmt.setString(1, SeqName);
// Query existing value
ResultSet rs = stmt.executeQuery();
if (rs.next())
{ // Read the Sequence Value
seqValue = Math.max(rs.getLong(1) + 1, minValue);
java.sql.Timestamp current = rs.getTimestamp(2);
db.closeResultSet(rs);
// Update existing Record
cmd.clear();
DBCmdParam name = cmd.addParam(SeqName);
DBCmdParam time = cmd.addParam(current);
cmd.set(C_SEQVALUE.to(seqValue));
cmd.set(C_TIMESTAMP.to(DBDatabase.SYSDATE));
cmd.where(C_SEQNAME.is(name));
cmd.where(C_TIMESTAMP.is(time));
if (driver.executeSQL(cmd.getUpdate(), cmd.getParamValues(), conn, null) < 1)
seqValue = 0; // Try again
}
else
{ // Close Reader
db.closeResultSet(rs);
// sequence does not exist
seqValue = minValue;
log.warn("Sequence {} does not exist! Creating sequence with start-value of {}", SeqName, seqValue);
// create a new sequence entry
cmd.clear();
cmd.set(C_SEQNAME.to(SeqName));
cmd.set(C_SEQVALUE.to(seqValue));
cmd.set(C_TIMESTAMP.to(DBDatabase.SYSDATE));
if (driver.executeSQL(cmd.getInsert(), cmd.getParamValues(), conn, null) < 1)
seqValue = 0; // Try again
}
// check for concurrency problem
if (seqValue == 0)
log.warn("Failed to increment sequence {}. Trying again!", SeqName);
// close
db.closeStatement(stmt);
cmd.clear();
rs = null;
}
if (log.isInfoEnabled())
log.info("Sequence {} incremented to {}.", SeqName, seqValue);
return new Long(seqValue);
} catch (SQLException e) {
// throw exception
throw new EmpireSQLException(this, e);
} finally
{ // Cleanup
db.closeStatement(stmt);
}
}
}
/**
* Constructor
*/
public DBDatabaseDriver()
{
// Initialize List of reserved Keywords
reservedSQLKeywords = new HashSet<String>(GENERAL_SQL_KEYWORDS.length);
for (String keyWord:GENERAL_SQL_KEYWORDS){
reservedSQLKeywords.add(keyWord);
}
}
/**
* This function creates a DBCommand derived object this database
* @param db the database for which to create a command object for
* @return a DBCommand object
*/
public abstract DBCommand createCommand(DBDatabase db);
/**
* This function gives the driver a chance to provide a custom implementation
* for a combined command such as UNION or INTERSECT
* @param left the left command
* @param keyWord the key word (either "UNION" or "INTERSECT")
* @param left the right command
* @return a DBCommandExpr object
*/
public DBCommandExpr createCombinedCommand(DBCommandExpr left, String keyWord, DBCommandExpr right)
{
return new DBCombinedCmd(left, keyWord, right);
}
/**
* Returns whether or not a particular feature is supported by this driver
* @param type type of requested feature. @see DBDriverFeature
* @return true if the features is supported or false otherwise
*/
public abstract boolean isSupported(DBDriverFeature type);
/**
* Detects whether a table or column name needs to be quoted or not<br>
* By default all reserved SQL keywords as well as names
* containing a "-", "/", "+" or " " require quoting.<br>
* Overrides this function to add database specific keywords like "user" or "count"
*/
protected boolean detectQuoteName(String name)
{
// Check for reserved names
if (reservedSQLKeywords.contains(name.toLowerCase()))
return true;
// Check for illegalNameChars
int len = name.length();
for (int i=0; i<len; i++)
{ char c = name.charAt(i);
for (int j=0; j<ILLEGAL_NAME_CHARS.length; j++)
{ char ic = ILLEGAL_NAME_CHARS[j];
if (c>ic)
break;
if (c==ic)
return true;
}
}
// Quoting not necessary
return false;
}
/**
* Appends a table, view or column name to an SQL phrase.
*
* @param sql the StringBuilder containing the SQL phrase.
* @param name the name of the object (table, view or column)
* @param useQuotes use quotes or not
*/
public void appendElementName(StringBuilder sql, String name, boolean useQuotes)
{
// Check whether to use quotes or not
if (useQuotes)
sql.append(getSQLPhrase(DBDatabaseDriver.SQL_QUOTES_OPEN));
// Append Name
sql.append(name);
// End Quotes
if (useQuotes)
sql.append(getSQLPhrase(DBDatabaseDriver.SQL_QUOTES_CLOSE));
}
/**
* Appends a table, view or column name to an SQL phrase.
* @param sql the StringBuilder containing the SQL phrase.
* @param name the name of the object (table, view or column)
*/
public final void appendElementName(StringBuilder sql, String name)
{
appendElementName(sql, name, detectQuoteName(name));
}
/**
* Returns an sql phrase template for this database system.<br>
* Templates for sql function expressions must contain a '?' character which will be
* replaced by the current column expression.<br>
* If other parameters are necessary the template must contain placeholders like {0}, {1} etc.
* @param phrase the identifier of the phrase
* @return the phrase template
*/
public abstract String getSQLPhrase(int phrase);
/**
* Returns a data type convertion phrase template for this driver<br>
* The returned template must contain a '?' which will be replaced by a column expression.
* @param destType the target data type
* @param srcType the source data type
* @param format additional formatting information (optional)
* @return the data conversion phrase template
*/
public abstract String getConvertPhrase(DataType destType, DataType srcType, Object format);
/**
* Returns the next value of a named sequence The numbers are used for fields of type DBExpr.DT_AUTOINC.<BR>
* If a driver supports this function it must return true for isSupported(DBDriverFeature.SEQUENCES).
*
* @param db the database
* @param SeqName the name of the sequence
* @param minValue the minimum value of the sequence
* @param conn a valid database connection
* @return a new unique sequence value or null if an error occurred
*/
public abstract Object getNextSequenceValue(DBDatabase db, String SeqName, int minValue, Connection conn);
/**
* Returns an expression for creating a sequence value.
* This is intended for the use with INSERT INTO statements where many records are affected.
* @param col the column for which to obtain an expression providing the next sequence value
* @return an expression for the next sequence value
*/
public abstract DBColumnExpr getNextSequenceValueExpr(DBTableColumn column);
/**
* Returns an auto-generated value for a particular column
*
* @param db the database
* @param column the column for which a value is required
* @param conn a valid database connection
* @return the auto-generated value
*/
public Object getColumnAutoValue(DBDatabase db, DBTableColumn column, Connection conn)
{
// Supports sequences?
DataType type = column.getDataType();
if (type == DataType.AUTOINC)
{ // Use a numeric sequence
if (isSupported(DBDriverFeature.SEQUENCES)==false)
return null; // Create Later
String SeqName = column.getSequenceName();
return db.getNextSequenceValue(SeqName, conn);
}
else if (type== DataType.UNIQUEID)
{ // emulate using java.util.UUID
return UUID.randomUUID();
}
else if (type==DataType.DATE || type==DataType.DATETIME || type==DataType.TIMESTAMP)
{ // Get database system's date and time
Date ts = db.getUpdateTimestamp(conn);
return (type==DataType.DATE ? DateUtils.getDateOnly(ts) : ts);
}
// Other types
throw new NotSupportedException(this, "getColumnAutoValue() for "+type);
}
/**
* Prepares an sql statement by setting the supplied objects as parameters.
*
* @param pstmt the prepared statement
* @param sqlParams list of objects
*/
protected void prepareStatement(PreparedStatement pstmt, Object[] sqlParams)
throws SQLException
{
for (int i=0; i<sqlParams.length; i++)
{
Object value = sqlParams[i];
try {
addStatementParam(pstmt, i+1, value); // , conn
} catch(SQLException e) {
log.error("SQLException: Unable to set prepared statement parameter {} to '{}'", i+1, StringUtils.toString(value));
throw e;
}
}
}
/**
* Adds a statement parameter to a prepared statement
*
* @param pstmt the prepared statement
* @param paramIndex the parameter index
* @param value the parameter value
*/
protected void addStatementParam(PreparedStatement pstmt, int paramIndex, Object value)
throws SQLException
{
if (value instanceof DBBlobData)
{
// handling for blobs
DBBlobData blobData = (DBBlobData)value;
pstmt.setBinaryStream(paramIndex, blobData.getInputStream(), blobData.getLength());
// log
if (log.isDebugEnabled())
log.debug("Statement param {} set to BLOB data", paramIndex);
}
else if(value instanceof DBClobData)
{
// handling for clobs
DBClobData clobData = (DBClobData)value;
pstmt.setCharacterStream(paramIndex, clobData.getReader(), clobData.getLength());
// log
if (log.isDebugEnabled())
log.debug("Statement param {} set to CLOB data", paramIndex);
}
else if(value instanceof Date && !(value instanceof Timestamp))
{
// handling for dates
Timestamp ts = new Timestamp(((Date)value).getTime());
pstmt.setObject(paramIndex, ts);
// log
if (log.isDebugEnabled())
log.debug("Statement param {} set to date '{}'", paramIndex, ts);
}
else if((value instanceof Character)
|| (value instanceof Enum<?>))
{
// Objects that need String conversion
String strval = value.toString();
pstmt.setObject(paramIndex, strval);
// log
if (log.isDebugEnabled())
log.debug("Statement param {} set to '{}'", paramIndex, strval);
}
else
{ // simple parameter value
pstmt.setObject(paramIndex, value);
// log
if (log.isDebugEnabled())
log.debug("Statement param {} set to '{}'", paramIndex, value);
}
}
/**
* Extracts native error message of an sqlExeption.
*
* @param e the SQLException
* @return the error message of the database
*/
public String extractErrorMessage(SQLException e)
{
return e.getMessage();
}
/**
* <P>
* Reads a single column value from the given JDBC ResultSet and returns a value object of desired data type.<BR>
* See {@link DBExpr#getValueClass(DataType)} for java class type mapping.
* <P>
* This gives the driver the opportunity to change the value
* i.e. to simulate missing data types with other types.
* <P>
* @param rset the sql Resultset with the current data row
* @param columnIndex one based column Index of the desired column
* @param dataType the required data type
*
* @return the value of the Column
*
* @throws SQLException if a database access error occurs
*/
public Object getResultValue(ResultSet rset, int columnIndex, DataType dataType)
throws SQLException
{
if (dataType == DataType.DATETIME || dataType == DataType.TIMESTAMP)
{ // Get Timestamp (do not use getObject()!)
return rset.getTimestamp(columnIndex);
}
else if (dataType == DataType.CLOB)
{
java.sql.Clob clob = rset.getClob(columnIndex);
return ((clob != null) ? clob.getSubString(1, (int) clob.length()) : null);
}
else if (dataType == DataType.BLOB)
{ // Get bytes of a binary large object
java.sql.Blob blob = rset.getBlob(columnIndex);
return ((blob != null) ? blob.getBytes(1, (int) blob.length()) : null);
}
else
{
return rset.getObject(columnIndex);
}
}
/**
* Executes the select, update or delete SQL-Command with a Statement object.
*
* @param sqlCmd the SQL-Command
* @param sqlParams array of sql command parameters used for prepared statements (Optional).
* @param conn a valid connection to the database.
* @param genKeys allows to set the auto generated key of a record (INSERT statements only)
*
* @return the row count for insert, update or delete or 0 for SQL statements that return nothing
*
* @throws SQLException if a database access error occurs
*/
public int executeSQL(String sqlCmd, Object[] sqlParams, Connection conn, DBSetGenKeys genKeys)
throws SQLException
{ // Execute the Statement
Statement stmt = null;
try
{
int count = 0;
if (sqlParams!=null)
{ // Use a prepared statement
PreparedStatement pstmt = (genKeys!=null)
? conn.prepareStatement(sqlCmd, Statement.RETURN_GENERATED_KEYS)
: conn.prepareStatement(sqlCmd);
stmt = pstmt;
prepareStatement(pstmt, sqlParams);
count = pstmt.executeUpdate();
}
else
{ // Execute a simple statement
stmt = conn.createStatement();
count = (genKeys!=null)
? stmt.executeUpdate(sqlCmd, Statement.RETURN_GENERATED_KEYS)
: stmt.executeUpdate(sqlCmd);
}
// Retrieve any auto-generated keys
if (genKeys!=null && count>0)
{ // Return Keys
ResultSet rs = stmt.getGeneratedKeys();
try {
while(rs.next())
{
genKeys.set(rs.getObject(1));
}
} finally {
rs.close();
}
}
// done
return count;
} finally
{
close(stmt);
}
}
/**
* Executes a list of sql statements as batch
* @param sqlCmd
* @param sqlCmdParams
* @param conn
* @return
* @throws SQLException
*/
public int[] executeBatch(String[] sqlCmd, Object[][] sqlCmdParams, Connection conn)
throws SQLException
{ // Execute the Statement
if (sqlCmdParams!=null)
{ // Use a prepared statement
PreparedStatement pstmt = null;
try
{
int pos=0;
String lastCmd = null;
int[] result = new int[sqlCmd.length];
for (int i=0; i<=sqlCmd.length; i++)
{ // get cmd
String cmd = (i<sqlCmd.length ? sqlCmd[i] : null);
if (StringUtils.compareEqual(cmd, lastCmd, true)==false)
{ // close last statement
if (pstmt!=null)
{ // execute and close
log.debug("Executing batch containing {} statements", i-pos);
int[] res = pstmt.executeBatch();
for (int j=0; j<res.length; j++)
result[pos+j]=res[j];
pos+=res.length;
close(pstmt);
pstmt = null;
}
// has next?
if (cmd==null)
break;
// new statement
log.debug("Creating prepared statement for batch: "+cmd);
pstmt = conn.prepareStatement(cmd);
lastCmd = cmd;
}
// add batch
if (sqlCmdParams[i]!=null)
{
prepareStatement(pstmt, sqlCmdParams[i]);
}
log.debug("Adding batch with {} params.", (sqlCmdParams[i]!=null ? sqlCmdParams[i].length : 0));
pstmt.addBatch();
}
return result;
} finally {
close(pstmt);
}
}
else
{ // Execute a simple statement
Statement stmt = conn.createStatement();
try {
for (int i=0; i<sqlCmd.length; i++)
{
String cmd = sqlCmd[i];
log.debug("Adding statement to batch: "+cmd);
stmt.addBatch(cmd);
}
log.debug("Executing batch containing {} statements", sqlCmd.length);
int result[] = stmt.executeBatch();
return result;
} finally {
close(stmt);
}
}
}
// executeQuery
public ResultSet executeQuery(String sqlCmd, Object[] sqlParams, boolean scrollable, Connection conn)
throws SQLException
{
Statement stmt = null;
try
{ // Set scroll type
int type = (scrollable ? ResultSet.TYPE_SCROLL_INSENSITIVE
: ResultSet.TYPE_FORWARD_ONLY);
// Create an execute a query statement
if (sqlParams!=null)
{ // Use prepared statement
PreparedStatement pstmt = conn.prepareStatement(sqlCmd, type, ResultSet.CONCUR_READ_ONLY);
stmt = pstmt;
prepareStatement(pstmt, sqlParams);
return pstmt.executeQuery();
} else
{ // Use simple statement
stmt = conn.createStatement(type, ResultSet.CONCUR_READ_ONLY);
return stmt.executeQuery(sqlCmd);
}
} catch(SQLException e) {
// close statement (if not null)
log.error("Error executing query '"+sqlCmd+"' --> "+e.getMessage(), e);
close(stmt);
throw e;
}
}
// close
protected void close(Statement stmt)
{
try
{ // Statement close
if (stmt != null)
stmt.close();
} catch (SQLException sqle)
{
log.error("close statement:" + sqle.toString());
}
}
/**
* Creates a sql string for a given value.
* Text will be enclosed in single quotes and existing single quotes will be doubled.
* Empty strings are treated as null.
* Syntax of Date, Datetime and Boolean values are vendor specific.
*
* @param value the value which is inserted to the new String
* @param type the sql data type of the supplied value
* @return the sql string representing this value
*/
public String getValueString(Object value, DataType type)
{
if (ObjectUtils.isEmpty(value))
{
return getSQLPhrase(SQL_NULL_VALUE);
}
// set string buffer
switch (type)
{
case DATE:
return getSQLDateTimeString(value, SQL_DATE_TEMPLATE, SQL_DATE_PATTERN, SQL_CURRENT_DATE);
case DATETIME:
// Only date (without time) provided?
if (!DBDatabase.SYSDATE.equals(value) && !(value instanceof Date) && ObjectUtils.lengthOf(value)<=10)
return getSQLDateTimeString(value, SQL_DATE_TEMPLATE, SQL_DATE_PATTERN, SQL_CURRENT_TIMESTAMP);
// Complete Date-Time Object with time
return getSQLDateTimeString(value, SQL_DATETIME_TEMPLATE, SQL_DATETIME_PATTERN, SQL_CURRENT_TIMESTAMP);
case TIMESTAMP:
return getSQLDateTimeString(value, SQL_TIMESTAMP_TEMPLATE, SQL_TIMESTAMP_PATTERN, SQL_CURRENT_TIMESTAMP);
case TEXT:
case VARCHAR:
case CHAR:
case CLOB:
case UNIQUEID:
{ // Text value
return getSQLTextString(type, value);
}
case BOOL:
{ // Get Boolean value
boolean boolVal = false;
if (value instanceof Boolean)
{ boolVal = ((Boolean) value).booleanValue();
}
else
{ // Boolean from String
boolVal = stringToBoolean(value.toString());
}
return getSQLPhrase((boolVal) ? SQL_BOOLEAN_TRUE : SQL_BOOLEAN_FALSE);
}
case INTEGER:
case DECIMAL:
case FLOAT:
return getSQLNumberString(value, type);
case BLOB:
throw new NotSupportedException(this, "getValueString(?, DataType.BLOB)");
case AUTOINC:
case UNKNOWN:
/* Allow expressions */
return value.toString();
default:
log.warn("Unknown DataType {} for getValueString().", type);
return value.toString();
}
}
/**
* encodes a numeric value for an SQL command string.
* @param value the numeric value
* @param type the number data type
* @return the string reprentation of the number
*/
protected String getSQLNumberString(Object value, DataType type)
{
// already a number
if (value instanceof Number)
return value.toString();
// check if it is a number
String s = value.toString();
boolean integerOnly = (type==DataType.INTEGER);
for (int i=0; i<s.length(); i++)
{
char c = s.charAt(i);
if (c>='0' && c<='9')
continue; // OK
if (c=='-' || c=='+')
continue; // OK
if (c==' ' && i>0)
return s.substring(0,i);
// check
if (integerOnly || (c!='.' && c!=','))
throw new NumberFormatException(s);
}
return s;
}
/**
* encodes a Date value for an SQL command string.
* @param value
* @param sqlTemplate
* @param sqlPattern
* @param sqlCurrentDate
* @return
*/
protected String getSQLDateTimeString(Object value, int sqlTemplate, int sqlPattern, int sqlCurrentDate)
{
// is it a sysdate expression
if (DBDatabase.SYSDATE.equals(value))
return getSQLPhrase(sqlCurrentDate);
// Format the date (ymd)
Timestamp ts;
if ((value instanceof Timestamp))
{ // We have a timestamp
ts = (Timestamp)value;
}
else if ((value instanceof Date))
{ // Convert Date to Timestamp
ts = new Timestamp(((Date)value).getTime());
}
else
{ // "Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]"
String dtValue = value.toString().trim();
try
{ // parse timestamp
ts = Timestamp.valueOf(dtValue);
} catch (Throwable e) {
// Invalid date
log.error("Unable to parse date value "+dtValue, e);
throw new InvalidArgumentException("value", value);
}
}
// Convert to String
String pattern = getSQLPhrase(sqlPattern);
SimpleDateFormat sqlFormat = new SimpleDateFormat(getSQLPhrase(sqlPattern));
String datetime = sqlFormat.format(ts);
// Add micro / nanoseconds
int nanos = (ts.getNanos() % 1000000);
if (pattern.endsWith(".SSS") && nanos>0)
{ // Add nanoseconds
if (((nanos) % 100)>0)
datetime += String.format("%06d", nanos);
else
datetime += String.format("%04d",(nanos/100));
}
// Now Build String
String template = getSQLPhrase(sqlTemplate);
return StringUtils.replace(template, "{0}", datetime);
}
/**
* encodes Text values for an SQL command string.
* @param type date type (can only be TEXT, CHAR, CLOB and UNIQUEID)
* @param value the text to be encoded
* @return the encoded sql value
*/
protected String getSQLTextString(DataType type, Object value)
{
StringBuilder valBuf = new StringBuilder();
valBuf.append("'");
if (DBDatabase.EMPTY_STRING.equals(value)==false)
appendSQLTextValue(valBuf, value.toString());
valBuf.append("'");
return valBuf.toString();
}
/**
* this helper function doubles up single quotes for SQL
*/
protected void appendSQLTextValue(StringBuilder buf, String value)
{
if (value.indexOf('\'') >= 0)
{ // a routine to double up single quotes for SQL
int len = value.length();
for (int i = 0; i < len; i++)
{
if (value.charAt(i) == '\'')
buf.append("''");
else
buf.append(value.charAt(i));
}
}
else
{
buf.append(value);
}
}
/**
* this function converts a string containing a boolean expression to a boolean.
* @param value the string containing a boolean expression
* @return true if the string contains either "true", "y" or "1" or false otherwise
*/
protected boolean stringToBoolean(final String value)
{
return "1".equals(value) ||
"true".equalsIgnoreCase(value) ||
"y".equalsIgnoreCase(value);
}
/**
* Called when a database is opened
*/
protected void attachDatabase(DBDatabase db, Connection conn)
{
// Override to implement attaching behaviour
}
/**
* Called when a database is closed
*/
protected void detachDatabase(DBDatabase db, Connection conn)
{
// Override to implement closing behaviour
}
/**
* Checks the database whether or not it is consistent with the description.
*
* @param db the database
* @param owner the owner
* @param conn the connection
*/
public void checkDatabase(DBDatabase db, String owner, Connection conn)
{
throw new NotImplementedException(this, "checkDatabase");
}
/**
* Appends the required DLL commands to create, drop or alter an object to the supplied DBDQLScript.
* @param type operation to perform (CREATE, DROP, ALTER)
* @param dbo the object for which to perform the operation (DBDatabase, DBTable, DBView, DBColumn, DBRelation)
* @param script the script to which to add the DDL command(s)
*/
public void getDDLScript(DBCmdType type, DBObject dbo, DBSQLScript script)
{
throw new NotImplementedException(this, "getDDLScript");
}
/**
* Appends a statement to enable or disable a foreign key relation.<br>
* The default is to drop or create the relation
* Override this method to provide different behavior for your database.
* @param r the foreign key relation which should be enabled or disabled
* @param enable true to enable the relation or false to disable
* @param script the script to which to add the DDL command(s)
*/
public void addEnableRelationStmt(DBRelation r, boolean enable, DBSQLScript script)
{
if (enable)
getDDLScript(DBCmdType.CREATE, r, script);
else
getDDLScript(DBCmdType.DROP, r, script);
}
/**
* @return <code>true</code> if column default values are created with dll statements or <code>false</code> if not
*/
public boolean isDDLColumnDefaults()
{
return ddlColumnDefaults;
}
/**
* Set true if column default values should be included in DDL Statements
*
* @param ddlColumnDefaults <code>true</code> if dll statements should include
* column default values or <code>false</code> if not
*/
public void setDDLColumnDefaults(boolean ddlColumnDefaults)
{
this.ddlColumnDefaults = ddlColumnDefaults;
}
/**
* Returns a timestamp that is used for record updates.
*
* @param conn the connection that might be used
*
* @return the current date and time.
*/
public java.sql.Timestamp getUpdateTimestamp(Connection conn)
{
// Default implementation
java.util.Date date = new java.util.Date();
return new java.sql.Timestamp(date.getTime());
}
}