/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* Licensed 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 jef.database.dialect;
import java.io.File;
import java.sql.SQLException;
import org.apache.commons.lang.StringUtils;
import jef.database.ConnectInfo;
import jef.database.DbFunction;
import jef.database.dialect.ColumnType.AutoIncrement;
import jef.database.dialect.handler.LimitHandler;
import jef.database.dialect.handler.LimitOffsetLimitHandler;
import jef.database.exception.ViolatedConstraintNameExtracter;
import jef.database.meta.DbProperty;
import jef.database.meta.Feature;
import jef.database.query.Func;
import jef.database.query.Scientific;
import jef.database.query.function.CastFunction;
import jef.database.query.function.EmuDecodeWithCase;
import jef.database.query.function.EmuJDBCTimestampFunction;
import jef.database.query.function.EmuLRpadOnSqlite;
import jef.database.query.function.EmuTranslateByReplace;
import jef.database.query.function.NoArgSQLFunction;
import jef.database.query.function.StandardSQLFunction;
import jef.database.query.function.TemplateFunction;
import jef.database.query.function.TransformFunction;
import jef.database.query.function.VarArgsSQLFunction;
import jef.database.support.RDBMS;
import jef.tools.collection.CollectionUtils;
/**
* SQLite驱动方言,部分特性需要在驱动字符串后加上
* ?date_string_format=yyyy-MM-dd HH:mm:ss
*
* @author jiyi
*
*/
public class SqliteDialect extends AbstractDialect {
public SqliteDialect() {
features = CollectionUtils.identityHashSet();
features.add(Feature.SUPPORT_CONCAT);
features.add(Feature.AUTOINCREMENT_MUSTBE_PK);
features.add(Feature.TYPE_FORWARD_ONLY);
features.add(Feature.BATCH_GENERATED_KEY_ONLY_LAST);
features.add(Feature.NOT_SUPPORT_TRUNCATE);
features.add(Feature.NOT_SUPPORT_FOREIGN_KEY);
features.add(Feature.NOT_SUPPORT_USER_FUNCTION);
features.add(Feature.NOT_SUPPORT_SET_BINARY);
features.add(Feature.NOT_SUPPORT_INDEX_META);
features.add(Feature.NOT_SUPPORT_KEYWORD_DEFAULT);
features.add(Feature.NOT_SUPPORT_ALTER_DROP_COLUMN);
features.add(Feature.ONE_COLUMN_IN_SINGLE_DDL);
setProperty(DbProperty.ADD_COLUMN, "ADD COLUMN");
setProperty(DbProperty.MODIFY_COLUMN, "MODIFY COLUMN");
setProperty(DbProperty.DROP_COLUMN, "DROP COLUMN");
setProperty(DbProperty.CHECK_SQL, "select 1");
setProperty(DbProperty.SELECT_EXPRESSION, "select %s");
setProperty(DbProperty.WRAP_FOR_KEYWORD, "\"\"");
setProperty(DbProperty.GET_IDENTITY_FUNCTION, "select last_insert_rowid()");
registerCompatible(Func.concat, new VarArgsSQLFunction("", "||", ""));
registerNative(Scientific.soundex);
registerNative(Func.coalesce);
registerNative(Func.locate);
registerNative(Func.ceil);
registerNative(Func.floor);
registerNative(Func.round);
registerNative(Func.length);
registerNative(Func.lower);
registerNative(Func.upper);
registerNative(Func.trim);
registerNative(Func.ltrim);
registerNative(Func.rtrim);
registerNative(Func.nullif);
registerNative(Func.replace);
registerAlias("lcase", "lower");
registerAlias("ucase", "upper");
registerNative(new StandardSQLFunction("substr"));
registerAlias(Func.substring, "substr");
loadKeywords("mysql_keywords.properties");
/*
* 1) 关于SQLite取当前时区的问题
* 是SQLITE查询当前时间返回的不是本地时间而是GMT时间,因此使用current_timestamp或者now()查出的时间不正确。
* 遂将current_timestamp函数重写为"datetime('now','localtime'),测试案例能通过。
* 然而很快发现,当创建表的时候, default值用函数表达式则建表出错。目前还没想好怎么解决,此处先不改。
* 2014-7-21
*
* 2) 今天用Sqlite驱动3.8.11.2测试,发现原来的default不支持是误解,只要在函数两边加上括号(),就可以在default值中使用函数。
* 但是随后发现新的问题。由于使用datetime('now','localtime')会将日期格式化成 yyyy-MM-dd HH:mm:ss。
* 但默认的Sqlite日期格式为 yyyy-MM-dd HH:mm:ss.SSS。这造成该张表的数据无法读出(从ResultSet中get时获取抛出异常),
* 目前解决办法是——在jdbc url中增加 ?date_string_format=yyyy-MM-dd HH:mm:ss。
* a)该方法仅对 3.8以后的版本有效
* TODO b)Document it into FAQ.
* 2016-1-12
*
*/
registerCompatible(Func.current_timestamp, new NoArgSQLFunction("datetime('now','localtime')", false), "now", "sysdate");
registerAlias(Func.now, "current_timestamp");
registerNative(Func.current_time, new NoArgSQLFunction("current_time", false));
registerNative(Func.current_date, new NoArgSQLFunction("current_date", false));
registerCompatible(Func.mod, new TemplateFunction("mod", "(%1$s %% %2$s)"));
registerCompatible(Func.locate, new TransformFunction("locate", "instr", new int[] { 2, 1 }));// 用instr来模拟locate参数相反
registerNative(new StandardSQLFunction("instr"));
registerNative(new StandardSQLFunction("ifnull"));
registerAlias(Func.nvl, "ifnull");
registerNative(new StandardSQLFunction("hex"));
registerNative(new StandardSQLFunction("like"));
registerNative(new StandardSQLFunction("likelihood"));
registerNative(new StandardSQLFunction("load_extension"));
registerNative(new StandardSQLFunction("quote"));
registerNative(new StandardSQLFunction("random"));
registerNative(new StandardSQLFunction("randomblob"));
registerNative(new StandardSQLFunction("zeroblob"));
registerNative(new StandardSQLFunction("sqlite_compileoption_get"));
registerNative(new StandardSQLFunction("sqlite_compileoption_used"));
registerNative(new StandardSQLFunction("sqlite_source_id"));
registerNative(new StandardSQLFunction("sqlite_version"));
registerNative(new StandardSQLFunction("typeof"));
registerNative(new StandardSQLFunction("unlikely"));
registerNative(new StandardSQLFunction("unicode"));
registerNative(new StandardSQLFunction("total"));
registerNative(new StandardSQLFunction("total_changes"));
registerNative(new StandardSQLFunction("datetime"));
registerNative(Func.date);
registerNative(Func.time);
/**
* Functions define in extension-function.c
*/
registerNative(new StandardSQLFunction("padl"));
registerNative(new StandardSQLFunction("padr"));
registerNative(new StandardSQLFunction("padc"));
registerNative(new StandardSQLFunction("strfilter"));
registerNative(Func.cast, new CastFunction());
registerCompatible(Func.str, new TemplateFunction("str", "cast(%s as char)"));
registerCompatible(Func.lpad, new EmuLRpadOnSqlite(true));
registerCompatible(Func.rpad, new EmuLRpadOnSqlite(false));
registerCompatible(Func.year, new TemplateFunction("year", "strftime('%%Y',%s)"));
registerCompatible(Func.month, new TemplateFunction("year", "strftime('%%m',%s)"));
registerCompatible(Func.day, new TemplateFunction("year", "strftime('%%d',%s)"));
registerCompatible(Func.hour, new TemplateFunction("year", "strftime('%%H',%s)"));
registerCompatible(Func.minute, new TemplateFunction("year", "strftime('%%M',%s)"));
registerCompatible(Func.second, new TemplateFunction("year", "strftime('%%S',%s)"));
registerCompatible(Func.datediff, new TemplateFunction("datediff", "cast(julianday(%1$s) - julianday(%2$s) as integer)"));
registerCompatible(Func.adddate, new TemplateFunction("adddate", "datetime(%1$s,'localtime','%2$s day')"));
registerCompatible(Func.subdate, new TemplateFunction("subdate", "datetime(%1$s,'localtime','-%2$s day')"));
registerCompatible(Func.add_months, new TemplateFunction("add_months", "datetime(%1$s,'localtime','%2$s month')"));
registerCompatible(Func.timestampdiff, new EmuJDBCTimestampFunction(Func.timestampdiff, this));
registerCompatible(Func.timestampadd, new EmuJDBCTimestampFunction(Func.timestampadd, this));
registerCompatible(Func.trunc, new TemplateFunction("trunc", "cast(%s as integer)"));//FIXME: the minus value -10.5 will be floor to -11.
registerCompatible(Func.lengthb, new TemplateFunction("rpad", "length(hex(%s))/2"));
registerCompatible(Func.translate, new EmuTranslateByReplace());
registerCompatible(Func.decode, new EmuDecodeWithCase());
}
@Override
protected String getComment(AutoIncrement column, boolean flag) {
StringBuilder sb = new StringBuilder();
sb.append("integer primary key autoincrement");
if (flag) {
if (!column.nullable)
sb.append(" not null");
}
return sb.toString();
}
public String getDriverClass(String url) {
return "org.sqlite.JDBC";
}
public int getPort() {
return 0;
}
public RDBMS getName() {
return RDBMS.sqlite;
}
public void parseDbInfo(ConnectInfo connectInfo) {
String url = connectInfo.getUrl();
if (!url.startsWith("jdbc:sqlite:")) {
throw new IllegalArgumentException(url);
}
String dbpath = url.substring(12);
dbpath=StringUtils.substringBefore(dbpath, "?");
File file = new File(dbpath);
connectInfo.setDbname(file.getName());
connectInfo.setHost("");
}
private final LimitHandler limit = new LimitOffsetLimitHandler();
@Override
public LimitHandler getLimitHandler() {
return limit;
}
private static ViolatedConstraintNameExtracter EXTRACTER = new ViolatedConstraintNameExtracter() {
@Override
public String extractConstraintName(SQLException sqle) {
String message = sqle.getMessage();
if (message.startsWith("[SQLITE_CONSTRAINT]") || message.startsWith("UNIQUE constraint")) {
return message;
} else if ("PRIMARY KEY must be unique".equals(message)) {
return message;
}
return null;
}
};
@Override
public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() {
return EXTRACTER;
}
@Override
public String toDefaultString(Object defaultValue, int sqlType, int changeTo) {
if (defaultValue instanceof DbFunction) {
return "("+this.getFunction((DbFunction) defaultValue)+")";
}
return super.toDefaultString(defaultValue, sqlType, changeTo);
}
}