// JOIN ON : 实际可以继续 JOIN ON 更多表SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id; // FROM 多表 :实际可以继续 FROM 多更表SELECT * FROM t_order o, t_order_item i
在看实现代码之前,先一起看下调用顺序图:
看懂上图后,来继续看下实现代码(代码有点多,不要方!):
// AbstractSelectParser.java/*** 解析所有表名和表别名*/public final void parseFrom() { if (sqlParser.skipIfEqual(DefaultKeyword.FROM)) { parseTable(); }}/*** 解析所有表名和表别名*/public void parseTable() { // 解析子查询 if (sqlParser.skipIfEqual(Symbol.LEFT_PAREN)) { if (!selectStatement.getTables().isEmpty()) { throw new UnsupportedOperationException("Cannot support subquery for nested tables."); } selectStatement.setContainStar(false); sqlParser.skipUselessParentheses(); // 去掉子查询左括号 parse(); // 解析子查询 SQL sqlParser.skipUselessParentheses(); // 去掉子查询右括号 // if (!selectStatement.getTables().isEmpty()) { return; } } parseTableFactor(); // 解析当前表 parseJoinTable(); // 解析下一个表}/*** 解析单个表名和表别名*/protected final void parseTableFactor() { int beginPosition = sqlParser.getLexer().getCurrentToken().getEndPosition() - sqlParser.getLexer().getCurrentToken().getLiterals().length(); String literals = sqlParser.getLexer().getCurrentToken().getLiterals(); sqlParser.getLexer().nextToken(); // TODO 包含Schema解析 if (sqlParser.skipIfEqual(Symbol.DOT)) { // https://dev.mysql.com/doc/refman/5.7/en/information-schema.html :SELECT table_name, table_type, engine FROM information_schema.tables sqlParser.getLexer().nextToken(); sqlParser.parseAlias(); return; } // FIXME 根据shardingRule过滤table selectStatement.getSqlTokens().add(new TableToken(beginPosition, literals)); // 表 以及 表别名 selectStatement.getTables().add(new Table(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));}/*** 解析 Join Table 或者 FROM 下一张 Table*/protected void parseJoinTable() { if (sqlParser.skipJoin()) { // 这里调用 parseJoinTable() 而不是 parseTableFactor() :下一个 Table 可能是子查询 // 例如:SELECT * FROM t_order JOIN (SELECT * FROM t_order_item JOIN t_order_other ON ) ..... parseTable(); if (sqlParser.skipIfEqual(DefaultKeyword.ON)) { // JOIN 表时 ON 条件 do { parseTableCondition(sqlParser.getLexer().getCurrentToken().getEndPosition()); sqlParser.accept(Symbol.EQ); parseTableCondition(sqlParser.getLexer().getCurrentToken().getEndPosition() - sqlParser.getLexer().getCurrentToken().getLiterals().length()); } while (sqlParser.skipIfEqual(DefaultKeyword.AND)); } else if (sqlParser.skipIfEqual(DefaultKeyword.USING)) { // JOIN 表时 USING 为使用两表相同字段相同时对 ON 的简化。例如以下两条 SQL 等价: // SELECT * FROM t_order o JOIN t_order_item i USING (order_id); // SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id sqlParser.skipParentheses(); } parseJoinTable(); // 继续递归 }}/*** 解析 ON 条件里的 TableToken** @param startPosition 开始位置*/private void parseTableCondition(final int startPosition) { SQLExpression sqlExpression = sqlParser.parseExpression(); if (!(sqlExpression instanceof SQLPropertyExpression)) { return; } SQLPropertyExpression sqlPropertyExpression = (SQLPropertyExpression) sqlExpression; if (selectStatement.getTables().getTableNames().contains(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner().getName()))) { selectStatement.getSqlTokens().add(new TableToken(startPosition, sqlPropertyExpression.getOwner().getName())); }}
SELECT o3.* FROM (SELECT * FROM (SELECT * FROM t_order o) o2) o3;SELECT o3.* FROM (SELECT * FROM (SELECT * FROM t_order o) o2) o3 JOIN t_order_item i ON o3.order_id = i.order_id;
不支持第二个开始包含多层级的数据子查询。例如:
SELECT o3.* FROM t_order_item i JOIN (SELECT * FROM (SELECT * FROM t_order o) o2) o3 ON o3.order_id = i.order_id; // 此条 SQL 是上面第二条 SQL 左右量表颠倒SELECT COUNT(*) FROM (SELECT * FROM t_order o WHERE o.id IN (SELECT id FROM t_order WHERE status = ?)) // FROM 官方不支持 SQL 举例
使用第二个开始的子查询会抛出异常,代码如下:
// AbstractSelectParser.java#parseTable()片段if (!selectStatement.getTables().isEmpty()) { throw new UnsupportedOperationException("Cannot support subquery for nested tables.");}
使用子查询,建议认真阅读官方《分页及子查询》文档。
3.4.3 #parseJoinTable()
MySQLSelectParser 重写了 #parseJoinTable() 方法用于解析 USE / IGNORE / FORCE index_hint。具体语法见上文 JOIN Syntax。这里就跳过,有兴趣的同学可以去看看。
3.4.4 Tables 表集合对象
属于分片上下文信息
// Tables.javapublic final class Tables { private final List
tables = new ArrayList<>();}// Table.javapublic final class Table { /** * 表 */ private final String name; /** * 别名 */ private final Optional alias;}// AbstractSelectParser.java#parseTableFactor()片段selectStatement.getTables().add(new Table(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
3.5 #parseWhere()
解析 WHERE 条件。解析代码:《SQL 解析(二)之SQL解析》的#parseWhere()小节。
3.6 #parseGroupBy()
解析分组条件,实现上比较类似 #parseSelectList,会更加简单一些。
// AbstractSelectParser.java/*** 解析 Group By 和 Having(暂时不支持)*/protected void parseGroupBy() { if (sqlParser.skipIfEqual(DefaultKeyword.GROUP)) { sqlParser.accept(DefaultKeyword.BY); // 解析 Group By 每个字段 while (true) { addGroupByItem(sqlParser.parseExpression(selectStatement)); if (!sqlParser.equalAny(Symbol.COMMA)) { break; } sqlParser.getLexer().nextToken(); } while (sqlParser.equalAny(DefaultKeyword.WITH) || sqlParser.getLexer().getCurrentToken().getLiterals().equalsIgnoreCase("ROLLUP")) { sqlParser.getLexer().nextToken(); } // Having(暂时不支持) if (sqlParser.skipIfEqual(DefaultKeyword.HAVING)) { throw new UnsupportedOperationException("Cannot support Having"); } selectStatement.setGroupByLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition()); } else if (sqlParser.skipIfEqual(DefaultKeyword.HAVING)) { throw new UnsupportedOperationException("Cannot support Having"); }}/*** 解析 Group By 单个字段* Group By 条件是带有排序功能,默认ASC** @param sqlExpression 表达式*/protected final void addGroupByItem(final SQLExpression sqlExpression) { // Group By 字段 DESC / ASC / ;默认是 ASC。 OrderType orderByType = OrderType.ASC; if (sqlParser.equalAny(DefaultKeyword.ASC)) { sqlParser.getLexer().nextToken(); } else if (sqlParser.skipIfEqual(DefaultKeyword.DESC)) { orderByType = OrderType.DESC; } // 解析 OrderItem OrderItem orderItem; if (sqlExpression instanceof SQLPropertyExpression) { SQLPropertyExpression sqlPropertyExpression = (SQLPropertyExpression) sqlExpression; orderItem = new OrderItem(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner().getName()), SQLUtil.getExactlyValue(sqlPropertyExpression.getName()), orderByType, getAlias(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner() + "." + SQLUtil.getExactlyValue(sqlPropertyExpression.getName())))); } else if (sqlExpression instanceof SQLIdentifierExpression) { SQLIdentifierExpression sqlIdentifierExpression = (SQLIdentifierExpression) sqlExpression; orderItem = new OrderItem(SQLUtil.getExactlyValue(sqlIdentifierExpression.getName()), orderByType, getAlias(SQLUtil.getExactlyValue(sqlIdentifierExpression.getName()))); } else { return; } selectStatement.getGroupByItems().add(orderItem);}/*** 字段在查询项里的别名** @param name 字段* @return 别名*/private Optional getAlias(final String name) { if (selectStatement.isContainStar()) { return Optional.absent(); } String rawName = SQLUtil.getExactlyValue(name); for (SelectItem each : selectStatement.getItems()) { if (rawName.equalsIgnoreCase(SQLUtil.getExactlyValue(each.getExpression()))) { return each.getAlias(); } if (rawName.equalsIgnoreCase(each.getAlias().orNull())) { return Optional.of(rawName); } } return Optional.absent();}
3.6.1 OrderItem 排序项
属于分片上下文信息
public final class OrderItem { /** * 所属表别名 */ private final Optional owner; /** * 排序字段 */ private final Optional name; /** * 排序类型 */ private final OrderType type; /** * 按照第几个查询字段排序 * ORDER BY 数字 的 数字代表的是第几个字段 */ @Setter private int index = -1; /** * 字段在查询项({@link com.dangdang.ddframe.rdb.sharding.parsing.parser.context.selectitem.SelectItem} 的别名 */ @Setter private Optional alias;}
// Limit.javapublic final class Limit { /** * 是否重写rowCount * TODO 待补充:预计和内存分页合并有关 */ private final boolean rowCountRewriteFlag; /** * offset */ private LimitValue offset; /** * row */ private LimitValue rowCount;}// LimitValue.javapublic final class LimitValue { /** * 值 * 当 value == -1 时,为占位符 */ private int value; /** * 第几个占位符 */ private int index;}
3.8.2 OffsetToken RowCountToken
OffsetToken:分页偏移量标记对象
RowCountToken:分页长度标记对象
只有在对应位置非占位符才有该 SQLToken。
// OffsetToken.javapublic final class OffsetToken implements SQLToken { /** * SQL 所在开始位置 */ private final int beginPosition; /** * 偏移值 */ private final int offset;}// RowCountToken.javapublic final class RowCountToken implements SQLToken { /** * SQL 所在开始位置 */ private final int beginPosition; /** * 行数 */ private final int rowCount;}
3.9 #queryRest()
// AbstractSelectParser.javaprotected void queryRest() { if (sqlParser.equalAny(DefaultKeyword.UNION, DefaultKeyword.EXCEPT, DefaultKeyword.INTERSECT, DefaultKeyword.MINUS)) { throw new SQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType()); }}
不支持 UNION / EXCEPT / INTERSECT / MINUS ,调用会抛出异常。
4. appendDerived等方法
因为 Sharding-JDBC 对表做了分片,在 AVG , GROUP BY , ORDER BY 需要对 SQL 进行一些改写,以达到能在内存里对结果做进一步处理,例如求平均值、分组、排序等。
// AbstractSelectParser.java/*** 针对 GROUP BY 或 ORDER BY 字段,增加推导字段* 如果该字段不在查询字段里,需要额外查询该字段,这样才能在内存里 GROUP BY 或 ORDER BY** @param itemsToken 选择项标记对象* @param orderItems 排序字段* @param aliasPattern 别名模式*/private void appendDerivedOrderColumns(final ItemsToken itemsToken, final List orderItems, final String aliasPattern) { int derivedColumnOffset = 0; for (OrderItem each : orderItems) { if (!isContainsItem(each)) { String alias = String.format(aliasPattern, derivedColumnOffset++); each.setAlias(Optional.of(alias)); itemsToken.getItems().add(each.getQualifiedName().get() + " AS " + alias + " "); } }}/*** 查询字段是否包含排序字段** @param orderItem 排序字段* @return 是否*/private boolean isContainsItem(final OrderItem orderItem) { if (selectStatement.isContainStar()) { // SELECT * return true; } for (SelectItem each : selectStatement.getItems()) { if (-1 != orderItem.getIndex()) { // ORDER BY 使用数字 return true; } if (each.getAlias().isPresent() && orderItem.getAlias().isPresent() && each.getAlias().get().equalsIgnoreCase(orderItem.getAlias().get())) { // 字段别名比较 return true; } if (!each.getAlias().isPresent() && orderItem.getQualifiedName().isPresent() && each.getExpression().equalsIgnoreCase(orderItem.getQualifiedName().get())) { // 字段原名比较 return true; } } return false;}
4.3 ItemsToken
选择项标记对象,属于分片上下文信息,目前有 3 个情况会创建:
AVG 查询额外 COUNT 和 SUM: #appendAvgDerivedColumns()
GROUP BY 不在 查询字段,额外查询该字段 : #appendDerivedOrderColumns()
ORDER BY 不在 查询字段,额外查询该字段 : #appendDerivedOrderColumns()
public final class ItemsToken implements SQLToken { /** * SQL 开始位置 */ private final int beginPosition; /** * 字段名数组 */ private final List items = new LinkedList<>();}
4.4 appendDerivedOrderBy()
当 SQL 有聚合条件而无排序条件,根据聚合条件进行排序。这是数据库自己的执行规则。
mysql> SELECT order_id FROM t_order GROUP BY order_id;+----------+| order_id |+----------+| 1 || 2 || 3 |+----------+3 rows in set (0.05 sec)mysql> SELECT order_id FROM t_order GROUP BY order_id DESC;+----------+| order_id |+----------+| 3 || 2 || 1 |+----------+3 rows in set (0.02 sec)
// AbstractSelectParser.java/*** 当无 Order By 条件时,使用 Group By 作为排序条件(数据库本身规则)*/private void appendDerivedOrderBy() { if (!getSelectStatement().getGroupByItems().isEmpty() && getSelectStatement().getOrderByItems().isEmpty()) { getSelectStatement().getOrderByItems().addAll(getSelectStatement().getGroupByItems()); getSelectStatement().getSqlTokens().add(new OrderByToken(getSelectStatement().getGroupByLastPosition())); }}
4.3.1 OrderByToken
排序标记对象。当无 Order By 条件时,使用 Group By 作为排序条件(数据库本身规则)。
// OrderByToken.javapublic final class OrderByToken implements SQLToken { /** * SQL 所在开始位置 */ private final int beginPosition;}
昨晚和朋友聊天,喝了点咖啡,由于我经常喝茶,很长时间没喝咖啡了,所以失眠了,于是起床读JVM规范,读完后在朋友圈发了一条信息:
JVM Run-Time Data Areas:The Java Virtual Machine defines various run-time data areas that are used during execution of a program. So
Spark SQL supports most commonly used features of HiveQL. However, different HiveQL statements are executed in different manners:
1. DDL statements (e.g. CREATE TABLE, DROP TABLE, etc.)
nginx在运行过程中是否稳定,是否有异常退出过?这里总结几项平时会用到的小技巧。
1. 在error.log中查看是否有signal项,如果有,看看signal是多少。
比如,这是一个异常退出的情况:
$grep signal error.log
2012/12/24 16:39:56 [alert] 13661#0: worker process 13666 exited on s
方法一:常用方法 关闭XML验证
工具栏:windows => preferences => xml => xml files => validation => Indicate when no grammar is specified:选择Ignore即可。
方法二:(个人推荐)
添加 内容如下
<?xml version=
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml&quo
最主要的是使用到了一个jquery的插件jquery.media.js,使用这个插件就很容易实现了。
核心代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.