package com.alibaba.druid.sql;

import com.alibaba.druid.DbType;
import com.alibaba.druid.FastsqlException;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLOver;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLAggregateOption;
import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLLiteralExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumberExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.dialect.hive.ast.stmt.HiveSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectQueryBlock;
import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerTop;
import java.util.List;

public class PagerUtils {
   public static String count(String sql, DbType dbType) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      if (stmtList.size() != 1) {
         throw new IllegalArgumentException("sql not support count : " + sql);
      } else {
         SQLStatement stmt = (SQLStatement)stmtList.get(0);
         if (!(stmt instanceof SQLSelectStatement)) {
            throw new IllegalArgumentException("sql not support count : " + sql);
         } else {
            SQLSelectStatement selectStmt = (SQLSelectStatement)stmt;
            return count(selectStmt.getSelect(), dbType);
         }
      }
   }

   public static String limit(String sql, DbType dbType, int offset, int count) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      if (stmtList.size() != 1) {
         throw new IllegalArgumentException("sql not support count : " + sql);
      } else {
         SQLStatement stmt = (SQLStatement)stmtList.get(0);
         if (!(stmt instanceof SQLSelectStatement)) {
            throw new IllegalArgumentException("sql not support count : " + sql);
         } else {
            SQLSelectStatement selectStmt = (SQLSelectStatement)stmt;
            return limit(selectStmt.getSelect(), dbType, offset, count);
         }
      }
   }

   public static String limit(String sql, DbType dbType, int offset, int count, boolean check) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      if (stmtList.size() != 1) {
         throw new IllegalArgumentException("sql not support count : " + sql);
      } else {
         SQLStatement stmt = (SQLStatement)stmtList.get(0);
         if (!(stmt instanceof SQLSelectStatement)) {
            throw new IllegalArgumentException("sql not support count : " + sql);
         } else {
            SQLSelectStatement selectStmt = (SQLSelectStatement)stmt;
            limit(selectStmt.getSelect(), dbType, offset, count, check);
            return selectStmt.toString();
         }
      }
   }

   public static String limit(SQLSelect select, DbType dbType, int offset, int count) {
      limit(select, dbType, offset, count, false);
      return SQLUtils.toSQLString(select, (DbType)dbType);
   }

   public static boolean limit(SQLSelect select, DbType dbType, int offset, int count, boolean check) {
      SQLSelectQuery query = select.getQuery();
      switch (dbType) {
         case oracle:
            return limitOracle(select, dbType, offset, count, check);
         case db2:
            return limitDB2(select, dbType, offset, count, check);
         case sqlserver:
         case jtds:
            return limitSQLServer(select, dbType, offset, count, check);
         default:
            if (query instanceof SQLSelectQueryBlock) {
               return limitQueryBlock(select, dbType, offset, count, check);
            } else if (query instanceof SQLUnionQuery) {
               return limitUnion((SQLUnionQuery)query, dbType, offset, count, check);
            } else {
               throw new UnsupportedOperationException();
            }
      }
   }

   private static boolean limitUnion(SQLUnionQuery queryBlock, DbType dbType, int offset, int count, boolean check) {
      SQLLimit limit = queryBlock.getLimit();
      if (limit != null) {
         if (offset > 0) {
            limit.setOffset(new SQLIntegerExpr(offset));
         }

         if (check && limit.getRowCount() instanceof SQLNumericLiteralExpr) {
            int rowCount = ((SQLNumericLiteralExpr)limit.getRowCount()).getNumber().intValue();
            if (rowCount <= count && offset <= 0) {
               return false;
            }
         } else if (check && limit.getRowCount() instanceof SQLVariantRefExpr) {
            return false;
         }

         limit.setRowCount(new SQLIntegerExpr(count));
      }

      if (limit == null) {
         limit = new SQLLimit();
         if (offset > 0) {
            limit.setOffset(new SQLIntegerExpr(offset));
         }

         limit.setRowCount(new SQLIntegerExpr(count));
         queryBlock.setLimit(limit);
      }

      return true;
   }

   private static boolean limitQueryBlock(SQLSelect select, DbType dbType, int offset, int count, boolean check) {
      SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)select.getQuery();
      if (dbType == null) {
         dbType = DbType.other;
      }

      switch (dbType) {
         case oracle:
         case oceanbase_oracle:
            return limitOracle(select, dbType, offset, count, check);
         case db2:
         case sqlserver:
         case jtds:
         default:
            throw new UnsupportedOperationException();
         case mysql:
         case mariadb:
         case h2:
         case ads:
            return limitMySqlQueryBlock(queryBlock, dbType, offset, count, check);
         case postgresql:
         case hive:
         case odps:
            return limitSQLQueryBlock(queryBlock, dbType, offset, count, check);
      }
   }

   private static boolean limitSQLQueryBlock(SQLSelectQueryBlock queryBlock, DbType dbType, int offset, int count, boolean check) {
      SQLLimit limit = queryBlock.getLimit();
      if (limit != null) {
         if (offset > 0) {
            limit.setOffset(new SQLIntegerExpr(offset));
         }

         if (check && limit.getRowCount() instanceof SQLNumericLiteralExpr) {
            int rowCount = ((SQLNumericLiteralExpr)limit.getRowCount()).getNumber().intValue();
            if (rowCount <= count && offset <= 0) {
               return false;
            }
         }

         limit.setRowCount(new SQLIntegerExpr(count));
      }

      limit = new SQLLimit();
      if (offset > 0) {
         limit.setOffset(new SQLIntegerExpr(offset));
      }

      limit.setRowCount(new SQLIntegerExpr(count));
      queryBlock.setLimit(limit);
      return true;
   }

   private static boolean limitDB2(SQLSelect select, DbType dbType, int offset, int count, boolean check) {
      SQLSelectQuery query = select.getQuery();
      SQLBinaryOpExpr gt = new SQLBinaryOpExpr(new SQLIdentifierExpr("ROWNUM"), SQLBinaryOperator.GreaterThan, new SQLNumberExpr(offset), DbType.db2);
      SQLBinaryOpExpr lteq = new SQLBinaryOpExpr(new SQLIdentifierExpr("ROWNUM"), SQLBinaryOperator.LessThanOrEqual, new SQLNumberExpr(count + offset), DbType.db2);
      new SQLBinaryOpExpr(gt, SQLBinaryOperator.BooleanAnd, lteq, DbType.db2);
      if (!(query instanceof SQLSelectQueryBlock)) {
         SQLAggregateExpr aggregateExpr = new SQLAggregateExpr("ROW_NUMBER");
         SQLOrderBy orderBy = select.getOrderBy();
         aggregateExpr.setOver(new SQLOver(orderBy));
         select.setOrderBy((SQLOrderBy)null);
         return offset <= 0 ? true : true;
      } else {
         SQLAggregateExpr aggregateExpr = new SQLAggregateExpr("ROW_NUMBER");
         SQLOrderBy orderBy = select.getOrderBy();
         if (orderBy == null && select.getQuery() instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock selectQueryBlcok = (SQLSelectQueryBlock)select.getQuery();
            orderBy = selectQueryBlcok.getOrderBy();
            selectQueryBlcok.setOrderBy((SQLOrderBy)null);
         } else {
            select.setOrderBy((SQLOrderBy)null);
         }

         aggregateExpr.setOver(new SQLOver(orderBy));
         return true;
      }
   }

   private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offset, int count, boolean check) {
      SQLSelectQuery query = select.getQuery();
      SQLBinaryOpExpr gt = new SQLBinaryOpExpr(new SQLIdentifierExpr("ROWNUM"), SQLBinaryOperator.GreaterThan, new SQLNumberExpr(offset), DbType.sqlserver);
      SQLBinaryOpExpr lteq = new SQLBinaryOpExpr(new SQLIdentifierExpr("ROWNUM"), SQLBinaryOperator.LessThanOrEqual, new SQLNumberExpr(count + offset), DbType.sqlserver);
      SQLBinaryOpExpr pageCondition = new SQLBinaryOpExpr(gt, SQLBinaryOperator.BooleanAnd, lteq, DbType.sqlserver);
      if (query instanceof SQLSelectQueryBlock) {
         SQLServerSelectQueryBlock queryBlock = (SQLServerSelectQueryBlock)query;
         if (offset <= 0) {
            SQLServerTop top = queryBlock.getTop();
            if (check && top != null && !top.isPercent() && top.getExpr() instanceof SQLNumericLiteralExpr) {
               int rowCount = ((SQLNumericLiteralExpr)top.getExpr()).getNumber().intValue();
               if (rowCount <= count) {
                  return false;
               }
            }

            queryBlock.setTop(new SQLServerTop(new SQLNumberExpr(count)));
            return true;
         } else {
            SQLAggregateExpr aggregateExpr = new SQLAggregateExpr("ROW_NUMBER");
            SQLOrderBy orderBy = select.getOrderBy();
            if (orderBy != null) {
               aggregateExpr.setOver(new SQLOver(orderBy));
               select.setOrderBy((SQLOrderBy)null);
            } else if (queryBlock.getOrderBy() != null) {
               aggregateExpr.setOver(new SQLOver(queryBlock.getOrderBy()));
               queryBlock.setOrderBy((SQLOrderBy)null);
            }

            queryBlock.getSelectList().add(new SQLSelectItem(aggregateExpr, "ROWNUM"));
            SQLServerSelectQueryBlock countQueryBlock = new SQLServerSelectQueryBlock();
            countQueryBlock.getSelectList().add(new SQLSelectItem(new SQLAllColumnExpr()));
            countQueryBlock.setFrom(new SQLSubqueryTableSource(select.clone(), "XX"));
            countQueryBlock.setWhere(pageCondition);
            select.setQuery(countQueryBlock);
            return true;
         }
      } else {
         SQLServerSelectQueryBlock countQueryBlock = new SQLServerSelectQueryBlock();
         countQueryBlock.getSelectList().add(new SQLSelectItem(new SQLPropertyExpr(new SQLIdentifierExpr("XX"), "*")));
         countQueryBlock.setFrom(new SQLSubqueryTableSource(select.clone(), "XX"));
         if (offset <= 0) {
            return true;
         } else {
            SQLAggregateExpr aggregateExpr = new SQLAggregateExpr("ROW_NUMBER");
            SQLOrderBy orderBy = select.getOrderBy();
            aggregateExpr.setOver(new SQLOver(orderBy));
            select.setOrderBy((SQLOrderBy)null);
            countQueryBlock.getSelectList().add(new SQLSelectItem(aggregateExpr, "ROWNUM"));
            SQLServerSelectQueryBlock offsetQueryBlock = new SQLServerSelectQueryBlock();
            offsetQueryBlock.getSelectList().add(new SQLSelectItem(new SQLAllColumnExpr()));
            offsetQueryBlock.setFrom(new SQLSubqueryTableSource(new SQLSelect(countQueryBlock), "XXX"));
            offsetQueryBlock.setWhere(pageCondition);
            select.setQuery(offsetQueryBlock);
            return true;
         }
      }
   }

   private static boolean limitOracle(SQLSelect select, DbType dbType, int offset, int count, boolean check) {
      SQLSelectQuery query = select.getQuery();
      if (query instanceof SQLSelectQueryBlock) {
         OracleSelectQueryBlock queryBlock = (OracleSelectQueryBlock)query;
         SQLOrderBy orderBy = select.getOrderBy();
         if (orderBy == null && queryBlock.getOrderBy() != null) {
            orderBy = queryBlock.getOrderBy();
         }

         if (queryBlock.getGroupBy() == null && orderBy == null && offset <= 0) {
            SQLExpr where = queryBlock.getWhere();
            if (check && where instanceof SQLBinaryOpExpr) {
               SQLBinaryOpExpr binaryOpWhere = (SQLBinaryOpExpr)where;
               if (binaryOpWhere.getOperator() == SQLBinaryOperator.LessThanOrEqual) {
                  SQLExpr left = binaryOpWhere.getLeft();
                  SQLExpr right = binaryOpWhere.getRight();
                  if (left instanceof SQLIdentifierExpr && ((SQLIdentifierExpr)left).getName().equalsIgnoreCase("ROWNUM") && right instanceof SQLNumericLiteralExpr) {
                     int rowCount = ((SQLNumericLiteralExpr)right).getNumber().intValue();
                     if (rowCount <= count) {
                        return false;
                     }
                  }
               }
            }

            SQLExpr condition = new SQLBinaryOpExpr(new SQLIdentifierExpr("ROWNUM"), SQLBinaryOperator.LessThanOrEqual, new SQLNumberExpr(count), DbType.oracle);
            if (queryBlock.getWhere() == null) {
               queryBlock.setWhere(condition);
            } else {
               queryBlock.setWhere(new SQLBinaryOpExpr(queryBlock.getWhere(), SQLBinaryOperator.BooleanAnd, condition, DbType.oracle));
            }

            return true;
         }
      }

      OracleSelectQueryBlock countQueryBlock = new OracleSelectQueryBlock();
      countQueryBlock.getSelectList().add(new SQLSelectItem(new SQLPropertyExpr(new SQLIdentifierExpr("XX"), "*")));
      countQueryBlock.getSelectList().add(new SQLSelectItem(new SQLIdentifierExpr("ROWNUM"), "RN"));
      countQueryBlock.setFrom(new SQLSubqueryTableSource(select.clone(), "XX"));
      countQueryBlock.setWhere(new SQLBinaryOpExpr(new SQLIdentifierExpr("ROWNUM"), SQLBinaryOperator.LessThanOrEqual, new SQLNumberExpr(count + offset), DbType.oracle));
      select.setOrderBy((SQLOrderBy)null);
      if (offset <= 0) {
         select.setQuery(countQueryBlock);
         return true;
      } else {
         OracleSelectQueryBlock offsetQueryBlock = new OracleSelectQueryBlock();
         offsetQueryBlock.getSelectList().add(new SQLSelectItem(new SQLAllColumnExpr()));
         offsetQueryBlock.setFrom(new SQLSubqueryTableSource(new SQLSelect(countQueryBlock), "XXX"));
         offsetQueryBlock.setWhere(new SQLBinaryOpExpr(new SQLIdentifierExpr("RN"), SQLBinaryOperator.GreaterThan, new SQLNumberExpr(offset), DbType.oracle));
         select.setQuery(offsetQueryBlock);
         return true;
      }
   }

   private static boolean limitMySqlQueryBlock(SQLSelectQueryBlock queryBlock, DbType dbType, int offset, int count, boolean check) {
      SQLLimit limit = queryBlock.getLimit();
      if (limit != null) {
         if (offset > 0) {
            limit.setOffset(new SQLIntegerExpr(offset));
         }

         if (check && limit.getRowCount() instanceof SQLNumericLiteralExpr) {
            int rowCount = ((SQLNumericLiteralExpr)limit.getRowCount()).getNumber().intValue();
            if (rowCount <= count && offset <= 0) {
               return false;
            }
         } else if (check && limit.getRowCount() instanceof SQLVariantRefExpr) {
            return false;
         }

         limit.setRowCount(new SQLIntegerExpr(count));
      }

      if (limit == null) {
         limit = new SQLLimit();
         if (offset > 0) {
            limit.setOffset(new SQLIntegerExpr(offset));
         }

         limit.setRowCount(new SQLIntegerExpr(count));
         queryBlock.setLimit(limit);
      }

      return true;
   }

   private static String count(SQLSelect select, DbType dbType) {
      if (select.getOrderBy() != null) {
         select.setOrderBy((SQLOrderBy)null);
      }

      SQLSelectQuery query = select.getQuery();
      clearOrderBy(query);
      if (query instanceof SQLSelectQueryBlock) {
         SQLSelectItem countItem = createCountItem(dbType);
         SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)query;
         List<SQLSelectItem> selectList = queryBlock.getSelectList();
         if (queryBlock.getGroupBy() != null && queryBlock.getGroupBy().getItems().size() > 0) {
            if (queryBlock.getSelectList().size() == 1 && ((SQLSelectItem)queryBlock.getSelectList().get(0)).getExpr() instanceof SQLAllColumnExpr) {
               queryBlock.getSelectList().clear();
               queryBlock.getSelectList().add(new SQLSelectItem(new SQLIntegerExpr(1)));
            }

            return createCountUseSubQuery(select, dbType);
         } else {
            int option = queryBlock.getDistionOption();
            if (option == 2 && selectList.size() >= 1) {
               SQLAggregateExpr countExpr = new SQLAggregateExpr("COUNT", SQLAggregateOption.DISTINCT);

               for(int i = 0; i < selectList.size(); ++i) {
                  countExpr.addArgument(((SQLSelectItem)selectList.get(i)).getExpr());
               }

               selectList.clear();
               queryBlock.setDistionOption(0);
               queryBlock.addSelectItem((SQLExpr)countExpr);
            } else {
               selectList.clear();
               selectList.add(countItem);
            }

            return SQLUtils.toSQLString(select, (DbType)dbType);
         }
      } else if (query instanceof SQLUnionQuery) {
         return createCountUseSubQuery(select, dbType);
      } else {
         throw new IllegalStateException();
      }
   }

   private static String createCountUseSubQuery(SQLSelect select, DbType dbType) {
      SQLSelectQueryBlock countSelectQuery = createQueryBlock(dbType);
      SQLSelectItem countItem = createCountItem(dbType);
      countSelectQuery.getSelectList().add(countItem);
      SQLSubqueryTableSource fromSubquery = new SQLSubqueryTableSource(select);
      fromSubquery.setAlias("ALIAS_COUNT");
      countSelectQuery.setFrom((SQLTableSource)fromSubquery);
      SQLSelect countSelect = new SQLSelect(countSelectQuery);
      SQLSelectStatement countStmt = new SQLSelectStatement(countSelect, dbType);
      return SQLUtils.toSQLString(countStmt, (DbType)dbType);
   }

   private static SQLSelectQueryBlock createQueryBlock(DbType dbType) {
      if (dbType == null) {
         dbType = DbType.other;
      }

      switch (dbType) {
         case oracle:
            return new OracleSelectQueryBlock();
         case db2:
         case h2:
         case postgresql:
         default:
            return new SQLSelectQueryBlock(dbType);
         case sqlserver:
         case jtds:
            return new SQLServerSelectQueryBlock();
         case mysql:
         case mariadb:
         case ads:
            return new MySqlSelectQueryBlock();
         case hive:
            return new HiveSelectQueryBlock();
      }
   }

   private static SQLSelectItem createCountItem(DbType dbType) {
      SQLAggregateExpr countExpr = new SQLAggregateExpr("COUNT");
      countExpr.addArgument(new SQLAllColumnExpr());
      SQLSelectItem countItem = new SQLSelectItem(countExpr);
      return countItem;
   }

   private static void clearOrderBy(SQLSelectQuery query) {
      if (query instanceof SQLSelectQueryBlock) {
         SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)query;
         if (queryBlock.getOrderBy() != null) {
            queryBlock.setOrderBy((SQLOrderBy)null);
         }

      } else {
         if (query instanceof SQLUnionQuery) {
            SQLUnionQuery union = (SQLUnionQuery)query;
            if (union.getOrderBy() != null) {
               union.setOrderBy((SQLOrderBy)null);
            }

            clearOrderBy(union.getLeft());
            clearOrderBy(union.getRight());
         }

      }
   }

   public static int getLimit(String sql, DbType dbType) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      if (stmtList.size() != 1) {
         return -1;
      } else {
         SQLStatement stmt = (SQLStatement)stmtList.get(0);
         if (stmt instanceof SQLSelectStatement) {
            SQLSelectStatement selectStmt = (SQLSelectStatement)stmt;
            SQLSelectQuery query = selectStmt.getSelect().getQuery();
            if (query instanceof SQLSelectQueryBlock) {
               if (query instanceof MySqlSelectQueryBlock) {
                  SQLLimit limit = ((MySqlSelectQueryBlock)query).getLimit();
                  if (limit == null) {
                     return -1;
                  }

                  SQLExpr rowCountExpr = limit.getRowCount();
                  if (rowCountExpr instanceof SQLNumericLiteralExpr) {
                     int rowCount = ((SQLNumericLiteralExpr)rowCountExpr).getNumber().intValue();
                     return rowCount;
                  }

                  return Integer.MAX_VALUE;
               }

               return -1;
            }
         }

         return -1;
      }
   }

   public static boolean hasUnorderedLimit(String sql, DbType dbType) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      if (DbType.mysql == dbType) {
         MySqlUnorderedLimitDetectVisitor visitor = new MySqlUnorderedLimitDetectVisitor();

         for(SQLStatement stmt : stmtList) {
            stmt.accept(visitor);
         }

         return visitor.unorderedLimitCount > 0;
      } else if (DbType.oracle != dbType) {
         throw new FastsqlException("not supported. dbType : " + dbType);
      } else {
         OracleUnorderedLimitDetectVisitor visitor = new OracleUnorderedLimitDetectVisitor();

         for(SQLStatement stmt : stmtList) {
            stmt.accept(visitor);
         }

         return visitor.unorderedLimitCount > 0;
      }
   }

   private static class MySqlUnorderedLimitDetectVisitor extends MySqlASTVisitorAdapter {
      public int unorderedLimitCount;

      private MySqlUnorderedLimitDetectVisitor() {
      }

      public boolean visit(MySqlSelectQueryBlock x) {
         SQLOrderBy orderBy = x.getOrderBy();
         SQLLimit limit = x.getLimit();
         if (limit != null && (orderBy == null || orderBy.getItems().size() == 0)) {
            boolean subQueryHasOrderBy = false;
            SQLTableSource from = x.getFrom();
            if (from instanceof SQLSubqueryTableSource) {
               SQLSubqueryTableSource subqueryTabSrc = (SQLSubqueryTableSource)from;
               SQLSelect select = subqueryTabSrc.getSelect();
               if (select.getQuery() instanceof SQLSelectQueryBlock) {
                  SQLSelectQueryBlock subquery = (SQLSelectQueryBlock)select.getQuery();
                  if (subquery.getOrderBy() != null && subquery.getOrderBy().getItems().size() > 0) {
                     subQueryHasOrderBy = true;
                  }
               }
            }

            if (!subQueryHasOrderBy) {
               ++this.unorderedLimitCount;
            }
         }

         return true;
      }
   }

   private static class OracleUnorderedLimitDetectVisitor extends OracleASTVisitorAdapter {
      public int unorderedLimitCount;

      private OracleUnorderedLimitDetectVisitor() {
      }

      public boolean visit(SQLBinaryOpExpr x) {
         SQLExpr left = x.getLeft();
         SQLExpr right = x.getRight();
         boolean rownum = false;
         if (left instanceof SQLIdentifierExpr && ((SQLIdentifierExpr)left).getName().equalsIgnoreCase("ROWNUM") && right instanceof SQLLiteralExpr) {
            rownum = true;
         } else if (right instanceof SQLIdentifierExpr && ((SQLIdentifierExpr)right).getName().equalsIgnoreCase("ROWNUM") && left instanceof SQLLiteralExpr) {
            rownum = true;
         }

         OracleSelectQueryBlock selectQuery = null;
         if (rownum) {
            for(SQLObject parent = x.getParent(); parent != null; parent = parent.getParent()) {
               if (parent instanceof SQLSelectQuery) {
                  if (parent instanceof OracleSelectQueryBlock) {
                     OracleSelectQueryBlock queryBlock = (OracleSelectQueryBlock)parent;
                     SQLTableSource from = queryBlock.getFrom();
                     if (from instanceof SQLExprTableSource) {
                        selectQuery = queryBlock;
                     } else if (from instanceof SQLSubqueryTableSource) {
                        SQLSelect subSelect = ((SQLSubqueryTableSource)from).getSelect();
                        if (subSelect.getQuery() instanceof OracleSelectQueryBlock) {
                           selectQuery = (OracleSelectQueryBlock)subSelect.getQuery();
                        }
                     }
                  }
                  break;
               }
            }
         }

         if (selectQuery != null) {
            SQLOrderBy orderBy = selectQuery.getOrderBy();
            SQLObject parent = selectQuery.getParent();
            if (orderBy == null && parent instanceof SQLSelect) {
               SQLSelect select = (SQLSelect)parent;
               orderBy = select.getOrderBy();
            }

            if (orderBy == null || orderBy.getItems().size() == 0) {
               ++this.unorderedLimitCount;
            }
         }

         return true;
      }

      public boolean visit(OracleSelectQueryBlock queryBlock) {
         boolean isExprTableSrc = queryBlock.getFrom() instanceof SQLExprTableSource;
         if (!isExprTableSrc) {
            return true;
         } else {
            boolean rownum = false;

            for(SQLSelectItem item : queryBlock.getSelectList()) {
               SQLExpr itemExpr = item.getExpr();
               if (itemExpr instanceof SQLIdentifierExpr && ((SQLIdentifierExpr)itemExpr).getName().equalsIgnoreCase("ROWNUM")) {
                  rownum = true;
                  break;
               }
            }

            if (!rownum) {
               return true;
            } else {
               SQLObject parent = queryBlock.getParent();
               if (!(parent instanceof SQLSelect)) {
                  return true;
               } else {
                  SQLSelect select = (SQLSelect)parent;
                  if (select.getOrderBy() == null || select.getOrderBy().getItems().size() == 0) {
                     ++this.unorderedLimitCount;
                  }

                  return false;
               }
            }
         }
      }
   }
}
