Index: h2/src/main/org/h2/expression/Rank.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/expression/Rank.java	(revision )
+++ h2/src/main/org/h2/expression/Rank.java	(revision )
@@ -0,0 +1,280 @@
+package org.h2.expression;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.h2.command.Parser;
+import org.h2.command.Prepared;
+import org.h2.command.dml.Select;
+import org.h2.command.dml.SelectOrderBy;
+import org.h2.engine.Session;
+import org.h2.message.DbException;
+import org.h2.result.ResultInterface;
+import org.h2.table.ColumnResolver;
+import org.h2.table.TableFilter;
+import org.h2.util.StatementBuilder;
+import org.h2.util.StringUtils;
+import org.h2.value.Value;
+import org.h2.value.ValueArray;
+import org.h2.value.ValueInt;
+
+public class Rank extends Expression {
+	private final Select mainSelect;
+	private final ArrayList<SelectOrderBy> rankOrderList;
+	private final ArrayList<Expression> partitions;
+	private final boolean dense;
+	private Map<ValueArray, Integer> rankValues;
+
+	public Rank(Select mainSelect, ArrayList<Expression> partitions, ArrayList<SelectOrderBy> rankOrderList,
+			boolean dense) {
+		this.mainSelect = mainSelect;
+		this.partitions = partitions;
+		this.rankOrderList = rankOrderList;
+		this.dense = dense;
+	}
+
+	@Override
+	public Value getValue(Session session) {
+		Map<ValueArray, Integer> rankValues = getRankValues(session);
+
+		// compute key (partition + rankOrderList)
+		Value[] values = new Value[partitions.size() + rankOrderList.size()];
+
+		int idx = 0;
+		for (Expression e : partitions) {
+			values[idx++] = e.getValue(session);
+		}
+		for (SelectOrderBy o : rankOrderList) {
+			values[idx++] = o.expression.getValue(session);
+		}
+
+		// get rank
+		Integer rank = rankValues.get(ValueArray.get(values));
+		return ValueInt.get(rank != null ? rank : 0);
+	}
+
+	private Map<ValueArray, Integer> getRankValues(Session session) {
+		// build rank index first time
+		if (rankValues == null) {
+			rankValues = buildRankMap(session);
+		}
+
+		return rankValues;
+	}
+
+	private Map<ValueArray, Integer> buildRankMap(Session session) {
+		Prepared rankSelect = buildRankSelect(session);
+		ResultInterface result = rankSelect.query(0);
+
+		// build rank index
+		int rank = 0;
+		int row = 1;
+		ValueArray previousPartitions = null;
+		ValueArray previousValues = null;
+		Map<ValueArray, Integer> map = new HashMap<ValueArray, Integer>();
+		while (result.next()) {
+			Value[] values = result.currentRow();
+
+			// partition change
+			if (partitions.size() > 0) {
+				ValueArray currentPartitions = ValueArray.get(Arrays.copyOfRange(values, 0, partitions.size()));
+				if (!currentPartitions.equals(previousPartitions)) {
+					// reset rank for a new partition
+					row = 1;
+					rank = 0;
+					previousValues = null;
+					previousPartitions = currentPartitions;
+				}
+			}
+
+			// value change
+			ValueArray currentValues = ValueArray
+					.get(partitions.size() > 0 ? Arrays.copyOfRange(values, partitions.size(), values.length) : values);
+			if (!currentValues.equals(previousValues)) {
+				if (dense)
+					rank++;
+				else
+					rank = row;
+
+				previousValues = currentValues;
+			}
+
+			// System.out.println("=> rank=" + rank);
+
+			map.put(ValueArray.get(values), rank);
+			row++;
+		}
+
+		result.close();
+
+		return map;
+	}
+
+	private Prepared buildRankSelect(Session session) {
+		// build rank map select
+		StatementBuilder sb = new StatementBuilder();
+
+		sb.append("SELECT ");
+
+		sb.resetCount();
+		for (Expression e : partitions) {
+			sb.appendExceptFirst(",");
+			sb.append(e.getSQL());
+		}
+		for (SelectOrderBy o : rankOrderList) {
+			sb.appendExceptFirst(",");
+			sb.append(o.expression.getSQL());
+		}
+
+		sb.append(" FROM ");
+
+		TableFilter filter = mainSelect.getTopTableFilter();
+		if (filter != null) {
+			sb.resetCount();
+			int i = 0;
+			do {
+				sb.appendExceptFirst(" ");
+				sb.append(filter.getPlanSQL(i++ > 0));
+				filter = filter.getJoin();
+			} while (filter != null);
+		} else {
+			sb.resetCount();
+			int i = 0;
+			for (TableFilter f : mainSelect.getTopFilters()) {
+				do {
+					sb.appendExceptFirst(" ");
+					sb.append(f.getPlanSQL(i++ > 0));
+					f = f.getJoin();
+				} while (f != null);
+			}
+		}
+
+		if (mainSelect.getCondition() != null) {
+			sb.append(" WHERE ").append(StringUtils.unEnclose(mainSelect.getCondition().getSQL()));
+		}
+
+		sb.append(" ORDER BY ");
+
+		sb.resetCount();
+		for (Expression e : partitions) {
+			sb.appendExceptFirst(",");
+			sb.append(StringUtils.unEnclose(e.getSQL()));
+		}
+		for (SelectOrderBy o : rankOrderList) {
+			sb.appendExceptFirst(",");
+			sb.append(StringUtils.unEnclose(o.getSQL()));
+		}
+
+		// prepare
+		Parser parser = new Parser(session);
+		Prepared prepared = parser.prepare(sb.toString());
+
+		// copy parameter values
+		for (int i = 0; i < prepared.getParameters().size(); i++) {
+			prepared.getParameters().get(i).setValue(mainSelect.getParameters().get(i).getValue(session));
+		}
+
+		return prepared;
+	}
+
+	@Override
+	public int getType() {
+		return Value.INT;
+	}
+
+	@Override
+	public void mapColumns(ColumnResolver resolver, int level) {
+		if (partitions != null) {
+			for (Expression e : partitions) {
+				e.mapColumns(resolver, level);
+			}
+		}
+		for (SelectOrderBy o : rankOrderList) {
+			o.expression.mapColumns(resolver, level);
+		}
+	}
+
+	@Override
+	public Expression optimize(Session session) {
+		return this;
+	}
+
+	@Override
+	public void setEvaluatable(TableFilter tableFilter, boolean b) {
+		// nothing to do
+	}
+
+	@Override
+	public int getScale() {
+		return 0;
+	}
+
+	@Override
+	public long getPrecision() {
+		return ValueInt.PRECISION;
+	}
+
+	@Override
+	public int getDisplaySize() {
+		return ValueInt.DISPLAY_SIZE;
+	}
+
+	@Override
+	public String getSQL() {
+		StatementBuilder sb = new StatementBuilder();
+		sb.append(dense ? "DENSE_RANK() OVER (" : "RANK() OVER (");
+
+		if (partitions.size() > 0) {
+			sb.append(" PARTITION BY ");
+			sb.resetCount();
+			for (Expression e : partitions) {
+				sb.appendExceptFirst(",");
+				sb.append(e.getSQL());
+			}
+		}
+		sb.append(" ORDER BY ");
+
+		sb.resetCount();
+		for (SelectOrderBy orderBy : rankOrderList) {
+			sb.appendExceptFirst(",");
+			sb.append(orderBy.getSQL());
+		}
+
+		sb.append(")");
+		return sb.toString();
+	}
+
+	@Override
+	public void updateAggregate(Session session) {
+		// nothing to do
+	}
+
+	@Override
+	public boolean isEverything(ExpressionVisitor visitor) {
+		switch (visitor.getType()) {
+		case ExpressionVisitor.QUERY_COMPARABLE:
+		case ExpressionVisitor.OPTIMIZABLE_MIN_MAX_COUNT_ALL:
+		case ExpressionVisitor.DETERMINISTIC:
+		case ExpressionVisitor.INDEPENDENT:
+			return false;
+		case ExpressionVisitor.EVALUATABLE:
+		case ExpressionVisitor.READONLY:
+		case ExpressionVisitor.NOT_FROM_RESOLVER:
+		case ExpressionVisitor.GET_DEPENDENCIES:
+		case ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID:
+		case ExpressionVisitor.GET_COLUMNS:
+			// if everything else is the same, the rank is the same
+			return true;
+		default:
+			throw DbException.throwInternalError("type=" + visitor.getType());
+		}
+	}
+
+	@Override
+	public int getCost() {
+		return 0;
+	}
+
+}
Index: h2/src/main/org/h2/command/Parser.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/Parser.java	(date 1497098375000)
+++ h2/src/main/org/h2/command/Parser.java	(revision )
@@ -8,153 +8,33 @@
  */
 package org.h2.command;
 
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
 import org.h2.api.ErrorCode;
 import org.h2.api.Trigger;
