blob: 31921a6b21d8a5f0ea84de0c63ca201e047d0177 [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.dbms.sqlite;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import org.apache.empire.data.DataType;
import org.apache.empire.db.DBColumnExpr;
import org.apache.empire.db.DBCommand;
import org.apache.empire.db.DBDDLGenerator;
import org.apache.empire.db.DBDDLGenerator.DDLActionType;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.db.DBJoinType;
import org.apache.empire.db.DBObject;
import org.apache.empire.db.DBSQLScript;
import org.apache.empire.db.DBTableColumn;
import org.apache.empire.db.expr.join.DBColumnJoinExpr;
import org.apache.empire.db.expr.join.DBJoinExpr;
import org.apache.empire.dbms.DBMSHandler;
import org.apache.empire.dbms.DBMSHandlerBase;
import org.apache.empire.dbms.DBMSFeature;
import org.apache.empire.dbms.DBSqlPhrase;
import org.apache.empire.exceptions.NotImplementedException;
import org.apache.empire.exceptions.NotSupportedException;
import org.apache.empire.exceptions.UnexpectedReturnValueException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides support for the SQLite database system.<br>
*
*/
public class DBMSHandlerSQLite extends DBMSHandlerBase
{
// *Deprecated* private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DBMSHandlerSQLite.class);
/**
* Defines the SQLite command type.
*/
public static class DBCommandSQLite extends DBCommand
{
// *Deprecated* private static final long serialVersionUID = 1L;
/**
* @param dmbs the SQLLite database handler
* @param autoPrepareStmt flag whether to automatically prepare statements
* @see org.apache.empire.db.DBCommand
*/
public DBCommandSQLite(DBMSHandlerSQLite dmbs, boolean autoPrepareStmt)
{
super(dmbs, autoPrepareStmt);
}
@Override
public DBCommandSQLite join(DBJoinExpr join)
{
// http://www.sqlite.org/omitted.html
if (join.getType() != DBJoinType.LEFT) {
throw new NotImplementedException(join.getType(), join.getLeftTable().getName() + " join " + join.getRightTable().getName());
}
super.join(join);
return this;
}
@Override
public void addJoins(List<DBJoinExpr> joinExprList)
{
for (DBJoinExpr joinExpr : joinExprList)
{
if ((joinExpr instanceof DBColumnJoinExpr) &&
(joinExpr.getType() != DBJoinType.LEFT)) {
DBColumnJoinExpr join = (DBColumnJoinExpr)joinExpr;
throw new NotImplementedException(joinExpr.getType(), join.getLeft() + " join " + join.getRight());
}
}
/*
* Iterator<DBJoinExpr> iterator = joinExprList.iterator(); for
* (DBJoinExpr joinExpr = null; iterator.hasNext(); joinExpr =
* iterator.next()) { if(joinExpr.getType() != DBJoinType.LEFT) {
* iterator.remove(); } }
*/
super.addJoins(joinExprList);
}
}
private DBDDLGenerator<?> ddlGenerator = null; // lazy creation
/**
* Constructor for the SQLite database dbms.
*/
public DBMSHandlerSQLite()
{
setReservedKeywords();
}
private void addReservedKeyWord(final String keyWord)
{
boolean added = reservedSQLKeywords.add(keyWord.toLowerCase());
if (!added)
{
log.debug("Existing keyWord added: " + keyWord);
}
}
private void setReservedKeywords()
{
// list of reserved keywords
// http://www.sqlite.org/lang_keywords.html
addReservedKeyWord("ABORT".toLowerCase());
addReservedKeyWord("ACTION".toLowerCase());
addReservedKeyWord("ADD".toLowerCase());
addReservedKeyWord("AFTER".toLowerCase());
addReservedKeyWord("ALL".toLowerCase());
addReservedKeyWord("ALTER".toLowerCase());
addReservedKeyWord("ANALYZE".toLowerCase());
addReservedKeyWord("AND".toLowerCase());
addReservedKeyWord("AS".toLowerCase());
addReservedKeyWord("ASC".toLowerCase());
addReservedKeyWord("ATTACH".toLowerCase());
addReservedKeyWord("AUTOINCREMENT".toLowerCase());
addReservedKeyWord("BEFORE".toLowerCase());
addReservedKeyWord("BEGIN".toLowerCase());
addReservedKeyWord("BETWEEN".toLowerCase());
addReservedKeyWord("BY".toLowerCase());
addReservedKeyWord("CASCADE".toLowerCase());
addReservedKeyWord("CASE".toLowerCase());
addReservedKeyWord("CAST".toLowerCase());
addReservedKeyWord("CHECK".toLowerCase());
addReservedKeyWord("COLLATE".toLowerCase());
addReservedKeyWord("COLUMN".toLowerCase());
addReservedKeyWord("COMMIT".toLowerCase());
addReservedKeyWord("CONFLICT".toLowerCase());
addReservedKeyWord("CONSTRAINT".toLowerCase());
addReservedKeyWord("CREATE".toLowerCase());
addReservedKeyWord("CROSS".toLowerCase());
addReservedKeyWord("CURRENT_DATE".toLowerCase());
addReservedKeyWord("CURRENT_TIME".toLowerCase());
addReservedKeyWord("CURRENT_DATETIME".toLowerCase());
addReservedKeyWord("DATABASE".toLowerCase());
addReservedKeyWord("DEFAULT".toLowerCase());
addReservedKeyWord("DEFERRABLE".toLowerCase());
addReservedKeyWord("DEFERRED".toLowerCase());
addReservedKeyWord("DELETE".toLowerCase());
addReservedKeyWord("DESC".toLowerCase());
addReservedKeyWord("DETACH".toLowerCase());
addReservedKeyWord("DISTINCT".toLowerCase());
addReservedKeyWord("DROP".toLowerCase());
addReservedKeyWord("EACH".toLowerCase());
addReservedKeyWord("ELSE".toLowerCase());
addReservedKeyWord("END".toLowerCase());
addReservedKeyWord("ESCAPE".toLowerCase());
addReservedKeyWord("EXCEPT".toLowerCase());
addReservedKeyWord("EXCLUSIVE".toLowerCase());
addReservedKeyWord("EXISTS".toLowerCase());
addReservedKeyWord("EXPLAIN".toLowerCase());
addReservedKeyWord("FAIL".toLowerCase());
addReservedKeyWord("FOR".toLowerCase());
addReservedKeyWord("FOREIGN".toLowerCase());
addReservedKeyWord("FROM".toLowerCase());
addReservedKeyWord("FULL".toLowerCase());
addReservedKeyWord("GLOB".toLowerCase());
addReservedKeyWord("GROUP".toLowerCase());
addReservedKeyWord("HAVING".toLowerCase());
addReservedKeyWord("IF".toLowerCase());
addReservedKeyWord("IGNORE".toLowerCase());
addReservedKeyWord("IMMEDIATE".toLowerCase());
addReservedKeyWord("IN".toLowerCase());
addReservedKeyWord("INDEX".toLowerCase());
addReservedKeyWord("INDEXED".toLowerCase());
addReservedKeyWord("INITIALLY".toLowerCase());
addReservedKeyWord("INNER".toLowerCase());
addReservedKeyWord("INSERT".toLowerCase());
addReservedKeyWord("INSTEAD".toLowerCase());
addReservedKeyWord("INTERSECT".toLowerCase());
addReservedKeyWord("INTO".toLowerCase());
addReservedKeyWord("IS".toLowerCase());
addReservedKeyWord("ISNULL".toLowerCase());
addReservedKeyWord("JOIN".toLowerCase());
addReservedKeyWord("KEY".toLowerCase());
addReservedKeyWord("LEFT".toLowerCase());
addReservedKeyWord("LIKE".toLowerCase());
addReservedKeyWord("LIMIT".toLowerCase());
addReservedKeyWord("MATCH".toLowerCase());
addReservedKeyWord("NATURAL".toLowerCase());
addReservedKeyWord("NO".toLowerCase());
addReservedKeyWord("NOT".toLowerCase());
addReservedKeyWord("NOTNULL".toLowerCase());
addReservedKeyWord("NULL".toLowerCase());
addReservedKeyWord("OF".toLowerCase());
addReservedKeyWord("OFFSET".toLowerCase());
addReservedKeyWord("ON".toLowerCase());
addReservedKeyWord("OR".toLowerCase());
addReservedKeyWord("ORDER".toLowerCase());
addReservedKeyWord("OUTER".toLowerCase());
addReservedKeyWord("PLAN".toLowerCase());
addReservedKeyWord("PRAGMA".toLowerCase());
addReservedKeyWord("PRIMARY".toLowerCase());
addReservedKeyWord("QUERY".toLowerCase());
addReservedKeyWord("RAISE".toLowerCase());
addReservedKeyWord("REFERENCES".toLowerCase());
addReservedKeyWord("REGEXP".toLowerCase());
addReservedKeyWord("REINDEX".toLowerCase());
addReservedKeyWord("RELEASE".toLowerCase());
addReservedKeyWord("RENAME".toLowerCase());
addReservedKeyWord("REPLACE".toLowerCase());
addReservedKeyWord("RESTRICT".toLowerCase());
addReservedKeyWord("RIGHT".toLowerCase());
addReservedKeyWord("ROLLBACK".toLowerCase());
addReservedKeyWord("ROW".toLowerCase());
addReservedKeyWord("SAVEPOINT".toLowerCase());
addReservedKeyWord("SELECT".toLowerCase());
addReservedKeyWord("SET".toLowerCase());
addReservedKeyWord("TABLE".toLowerCase());
addReservedKeyWord("TEMP".toLowerCase());
addReservedKeyWord("TEMPORARY".toLowerCase());
addReservedKeyWord("THEN".toLowerCase());
addReservedKeyWord("TO".toLowerCase());
addReservedKeyWord("TRANSACTION".toLowerCase());
addReservedKeyWord("TRIGGER".toLowerCase());
addReservedKeyWord("UNION".toLowerCase());
addReservedKeyWord("UNIQUE".toLowerCase());
addReservedKeyWord("UPDATE".toLowerCase());
addReservedKeyWord("USING".toLowerCase());
addReservedKeyWord("VACUUM".toLowerCase());
addReservedKeyWord("VALUES".toLowerCase());
addReservedKeyWord("VIEW".toLowerCase());
addReservedKeyWord("VIRTUAL".toLowerCase());
addReservedKeyWord("WHEN".toLowerCase());
addReservedKeyWord("WHERE".toLowerCase());
}
/**
* Creates a new SQLite command object.
*
* @return the new DBCommandSQLite object
*/
@Override
public DBCommand createCommand(boolean autoPrepareStmt)
{
// create command object
return new DBCommandSQLite(this, autoPrepareStmt);
}
/**
* Returns whether or not a particular feature is supported by this dbms
*
* @param type
* type of requested feature. @see DBMSFeature
* @return true if the features is supported or false otherwise
*/
@Override
public boolean isSupported(DBMSFeature type)
{
switch (type)
{ // return support info
case QUERY_LIMIT_ROWS:
return true;
case QUERY_SKIP_ROWS:
return true;
default:
// All other features are not supported by default
return false;
}
}
/**
* Gets an sql phrase template for this database system.<br>
*
* @see DBMSHandler#getSQLPhrase(DBSqlPhrase)
* @return the phrase template
*/
@Override
public String getSQLPhrase(DBSqlPhrase phrase)
{
switch (phrase)
{
// sql-phrases
case SQL_NULL: return "null";
case SQL_PARAMETER: return " ? ";
case SQL_RENAME_TABLE: return " ";
case SQL_RENAME_COLUMN: return " AS ";
case SQL_DATABASE_LINK: return "@";
case SQL_QUOTES_OPEN: return "`";
case SQL_QUOTES_CLOSE: return "`";
case SQL_CONCAT_EXPR: return "concat(?, {0})";
// data types
case SQL_BOOLEAN_TRUE: return "1";
case SQL_BOOLEAN_FALSE: return "0";
case SQL_CURRENT_DATE: return "date('now','localtime');";
case SQL_DATE_PATTERN: return "yyyy-MM-dd";
case SQL_DATE_TEMPLATE: return "date('{0}')";
case SQL_CURRENT_TIME: return "time('now');";
case SQL_TIME_TEMPLATE: return "time('{0}')";
case SQL_DATETIME_PATTERN: return "yyyy-MM-dd hh:mm:ss.sss";
case SQL_DATETIME_TEMPLATE: return "'{0}'";
case SQL_CURRENT_TIMESTAMP: return "NOW()";
case SQL_TIMESTAMP_PATTERN: return "yyyy-MM-dd hh:mm:ss.sss";
case SQL_TIMESTAMP_TEMPLATE: return "'{0}'";
// functions
case SQL_FUNC_COALESCE: return "coalesce(?, {0})";
case SQL_FUNC_SUBSTRING: return "substring(?, {0})";
case SQL_FUNC_SUBSTRINGEX: return "substring(?, {0}, {1})";
case SQL_FUNC_REPLACE: return "replace(?, {0}, {1})";
case SQL_FUNC_REVERSE: return "reverse(?)";
case SQL_FUNC_STRINDEX: return "instr(?, {0})";
case SQL_FUNC_STRINDEXFROM: return "locate({0}, ?, {1})";
case SQL_FUNC_LENGTH: return "length(?)";
case SQL_FUNC_UPPER: return "upper(?)";
case SQL_FUNC_LOWER: return "lcase(?)";
case SQL_FUNC_TRIM: return "trim(?)";
case SQL_FUNC_LTRIM: return "ltrim(?)";
case SQL_FUNC_RTRIM: return "rtrim(?)";
case SQL_FUNC_ESCAPE: return "? escape {0:VARCHAR}";
// Numeric
case SQL_FUNC_ABS: return "abs(?)";
case SQL_FUNC_ROUND: return "round(?,{0})";
case SQL_FUNC_TRUNC: return "truncate(?,{0})";
case SQL_FUNC_CEILING: return "ceiling(?)";
case SQL_FUNC_FLOOR: return "floor(?)";
case SQL_FUNC_MOD: return "mod(?,{0})";
case SQL_FUNC_FORMAT: return "printf({0:VARCHAR}, ?)";
// Date
case SQL_FUNC_DAY: return "day(?)";
case SQL_FUNC_MONTH: return "month(?)";
case SQL_FUNC_YEAR: return "year(?)";
// Aggregation
case SQL_FUNC_SUM: return "sum(?)";
case SQL_FUNC_MAX: return "max(?)";
case SQL_FUNC_MIN: return "min(?)";
case SQL_FUNC_AVG: return "avg(?)";
// Others
case SQL_FUNC_DECODE: return "case ? {0} end";
case SQL_FUNC_DECODE_SEP: return " ";
case SQL_FUNC_DECODE_PART: return "when {0} then {1}";
case SQL_FUNC_DECODE_ELSE: return "else {0}";
// Not defined
default:
// log.warn("SQL phrase " + phrase.name() + " is not defined!");
return phrase.getSqlDefault();
}
}
/**
* @see DBMSHandler#getConvertPhrase(DataType, DataType, Object)
*/
@Override
public String getConvertPhrase(DataType destType, DataType srcType, Object format)
{
switch (destType)
{
// Convert to text
case VARCHAR:
case CHAR:
if (format != null)
{ // Convert using a format string
if (srcType == DataType.INTEGER || srcType == DataType.AUTOINC)
{
log.error("getConvertPhrase: unknown type " + destType);
return "?";
}
else
{
return "to_char(?, '" + format.toString() + "')";
}
}
return "convert(?, CHAR)";
case INTEGER:
{
return "convert(?, BIGINT)";
}
case DECIMAL:
{
return "convert(?, DECIMAL)";
}
case FLOAT:
{
return "convert(?, DOUBLE)";
}
// Unknown Type
default:
log.error("getConvertPhrase: unknown type (" + String.valueOf(destType));
return "?";
}
}
/**
* Override since
* conn.prepareStatement(sqlCmd, Statement.RETURN_GENERATED_KEYS)
* is not supported by SQLLite dbms
*/
@Override
public int executeSQL(String sqlCmd, Object[] sqlParams, Connection conn, DBSetGenKeys genKeys) throws SQLException
{
Statement stmt = null;
int count = 0;
try
{
if (sqlParams != null)
{ // Use a prepared statement
PreparedStatement pstmt = conn.prepareStatement(sqlCmd);
stmt = pstmt;
prepareStatement(pstmt, sqlParams);
count = pstmt.executeUpdate();
}
else
{ // Execute a simple statement
stmt = conn.createStatement();
count = stmt.executeUpdate(sqlCmd);
}
// Retrieve any auto-generated keys
if (genKeys != null && count > 0)
{ // Return Keys
ResultSet rs = stmt.getGeneratedKeys();
try
{ int rownum = 0;
while (rs.next())
{
genKeys.set(rownum++, rs.getObject(1));
}
}
finally
{
rs.close();
}
}
}
finally
{
closeStatement(stmt);
}
return count;
}
@Override
public Object getResultValue(ResultSet rset, int columnIndex, DataType dataType) throws SQLException
{
if (dataType == DataType.DATETIME || dataType == DataType.TIMESTAMP)
{
try {
// try timestamp
return rset.getTimestamp(columnIndex);
} catch(Exception ex) {
try
{ // try Convert from String
String datePattern = getSQLPhrase(DBSqlPhrase.SQL_DATETIME_PATTERN);
DateFormat dateFormat = new SimpleDateFormat(datePattern);
Date timestamp = dateFormat.parse(rset.getString(columnIndex));
return new java.sql.Timestamp(timestamp.getTime());
}
catch (ParseException e)
{
throw new UnexpectedReturnValueException(rset.getString(columnIndex), "getResultValue");
}
}
}
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);
}
}
/**
* Overridden. Returns a timestamp that is used for record updates created
* by the database server.
*
* @return the current date and time of the database server.
*/
@Override
public java.sql.Timestamp getUpdateTimestamp(Connection conn)
{
// Default implementation
GregorianCalendar cal = new GregorianCalendar();
return new java.sql.Timestamp(cal.getTimeInMillis());
}
/**
* @see DBMSHandler#getDDLScript(DDLActionType, DBObject, DBSQLScript)
*/
@Override
public void getDDLScript(DDLActionType type, DBObject dbo, DBSQLScript script)
{
if (ddlGenerator == null)
ddlGenerator = new SQLiteDDLGenerator(this);
// forward request
ddlGenerator.getDDLScript(type, dbo, script);
}
@Override
public Object getNextSequenceValue(DBDatabase db, String SeqName, int minValue, Connection conn)
{
throw new NotImplementedException(db, " sequence values are assigned dynamicaly from sqlite ");
}
/**
* @see DBMSHandlerBase#getNextSequenceValueExpr(DBTableColumn col)
*/
@Override
public DBColumnExpr getNextSequenceValueExpr(DBTableColumn column)
{
throw new NotSupportedException(this, "getNextSequenceValueExpr");
}
}