Merge branch 'pr/448' into asf-master
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index 54baa19..87d0972 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -270,4 +270,5 @@
 	public List<String> getSystemSchemas() {
 		return getAdapter().getSystemSchemas();
 	}
+
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index a043e2f..54e8580 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -255,4 +255,5 @@
 	 * @return list of system schemas
 	 */
 	List<String> getSystemSchemas();
+
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
index 4ebde3f..bd43ee9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
@@ -21,9 +21,7 @@
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.dba.JdbcActionBuilder;
-import org.apache.cayenne.query.BatchQuery;
-import org.apache.cayenne.query.ProcedureQuery;
-import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.*;
 
 /**
  * @since 1.2
@@ -31,6 +29,13 @@
 public class SQLServerActionBuilder extends JdbcActionBuilder {
 
 	/**
+	 * Stores the major version of the database.
+	 *
+	 * @since 4.2
+	 */
+	private Integer version;
+
+	/**
 	 * @since 4.0
 	 */
 	public SQLServerActionBuilder(DataNode dataNode) {
@@ -53,4 +58,38 @@
 	public SQLAction procedureAction(ProcedureQuery query) {
 		return new SQLServerProcedureAction(query, dataNode);
 	}
+
+	/**
+	 * @since 4.2
+	 */
+	@Override
+	public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+		if (query.getOrderings() == null || query.getOrderings().size() == 0 ||
+				version == null || version < 12) {
+			return new SQLServerSelectAction(query, dataNode, true);
+		}
+
+		return new SQLServerSelectAction(query, dataNode, false);
+	}
+
+	/**
+	 * @since 4.2
+	 */
+	@Override
+	public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+		if (query.getOrderings() == null || query.getOrderings().size() == 0 ||
+				version == null || version < 12) {
+			return new SQLServerSelectAction(query, dataNode, true);
+		}
+
+		return new SQLServerSelectAction(query, dataNode, false);
+	}
+
+	public Integer getVersion() {
+		return version;
+	}
+
+	public void setVersion(Integer version) {
+		this.version = version;
+	}
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index 92fcd46..9805e0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -75,6 +75,15 @@
 public class SQLServerAdapter extends SybaseAdapter {
 
 	/**
+	 * Stores the major version of the database.
+	 * Database versions 12 and higher support the use of LIMIT,
+	 * lower versions use TOP N
+	 *
+	 * @since 4.2
+	 */
+	private Integer version;
+
+	/**
 	 * @deprecated since 4.2 unused
 	 */
 	@Deprecated
@@ -109,7 +118,9 @@
 	 */
 	@Override
 	public SQLTreeProcessor getSqlTreeProcessor() {
-		return new SQLServerTreeProcessor();
+		SQLServerTreeProcessor sqlServerTreeProcessor = new SQLServerTreeProcessor();
+		sqlServerTreeProcessor.setVersion(getVersion());
+		return sqlServerTreeProcessor;
 	}
 
 	/**
@@ -119,7 +130,9 @@
 	 */
 	@Override
 	public SQLAction getAction(Query query, DataNode node) {
-		return query.createSQLAction(new SQLServerActionBuilder(node));
+		SQLServerActionBuilder sqlServerActionBuilder = new SQLServerActionBuilder(node);
+		sqlServerActionBuilder.setVersion(this.version);
+		return query.createSQLAction(sqlServerActionBuilder);
 	}
 
 	@Override
@@ -127,4 +140,11 @@
 		return SYSTEM_SCHEMAS;
 	}
 
+	public Integer getVersion() {
+		return version;
+	}
+
+	public void setVersion(Integer version) {
+		this.version = version;
+	}
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java
new file mode 100644
index 0000000..7c09eef
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you 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
+ *
+ *      https://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 org.apache.cayenne.dba.sqlserver;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.Select;
+
+/**
+ * @since 4.2
+ */
+public class SQLServerSelectAction extends SelectAction {
+
+    /**
+     * When using TOP N instead of LIMIT. The offset will be manual.
+     *
+     */
+    private Boolean isManualOffset;
+
+    public SQLServerSelectAction(Select<?> query, DataNode dataNode, Boolean isManualOffset) {
+        super(query, dataNode);
+
+        this.isManualOffset = isManualOffset;
+    }
+
+    @Override
+    protected int getInMemoryOffset(int queryOffset) {
+        if (isManualOffset) {
+            return super.getInMemoryOffset(queryOffset);
+        }
+        return 0;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
index 052e8eb..f0c7d6f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
@@ -50,6 +50,7 @@
         SQLServerAdapter adapter = objectFactory.newInstance(
                 SQLServerAdapter.class,
                 SQLServerAdapter.class.getName());
+        adapter.setVersion(md.getDatabaseMajorVersion());
 
         // detect whether generated keys are supported
         boolean generatedKeys = false;
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java
index e2ecd53..347da1f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTreeProcessor.java
@@ -19,9 +19,9 @@
 
 package org.apache.cayenne.dba.sqlserver;
 
-import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
-import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.*;
 import org.apache.cayenne.dba.sqlserver.sqltree.SQLServerColumnNode;
+import org.apache.cayenne.dba.sqlserver.sqltree.SQLServerLimitOffsetNode;
 import org.apache.cayenne.dba.sybase.SybaseSQLTreeProcessor;
 
 /**
@@ -29,8 +29,32 @@
  */
 public class SQLServerTreeProcessor extends SybaseSQLTreeProcessor {
 
+    private Integer version;
+
     @Override
     protected void onColumnNode(Node parent, ColumnNode child, int index) {
         replaceChild(parent, index,  new SQLServerColumnNode(child));
     }
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+        if (version != null && version >= 12) {
+            for (int i = 0; i < parent.getChildrenCount(); i++) {
+                if (parent.getChild(i) instanceof OrderByNode) {
+                    replaceChild(parent, index,  new SQLServerLimitOffsetNode(child.getLimit(), child.getOffset()), false);
+                    return;
+                }
+            }
+        }
+
+        super.onLimitOffsetNode(parent, child, index);
+    }
+
+    public Integer getVersion() {
+        return version;
+    }
+
+    public void setVersion(Integer version) {
+        this.version = version;
+    }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java
new file mode 100644
index 0000000..1b916aa
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you 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
+ *
+ *      https://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 org.apache.cayenne.dba.sqlserver.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class SQLServerLimitOffsetNode extends LimitOffsetNode {
+
+    public SQLServerLimitOffsetNode(int limit, int offset) {
+        super(limit, offset);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        // OFFSET X ROWS FETCH NEXT Y ROWS ONLY
+        if(limit == 0 && offset == 0) {
+            return buffer;
+        }
+        buffer.append(" OFFSET ").append(offset).append(" ROWS");
+        if (limit > 0) {
+            return buffer.append(" FETCH NEXT ").append(limit).append(" ROWS ONLY");
+        }
+        return buffer;
+    }
+
+    @Override
+    public Node copy() {
+        return new SQLServerLimitOffsetNode(limit, offset);
+    }
+
+}