-import org.h2.command.ddl.AlterIndexRename;
-import org.h2.command.ddl.AlterSchemaRename;
-import org.h2.command.ddl.AlterTableAddConstraint;
-import org.h2.command.ddl.AlterTableAlterColumn;
-import org.h2.command.ddl.AlterTableDropConstraint;
-import org.h2.command.ddl.AlterTableRename;
-import org.h2.command.ddl.AlterTableRenameColumn;
-import org.h2.command.ddl.AlterTableRenameConstraint;
-import org.h2.command.ddl.AlterUser;
-import org.h2.command.ddl.AlterView;
-import org.h2.command.ddl.Analyze;
-import org.h2.command.ddl.CreateAggregate;
-import org.h2.command.ddl.CreateConstant;
-import org.h2.command.ddl.CreateFunctionAlias;
-import org.h2.command.ddl.CreateIndex;
-import org.h2.command.ddl.CreateLinkedTable;
-import org.h2.command.ddl.CreateRole;
-import org.h2.command.ddl.CreateSchema;
-import org.h2.command.ddl.CreateSequence;
-import org.h2.command.ddl.CreateTable;
-import org.h2.command.ddl.CreateTableData;
-import org.h2.command.ddl.CreateTrigger;
-import org.h2.command.ddl.CreateUser;
-import org.h2.command.ddl.CreateUserDataType;
-import org.h2.command.ddl.CreateView;
-import org.h2.command.ddl.DeallocateProcedure;
-import org.h2.command.ddl.DefineCommand;
-import org.h2.command.ddl.DropAggregate;
-import org.h2.command.ddl.DropConstant;
-import org.h2.command.ddl.DropDatabase;
-import org.h2.command.ddl.DropFunctionAlias;
-import org.h2.command.ddl.DropIndex;
-import org.h2.command.ddl.DropRole;
-import org.h2.command.ddl.DropSchema;
-import org.h2.command.ddl.DropSequence;
-import org.h2.command.ddl.DropTable;
-import org.h2.command.ddl.DropTrigger;
-import org.h2.command.ddl.DropUser;
-import org.h2.command.ddl.DropUserDataType;
-import org.h2.command.ddl.DropView;
-import org.h2.command.ddl.GrantRevoke;
-import org.h2.command.ddl.PrepareProcedure;
-import org.h2.command.ddl.SetComment;
-import org.h2.command.ddl.TruncateTable;
-import org.h2.command.dml.AlterSequence;
-import org.h2.command.dml.AlterTableSet;
-import org.h2.command.dml.BackupCommand;
-import org.h2.command.dml.Call;
-import org.h2.command.dml.Delete;
-import org.h2.command.dml.ExecuteProcedure;
-import org.h2.command.dml.Explain;
-import org.h2.command.dml.Insert;
-import org.h2.command.dml.Merge;
-import org.h2.command.dml.NoOperation;
-import org.h2.command.dml.Query;
-import org.h2.command.dml.Replace;
-import org.h2.command.dml.RunScriptCommand;
-import org.h2.command.dml.ScriptCommand;
-import org.h2.command.dml.Select;
-import org.h2.command.dml.SelectOrderBy;
-import org.h2.command.dml.SelectUnion;
+import org.h2.command.ddl.*;
+import org.h2.command.dml.*;
 import org.h2.command.dml.Set;
