/* * 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.sql.SQLException; import java.sql.Types; import java.util.Arrays; import jef.common.log.LogUtil; import jef.database.ConnectInfo; import jef.database.ORMConfig; import jef.database.annotation.DateGenerateType; import jef.database.dialect.ColumnType.AutoIncrement; import jef.database.dialect.handler.LimitHandler; import jef.database.dialect.handler.MySqlLimitHandler; import jef.database.exception.ViolatedConstraintNameExtracter; import jef.database.jsqlparser.expression.BinaryExpression; import jef.database.jsqlparser.expression.Function; import jef.database.jsqlparser.expression.Interval; import jef.database.meta.Column; 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.EmuDecodeWithIf; import jef.database.query.function.EmuTranslateByReplace; import jef.database.query.function.MySQLTruncate; import jef.database.query.function.NoArgSQLFunction; import jef.database.query.function.StandardSQLFunction; import jef.database.query.function.TemplateFunction; import jef.database.support.RDBMS; import jef.tools.ArrayUtils; import jef.tools.collection.CollectionUtils; import jef.tools.string.JefStringReader; /** * MySQL 特性 * <p> * <ol> * <li>自增特性 AUTO_INCREMENT 表示自增 SELECT LAST_INSERT_ID();获取自增量值 SELECT @@IDENTITY * 获取新分配的自增量值</li> * * <li>支持无符号数、支持BLOB,支持TEXT。数据类型较多。但是考虑兼容性,很多都不在本框架使用</li> * * <li>MYSQL VARCHAR长度 最大长度65535,在utf8编码时最大65535/3=21785,GBK则是65535/2. * 但是用varchar做主键的长度是受MYSQL索引限制的。 MYSQL索引只能索引768个字节。(过去某个版本好像是1000字节) * 当数据库默认使用GBK编码时。这个长度是383. UTF8这个是255,latin5是767. (建表时可以在尾部加上charset=latin5; * 来指定表的语言。) <br> * 因此在MYSQL中不能将长度超过这个限制的字段设置为主键。一般来说设置varchar也就到255,这是比较安全的。</li> * * <li>MY SQL中对不同大小的文有多种类型 * <ul> * <li>TINYTEXT,最大长度为255,占用空间也是(实际长度+1);</li> * <li>TEXT,最大长度65535,占用空间是(实际长度+2);</li> * <li>MEDIUMTEXT,最大长度16777215,占用空间是(实际长度+3);</li> * <li>LONGTEXT,最大长度4294967295,占用空间是(实际长度+4)。</li> * </ul> * * <li>BLOB和TEXT 作为主键、或者建立索引时,需要指定索引长度,限制见上。</li> * * <li>日期DATE、TIME、DATETIME、TIMESTAMP和YEAR等。</li> * </ol> * * <p> * 常用命令 * <ol> * <li>查看表的所有信息:show create table 表名;</li> * <li>添加主键约束:alter table 表名 add constraint 主键 (形如:PK_表名) primary key 表名(主键字段); * <li>添加外键约束:alter table 从表 add constraint 外键(形如:FK_从表_主表) foreign key 从表(外键字段) * references 主表(主键字段); * <li>删除主键约束:alter table 表名 drop primary key;</li> * <li>删除外键约束:alter table 表名 drop foreign key 外键(区分大小写);</li> * <li>修改表名: alter table t_book rename to bbb;</li> * <li>添加列: alter table 表名 add column 列名 varchar(30);</li> * <li>删除列: alter table 表名 drop column 列名;</li> * <li>修改列名: alter table bbb change nnnnn hh int;</li> * <li>修改列属性:alter table t_book modify name varchar(22);</li> * </ol> * * MySQL的四种BLOB类型 类型 大小(单位:字节) TinyBlob 最大 255 Blob 最大 65K MediumBlob 最大 16M * LongBlob 最大 4G * * * 遗留问题:MySQL能不能做成表名大小写不敏感的。目前是敏感的。这造成大小写不一致会认为是两张表。 */ public class MySqlDialect extends AbstractDialect { public MySqlDialect() { // 在MYSQL中 ||是逻辑运算符 features = CollectionUtils.identityHashSet(); features.addAll(Arrays.asList(Feature.DBNAME_AS_SCHEMA, Feature.SUPPORT_INLINE_COMMENT,Feature.ALTER_FOR_EACH_COLUMN, Feature.NOT_FETCH_NEXT_AUTOINCREAMENTD, Feature.SUPPORT_LIMIT, Feature.COLUMN_DEF_ALLOW_NULL)); setProperty(DbProperty.ADD_COLUMN, "ADD"); setProperty(DbProperty.MODIFY_COLUMN, "MODIFY"); 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_ID()"); setProperty(DbProperty.INDEX_LENGTH_LIMIT, "767"); setProperty(DbProperty.INDEX_LENGTH_LIMIT_FIX, "255"); setProperty(DbProperty.INDEX_LENGTH_CHARESET_FIX, "charset=latin5"); setProperty(DbProperty.DROP_INDEX_TABLE_PATTERN, "%1$s ON %2$s"); setProperty(DbProperty.DROP_FK_PATTERN, "alter table %1$s drop foreign key %2$s"); loadKeywords("mysql_keywords.properties"); registerNative(new StandardSQLFunction("ascii")); registerNative(new StandardSQLFunction("bin")); registerNative(new StandardSQLFunction("char_length"), "character_length"); registerNative(new StandardSQLFunction("length")); registerAlias(Func.lengthb, "length"); registerAlias(Func.length, "char_length"); registerNative(Func.lower, "lcase"); registerNative(Func.upper, "ucase"); registerNative(Func.locate); registerNative(new StandardSQLFunction("uuid")); registerNative(new StandardSQLFunction("ord")); registerNative(new StandardSQLFunction("quote")); registerNative(new StandardSQLFunction("reverse")); registerNative(Func.ltrim); registerNative(Func.rtrim); registerNative(Func.mod); registerNative(Func.coalesce); registerNative(Func.nullif); registerNative(Func.cast, new CastFunction()); registerNative(Scientific.soundex); registerNative(new StandardSQLFunction("space")); registerNative(new StandardSQLFunction("unhex")); registerNative(new StandardSQLFunction("truncate")); registerCompatible(Func.trunc, new MySQLTruncate());// MYSQL的truncate函数因为是必须双参数的,其他数据库的允许单参数 registerNative(Scientific.cot); registerNative(new StandardSQLFunction("crc32")); registerNative(Scientific.exp); registerNative(Scientific.ln, "log"); registerNative(new StandardSQLFunction("log2")); registerNative(Scientific.log10); registerNative(new NoArgSQLFunction("pi")); registerNative(new NoArgSQLFunction("rand")); registerAlias(Scientific.rand, "rand"); registerNative(Func.substring, "substr"); registerNative(Scientific.radians); registerNative(Scientific.degrees); registerNative(Func.ceil, "ceiling"); registerNative(Func.floor); registerNative(Func.round); registerNative(Func.datediff); registerNative(new StandardSQLFunction("timediff")); registerNative(new StandardSQLFunction("date_format")); registerNative(new StandardSQLFunction("ifnull")); registerAlias(Func.nvl, "ifnull"); registerNative(Func.adddate); registerNative(Func.subdate); registerNative(new StandardSQLFunction("date_add")); registerNative(new StandardSQLFunction("date_sub")); registerNative(Func.current_date, new NoArgSQLFunction("current_date", false), "curdate"); registerNative(Func.current_time, new NoArgSQLFunction("current_time", false), "curtime"); registerNative(Func.current_timestamp, new NoArgSQLFunction("current_timestamp", false)); registerAlias(Func.now, "current_timestamp"); registerAlias("sysdate", "current_timestamp"); registerNative(Func.date); registerNative(new StandardSQLFunction("timestampdiff")); registerNative(new StandardSQLFunction("timestampadd")); registerNative(Func.day, "dayofmonth"); registerNative(new StandardSQLFunction("dayname")); registerNative(new StandardSQLFunction("dayofweek")); registerNative(new StandardSQLFunction("dayofyear")); registerNative(new StandardSQLFunction("from_days")); registerNative(new StandardSQLFunction("from_unixtime")); registerNative(Func.hour); registerNative(new NoArgSQLFunction("localtime")); registerNative(new NoArgSQLFunction("localtimestamp")); registerNative(new StandardSQLFunction("microseconds")); registerNative(Func.minute); registerNative(Func.month); registerNative(new StandardSQLFunction("monthname")); registerNative(new StandardSQLFunction("quarter")); registerNative(Func.second); registerNative(new StandardSQLFunction("sec_to_time"));// 秒数转为time对象 registerNative(Func.time); registerNative(new StandardSQLFunction("timestamp")); registerNative(new StandardSQLFunction("time_to_sec")); registerNative(new StandardSQLFunction("to_days")); registerNative(new StandardSQLFunction("unix_timestamp")); registerNative(new NoArgSQLFunction("utc_date")); registerNative(new NoArgSQLFunction("utc_time")); registerNative(new NoArgSQLFunction("utc_timestamp")); registerNative(new StandardSQLFunction("week"), "weekofyear"); // 返回日期属于当年的第几周 registerNative(new StandardSQLFunction("weekday")); registerNative(Func.year); registerNative(new StandardSQLFunction("yearweek")); registerNative(new StandardSQLFunction("hex")); registerNative(new StandardSQLFunction("oct")); registerNative(new StandardSQLFunction("octet_length")); registerNative(new StandardSQLFunction("bit_length")); registerNative(new StandardSQLFunction("bit_count")); registerNative(new StandardSQLFunction("encrypt")); registerNative(new StandardSQLFunction("md5")); registerNative(new StandardSQLFunction("sha1")); registerNative(new StandardSQLFunction("sha")); registerNative(Func.trim); registerNative(Func.concat); registerNative(Func.replace); registerNative(Func.lpad); registerNative(Func.rpad); registerNative(Func.timestampdiff); registerNative(Func.timestampadd); registerCompatible(Func.add_months, new TemplateFunction("add_months", "timestampadd(MONTH,%2$s,%1$s)")); registerCompatible(Func.decode, new EmuDecodeWithIf()); registerCompatible(Func.translate, new EmuTranslateByReplace()); registerCompatible(Func.str, new TemplateFunction("str", "cast(%s as char)")); typeNames.put(Types.BOOLEAN,"BIT(1)", 0); typeNames.put(Types.BLOB,"mediumblob", 0); typeNames.put(Types.BLOB, 255, "tinyblob", 0); typeNames.put(Types.BLOB, 65535, "blob", 0); typeNames.put(Types.BLOB, 1024 * 1024 * 16, "mediumblob", 0); typeNames.put(Types.BLOB, 1024 * 1024 * 1024 * 4, "longblob", 0); typeNames.put(Types.CLOB, "text", 0); typeNames.put(Types.VARCHAR, 21785, "varchar($l)", 0); typeNames.put(Types.VARCHAR, 65535, "text", Types.CLOB); typeNames.put(Types.VARCHAR, 1024 * 1024 * 16, "mediumtext", Types.CLOB); // MYSQL中的Timestamp含义有些特殊,默认还是用datetime记录 typeNames.put(Types.TIMESTAMP, 1024 * 1024 * 16, "datetime", 0); typeNames.put(Types.TIMESTAMP, "datetime", 0); } @Override public String getCreationComment(ColumnType column, boolean flag) { DateGenerateType generateType = null; if (column instanceof SqlTypeDateTimeGenerated) { generateType = ((SqlTypeDateTimeGenerated) column).getGenerateType(); Object defaultValue = column.defaultValue; /* * 根据用户设置的defaultValue进行修正 */ if (generateType == null && (defaultValue == Func.current_date || defaultValue == Func.current_time || defaultValue == Func.now)) { generateType = DateGenerateType.created; } if(generateType== null && defaultValue!=null){ String dStr = defaultValue.toString().toLowerCase(); if (dStr.startsWith("current") || dStr.startsWith("sys")) { generateType = DateGenerateType.created; } } } if(generateType==DateGenerateType.created){ return "datetime NOT NULL"; }else if(generateType==DateGenerateType.modified){ return "timestamp NOT NULL DEFAULT current_timestamp ON UPDATE current_timestamp"; } return super.getCreationComment(column, flag); } protected String getComment(AutoIncrement column, boolean flag) { StringBuilder sb = new StringBuilder(); //sb.append("INT UNSIGNED"); //2016-4-19日从 int unsigned改为int,因为当一个表主键被另一个表作为外键引用时,双方类型必须完全一样。 //实际测试发现,由于一般建表时普通int字段不会处理为 int unsigned,造成外键创建失败。所以此处暂时为int sb.append("INT "); if (flag) { if (!column.nullable) sb.append(" NOT NULL"); } sb.append(" AUTO_INCREMENT"); return sb.toString(); } @Override public boolean containKeyword(String name) { return keywords.contains(name.toLowerCase()); } /** * MYSQL的时间日期类型有三种,date datetime,timestamp * * 其中 date time都只能设置默认值为常量,不能使用函数。 第一个timestamp则默认会变为not null default * current_timestamp on update current_timestamp */ @Override public String getCatlog(String schema) { return schema; } @Override public String getSchema(String schema) { return null; } /* * * 关于MySQL的Auto_increament访问是比较复杂的 SELECT `AUTO_INCREMENT` FROM * INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'DatabaseName' AND * TABLE_NAME = 'TableName'; * * * $result = mysql_query("SHOW TABLE STATUS LIKE 'table_name'"); $row = * mysql_fetch_array($result); $nextId = $row['Auto_increment']; * mysql_free_result($result); * * * * down vote accepted Use this: * * ALTER TABLE users AUTO_INCREMENT = 1001;or if you haven't already id * column, also add it * * ALTER TABLE users ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT, ADD INDEX * (id); */ public String getDriverClass(String url) { return "com.mysql.jdbc.Driver"; } @Override public String generateUrl(String host, int port, String pathOrName) { StringBuilder sb = new StringBuilder("jdbc:"); // jdbc:mysql://localhost:3306/allandb // ??useUnicode=true&characterEncoding=UTF-8 sb.append("mysql:"); sb.append("//").append(host).append(":").append(port <= 0 ? 3306 : port); sb.append("/").append(pathOrName).append("?useUnicode=true&characterEncoding=UTF-8");// String url = sb.toString(); if (ORMConfig.getInstance().isDebugMode()) { LogUtil.show(url); } return url; } @Override public ColumnType getProprtMetaFromDbType(Column column) { if ("DECIMAL".equals(column.getDataType())) { if (column.getDecimalDigit() > 0) {// 小数 return new ColumnType.Double(column.getColumnSize(), column.getDecimalDigit()); } else {// 整数 if (column.getColumnDef() != null && column.getColumnDef().startsWith("GENERATED")) { return new ColumnType.AutoIncrement(column.getColumnSize()); } else { return new ColumnType.Int(column.getColumnSize()); } } } else { return super.getProprtMetaFromDbType(column); } } public RDBMS getName() { return RDBMS.mysql; } // " jdbc:mysql://localhost:3306/allandb?useUnicode=true&characterEncoding=UTF-8" public void parseDbInfo(ConnectInfo connectInfo) { JefStringReader reader = new JefStringReader(connectInfo.getUrl()); reader.setIgnoreChars(' '); reader.consume("jdbc:mysql:"); reader.omitChars('/'); String host = reader.readToken(':', '/'); reader.omitUntillChar('/');// 忽略端口,直到db开始 reader.omit(1); String dbname = reader.readToken(new char[] { '?', ' ', ';' }); connectInfo.setHost(host); connectInfo.setDbname(dbname); reader.close(); } private final static int[] IO_ERROR_CODE = { 1158, 1159, 1160, 1161, 2001, 2002, 2003, 2004, 2006, 2013, 2024, 2025, 2026 }; @Override public boolean isIOError(SQLException se) { if (se.getSQLState() != null) { // per Mark Matthews at MySQL if (se.getSQLState().startsWith("08")) {// 08s01 网络错误 return true; } } int code = se.getErrorCode(); if (ArrayUtils.contains(IO_ERROR_CODE, code)) { return true; } else if (se.getCause() != null && "NetException".equals(se.getCause().getClass().getSimpleName())) { return true; } else { LogUtil.info("MySQL non-io Err:{}: {}", se.getErrorCode(), se.getMessage()); return false; } } @Override public void processIntervalExpression(BinaryExpression parent, Interval interval) { interval.toMySqlMode(); } @Override public void processIntervalExpression(Function func, Interval interval) { interval.toMySqlMode(); } private final LimitHandler limit=new MySqlLimitHandler(); @Override public LimitHandler getLimitHandler() { return limit; } @Override public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() { return EXTRACTER; } private static ViolatedConstraintNameExtracter EXTRACTER = new ViolatedConstraintNameExtracter(){ @Override public String extractConstraintName(SQLException sqle) { if("MySQLIntegrityConstraintViolationException".equals(sqle.getClass().getSimpleName())){ return sqle.getMessage(); } return null; } }; }