-import org.h2.command.dml.SetTypes;
-import org.h2.command.dml.TransactionCommand;
-import org.h2.command.dml.Update;
 import org.h2.constraint.ConstraintReferential;
-import org.h2.engine.Constants;
-import org.h2.engine.Database;
-import org.h2.engine.DbObject;
-import org.h2.engine.FunctionAlias;
-import org.h2.engine.Procedure;
-import org.h2.engine.Right;
-import org.h2.engine.Session;
-import org.h2.engine.SysProperties;
-import org.h2.engine.User;
-import org.h2.engine.UserAggregate;
-import org.h2.engine.UserDataType;
-import org.h2.expression.Aggregate;
-import org.h2.expression.Alias;
-import org.h2.expression.CompareLike;
-import org.h2.expression.Comparison;
-import org.h2.expression.ConditionAndOr;
-import org.h2.expression.ConditionExists;
-import org.h2.expression.ConditionIn;
-import org.h2.expression.ConditionInSelect;
-import org.h2.expression.ConditionNot;
-import org.h2.expression.Expression;
-import org.h2.expression.ExpressionColumn;
-import org.h2.expression.ExpressionList;
-import org.h2.expression.Function;
-import org.h2.expression.FunctionCall;
-import org.h2.expression.JavaAggregate;
-import org.h2.expression.JavaFunction;
-import org.h2.expression.Operation;
-import org.h2.expression.Parameter;
-import org.h2.expression.Rownum;
-import org.h2.expression.SequenceValue;
-import org.h2.expression.Subquery;
-import org.h2.expression.TableFunction;
-import org.h2.expression.ValueExpression;
-import org.h2.expression.Variable;
-import org.h2.expression.Wildcard;
+import org.h2.engine.*;
+import org.h2.expression.*;
 import org.h2.index.Index;
 import org.h2.message.DbException;
 import org.h2.result.SortOrder;
 import org.h2.schema.Schema;
 import org.h2.schema.Sequence;
-import org.h2.table.Column;
-import org.h2.table.FunctionTable;
-import org.h2.table.IndexColumn;
-import org.h2.table.IndexHints;
-import org.h2.table.RangeTable;
-import org.h2.table.Table;
-import org.h2.table.TableFilter;
+import org.h2.table.*;
 import org.h2.table.TableFilter.TableFilterVisitor;
-import org.h2.table.TableView;
 import org.h2.util.MathUtils;
 import org.h2.util.New;
 import org.h2.util.StatementBuilder;
 import org.h2.util.StringUtils;
-import org.h2.value.CompareMode;
-import org.h2.value.DataType;
-import org.h2.value.Value;
-import org.h2.value.ValueBoolean;
-import org.h2.value.ValueBytes;
-import org.h2.value.ValueDate;
-import org.h2.value.ValueDecimal;
-import org.h2.value.ValueEnum;
-import org.h2.value.ValueInt;
-import org.h2.value.ValueLong;
-import org.h2.value.ValueNull;
-import org.h2.value.ValueString;
-import org.h2.value.ValueTime;
-import org.h2.value.ValueTimestamp;
+import org.h2.value.*;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.text.Collator;
+import java.util.*;
+
 /**
  * The parser is used to convert a SQL statement string to an command object.
  *
@@ -1790,45 +1670,51 @@
         return command;
     }
 
-    private void parseEndOfQuery(Query command) {
-        if (readIf("ORDER")) {
-            read("BY");
-            Select oldSelect = currentSelect;
-            if (command instanceof Select) {
-                currentSelect = (Select) command;
-            }
+    private ArrayList<SelectOrderBy> parseOrderList() {
-            ArrayList<SelectOrderBy> orderList = New.arrayList();
-            do {
-                boolean canBeNumber = true;
-                if (readIf("=")) {
-                    canBeNumber = false;
-                }
-                SelectOrderBy order = new SelectOrderBy();
-                Expression expr = readExpression();
-                if (canBeNumber && expr instanceof ValueExpression &&
-                        expr.getType() == Value.INT) {
-                    order.columnIndexExpr = expr;
-                } else if (expr instanceof Parameter) {
-                    recompileAlways = true;
-                    order.columnIndexExpr = expr;
-                } else {
-                    order.expression = expr;
-                }
-                if (readIf("DESC")) {
-                    order.descending = true;
-                } else {
-                    readIf("ASC");
-                }
-                if (readIf("NULLS")) {
-                    if (readIf("FIRST")) {
-                        order.nullsFirst = true;
-                    } else {
-                        read("LAST");
-                        order.nullsLast = true;
-                    }
-                }
-                orderList.add(order);
-            } while (readIf(","));
+        ArrayList<SelectOrderBy> orderList = New.arrayList();
+        do {
+            boolean canBeNumber = true;
+            if (readIf("=")) {
+                canBeNumber = false;
+            }
+            SelectOrderBy order = new SelectOrderBy();
+            Expression expr = readExpression();
+            if (canBeNumber && expr instanceof ValueExpression &&
+                    expr.getType() == Value.INT) {
+                order.columnIndexExpr = expr;
+            } else if (expr instanceof Parameter) {
+                recompileAlways = true;
+                order.columnIndexExpr = expr;
+            } else {
+                order.expression = expr;
+            }
+            if (readIf("DESC")) {
+                order.descending = true;
+            } else {
+                readIf("ASC");
+            }
+            if (readIf("NULLS")) {
+                if (readIf("FIRST")) {
+                    order.nullsFirst = true;
+                } else {
+                    read("LAST");
+                    order.nullsLast = true;
+                }
+            }
+            orderList.add(order);
+        } while (readIf(","));
+
+        return orderList;
+    }
+
+    private void parseEndOfQuery(Query command) {
+        if (readIf("ORDER")) {
+            read("BY");
+            Select oldSelect = currentSelect;
+            if (command instanceof Select) {
+                currentSelect = (Select) command;
+            }
+            ArrayList<SelectOrderBy> orderList = parseOrderList();
             command.setOrder(orderList);
             currentSelect = oldSelect;
         }
@@ -2685,6 +2571,31 @@
             }
             return new Rownum(currentSelect == null ? currentPrepared
                     : currentSelect);
+
+            case Function.RANK:
+            case Function.DENSE_RANK:
+                boolean dense = (function.getFunctionType() == Function.DENSE_RANK);
+                read(")");
+                read("OVER");
+                read("(");
+
+                ArrayList<Expression> partitions = New.arrayList();
+                if (readIf("PARTITION")) {
+                    read("BY");
+                    boolean lparen = readIf("(");
+                    do {
+                        partitions.add(readExpression());
+                    } while (readIf(","));
+
+                    if (lparen)
+                        read(")");
+                }
+
+                read("ORDER");
+                read("BY");
+                ArrayList<SelectOrderBy> orderList = parseOrderList();
+                read(")");
+                return new Rank(currentSelect, partitions, orderList, dense);
         default:
             if (!readIf(")")) {
                 int i = 0;
Index: h2/src/test/org/h2/test/db/TestFunctions.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/test/org/h2/test/db/TestFunctions.java	(date 1497098375000)
+++ h2/src/test/org/h2/test/db/TestFunctions.java	(revision )
@@ -5,39 +5,6 @@
  */
 package org.h2.test.db;
 
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.math.BigDecimal;
-import java.sql.Array;
-import java.sql.Blob;
-import java.sql.CallableStatement;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.sql.Types;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Currency;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Properties;
-import java.util.TimeZone;
-import java.util.UUID;
 import org.h2.api.Aggregate;
 import org.h2.api.AggregateFunction;
 import org.h2.api.ErrorCode;
@@ -48,13 +15,17 @@
 import org.h2.test.TestBase;
 import org.h2.test.ap.TestAnnotationProcessor;
 import org.h2.tools.SimpleResultSet;
-import org.h2.util.DateTimeUtils;
-import org.h2.util.IOUtils;
-import org.h2.util.New;
-import org.h2.util.StringUtils;
-import org.h2.util.ToDateParser;
+import org.h2.util.*;
 import org.h2.value.Value;
 
+import java.io.*;
+import java.math.BigDecimal;
+import java.sql.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.Date;
+
 /**
  * Tests for user defined functions and aggregates.
  */
@@ -118,6 +89,8 @@
         testThatCurrentTimestampUpdatesOutsideATransaction();
         testAnnotationProcessorsOutput();
         testRound();
+        testRank(false);
+        testRank(true);
 
         deleteDb("functions");
     }
@@ -2125,6 +2098,99 @@
         assertEquals(Boolean.class.getName(), rs.getObject(1).getClass().getName());
 
         stat.execute("drop alias " + functionName + "");
+
+        conn.close();
+    }
+
+    private void testRank(boolean dense) throws SQLException {
+        /*
+         * CREATE TABLE testRank (
+         * id number(9),
+         * txt1 varchar(16),
+         * txt2 varchar(16),
+         * num number(9, 0));
+         *
+         * insert into testRank(id, txt1, txt2, num) values(1, 'a', 'a', 1);
+         * insert into testRank(id, txt1, txt2, num) values(2, 'a', 'b', 1);
+		 * insert into testRank(id, txt1, txt2, num) values(3, 'b', 'a', 1);
+		 * insert into testRank(id, txt1, txt2, num) values(4, null, null, 1);
+		 * insert into testRank(id, txt1, txt2, num) values(5, 'a', 'a', 2);
+		 * insert into testRank(id, txt1, txt2, num) values(6, 'a', 'b', 2);
+		 * insert into testRank(id, txt1, txt2, num) values(7, null, null, 2);
+		 * 
+		 * SELECT id,txt1,txt2,num,RANK () OVER (ORDER BY txt1 DESC NULLS LAST)
+		 * rnk FROM testRank WHERE num=1 order by id asc;
+		 * 
+		 * ID txt1 txt2 num rnk
+		 * 1  a    a    1   2
+		 * 2  a    b    1   2
+		 * 3  b    a    1   1
+		 * 4            1   4
+		 * 
+		 * SELECT id,txt1,txt2,num,RANK () OVER (PARTITION BY num ORDER BY txt1
+		 * DESC NULLS LAST) rnk FROM testRank order by id asc;
+		 * 
+		 * ID txt1 txt2 num rnk
+		 * 1  a    a    1   2
+		 * 2  a    b    1   2
+		 * 3  b    a    1   1
+		 * 4            1   4
+		 * 5  a    a    2   1
+		 * 6  a    b    2   1
+		 * 7            2   3
+		 */
+        String rankFunction = dense ? "DENSE_RANK()" : "RANK()";
+
+        Connection conn = getConnection("functions");
+        Statement stat = conn.createStatement();
+
+        stat.execute("DROP TABLE IF EXISTS testRank");
+        stat.execute("CREATE TABLE testRank(id BIGINT, txt1 " + "varchar(16), txt2 varchar(16), num number(9, 0));");
+
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(1, 'a', 'a', 1)");
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(2, 'a', 'b', 1)");
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(3, 'b', 'a', 1)");
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(4, null, null, 1)");
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(5, 'a', 'a', 2)");
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(6, 'a', 'b', 2)");
+        stat.execute("insert into testRank(id, txt1, txt2, num) " + "values(7, null, null, 2)");
+
+        // query 1
+        String query = "SELECT id,txt1,txt2,num," + rankFunction + " OVER (ORDER BY txt1 DESC NULLS LAST) rnk "
+                + "FROM testRank WHERE num=1 ORDER BY id ASC";
+        ResultSet rs = stat.executeQuery(query);
+
+        int[] expectedRanks = dense ? new int[]{2, 2, 1, 3} : new int[]{2, 2, 1, 4};
+        for (int i = 0; i < expectedRanks.length; i++) {
+            rs.next();
+            assertEquals("row " + i, expectedRanks[i], rs.getInt(5));
+        }
+        rs.close();
+
+        // query 2
+        query = "SELECT id,txt1,txt2,num," + rankFunction
+                + " OVER (PARTITION BY num ORDER BY txt1 DESC NULLS LAST) rnk " + "FROM testRank ORDER BY id ASC";
+        rs = stat.executeQuery(query);
+
+        expectedRanks = dense ? new int[]{2, 2, 1, 3, 1, 1, 2} : new int[]{2, 2, 1, 4, 1, 1, 3};
+        for (int i = 0; i < expectedRanks.length; i++) {
+            rs.next();
+            assertEquals("row " + i, expectedRanks[i], rs.getInt(5));
+        }
+        rs.close();
+
+        // query 3: same as query 1 but with parameters
+        PreparedStatement ps = conn.prepareStatement("SELECT id,txt1,txt2,num," + rankFunction
+                + " OVER (ORDER BY txt1 DESC NULLS LAST) rnk " + "FROM testRank WHERE num=? ORDER BY id ASC");
+        ps.setInt(1, 1);
+        rs = ps.executeQuery();
+
+        expectedRanks = dense ? new int[]{2, 2, 1, 3} : new int[]{2, 2, 1, 4};
+        for (int i = 0; i < expectedRanks.length; i++) {
+            rs.next();
+            assertEquals("row " + i, expectedRanks[i], rs.getInt(5));
+        }
+        rs.close();
 
         conn.close();
     }
Index: h2/src/main/org/h2/command/dml/Select.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/Select.java	(date 1497098375000)
+++ h2/src/main/org/h2/command/dml/Select.java	(revision )
@@ -5,10 +5,6 @@
  */
 package org.h2.command.dml;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
 import org.h2.api.ErrorCode;
 import org.h2.api.Trigger;
 import org.h2.command.CommandInterface;
@@ -16,30 +12,13 @@
 import org.h2.engine.Database;
 import org.h2.engine.Session;
 import org.h2.engine.SysProperties;
-import org.h2.expression.Comparison;
-import org.h2.expression.ConditionAndOr;
-import org.h2.expression.Expression;
-import org.h2.expression.ExpressionColumn;
-import org.h2.expression.ExpressionVisitor;
-import org.h2.expression.Parameter;
+import org.h2.expression.*;
 import org.h2.index.Cursor;
 import org.h2.index.Index;
 import org.h2.index.IndexType;
 import org.h2.message.DbException;
-import org.h2.result.LazyResult;
-import org.h2.result.LocalResult;
-import org.h2.result.ResultInterface;
-import org.h2.result.ResultTarget;
-import org.h2.result.Row;
-import org.h2.result.SearchRow;
-import org.h2.result.SortOrder;
-import org.h2.table.Column;
-import org.h2.table.ColumnResolver;
-import org.h2.table.IndexColumn;
-import org.h2.table.JoinBatch;
-import org.h2.table.Table;
-import org.h2.table.TableFilter;
-import org.h2.table.TableView;
+import org.h2.result.*;
+import org.h2.table.*;
 import org.h2.util.New;
 import org.h2.util.StatementBuilder;
 import org.h2.util.StringUtils;
@@ -48,6 +27,11 @@
 import org.h2.value.ValueArray;
 import org.h2.value.ValueNull;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
 /**
  * This class represents a simple SELECT statement.
  *
@@ -1195,6 +1179,10 @@
 
     public void setHaving(Expression having) {
         this.having = having;
+    }
+
+    public Expression getCondition() {
+        return condition;
     }
 
     public Expression getHaving() {
Index: h2/src/main/org/h2/expression/Function.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/expression/Function.java	(date 1497098375000)
+++ h2/src/main/org/h2/expression/Function.java	(revision )
@@ -5,23 +5,6 @@
  */
 package org.h2.expression;
 
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
 import org.h2.api.ErrorCode;
 import org.h2.command.Command;
 import org.h2.command.Parser;
@@ -37,40 +20,22 @@
 import org.h2.security.CipherFactory;
 import org.h2.security.SHA256;
 import org.h2.store.fs.FileUtils;
-import org.h2.table.Column;
-import org.h2.table.ColumnResolver;
-import org.h2.table.LinkSchema;
-import org.h2.table.Table;
-import org.h2.table.TableFilter;
+import org.h2.table.*;
 import org.h2.tools.CompressTool;
 import org.h2.tools.Csv;
-import org.h2.util.AutoCloseInputStream;
-import org.h2.util.DateTimeUtils;
-import org.h2.util.IOUtils;
-import org.h2.util.JdbcUtils;
-import org.h2.util.MathUtils;
-import org.h2.util.New;
-import org.h2.util.StatementBuilder;
-import org.h2.util.StringUtils;
-import org.h2.util.ToChar;
-import org.h2.util.ToDateParser;
-import org.h2.util.Utils;
-import org.h2.value.DataType;
-import org.h2.value.Value;
-import org.h2.value.ValueArray;
-import org.h2.value.ValueBoolean;
-import org.h2.value.ValueBytes;
-import org.h2.value.ValueDate;
-import org.h2.value.ValueDouble;
-import org.h2.value.ValueInt;
-import org.h2.value.ValueLong;
-import org.h2.value.ValueNull;
-import org.h2.value.ValueResultSet;
-import org.h2.value.ValueString;
-import org.h2.value.ValueTime;
-import org.h2.value.ValueTimestamp;
-import org.h2.value.ValueUuid;
+import org.h2.util.*;
+import org.h2.value.*;
 
+import java.io.*;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
 /**
  * This class implements most built-in functions of this database.
  */
@@ -124,6 +89,8 @@
 
     public static final int REGEXP_LIKE = 240;
 
+    public static final int RANK = 250, DENSE_RANK = 251;
+
     /**
      * Used in MySQL-style INSERT ... ON DUPLICATE KEY UPDATE ... VALUES
      */
@@ -486,6 +453,10 @@
 
         // ON DUPLICATE KEY VALUES function
         addFunction("VALUES", VALUES, 1, Value.NULL, false, true, false);
+
+        // RANK
+        addFunctionWithNull("RANK", RANK, 0, Value.INT);
+        addFunctionWithNull("DENSE_RANK", DENSE_RANK, 0, Value.INT);
     }
 
     protected Function(Database database, FunctionInfo info) {
