From 63ff050e4d48f8b997905e9e0a11d944ea6038a6 Mon Sep 17 00:00:00 2001
From: "Brian S. O'Neill" <bronee@gmail.com>
Date: Mon, 14 Apr 2008 03:18:32 +0000
Subject: Support query fetch limits.

---
 src/main/java/com/amazon/carbonado/Query.java      |  42 +++++++++
 .../com/amazon/carbonado/qe/AbstractQuery.java     |  22 +++++
 .../amazon/carbonado/qe/AbstractQueryExecutor.java |  23 ++++-
 .../carbonado/qe/DelegatedQueryExecutor.java       |   4 +
 .../java/com/amazon/carbonado/qe/EmptyQuery.java   |  16 ++++
 .../com/amazon/carbonado/qe/QueryExecutor.java     |   7 ++
 .../com/amazon/carbonado/qe/StandardQuery.java     |  23 +++++
 .../repo/jdbc/H2ExceptionTransformer.java          |  35 +++++++
 .../carbonado/repo/jdbc/H2SupportStrategy.java     |  60 ++++++++++++
 .../amazon/carbonado/repo/jdbc/JDBCStorage.java    | 103 ++++++++++++++++++++-
 .../carbonado/repo/jdbc/JDBCSupportStrategy.java   |  35 +++++++
 .../carbonado/repo/jdbc/MysqlSupportStrategy.java  |  21 +++++
 .../carbonado/repo/jdbc/OracleSupportStrategy.java |  22 +++++
 .../repo/jdbc/PostgresqlSupportStrategy.java       |  55 +++++++++++
 .../carbonado/repo/logging/LoggingQuery.java       |  18 ++++
 15 files changed, 483 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/H2ExceptionTransformer.java
 create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/H2SupportStrategy.java
 create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/PostgresqlSupportStrategy.java

(limited to 'src/main')

diff --git a/src/main/java/com/amazon/carbonado/Query.java b/src/main/java/com/amazon/carbonado/Query.java
index a729331..d1cebca 100644
--- a/src/main/java/com/amazon/carbonado/Query.java
+++ b/src/main/java/com/amazon/carbonado/Query.java
@@ -267,6 +267,23 @@ public interface Query<S extends Storable> {
      */
     Cursor<S> fetch() throws FetchException;
 
+    /**
+     * Fetches a slice of results for this query, as defined by a numerical
+     * range. A slice can be used to limit the number of results from a
+     * query. It is strongly recommended that the query be given a total {@link
+     * #orderBy ordering} in order for the slice results to be deterministic.
+     *
+     * @param from zero-based {@code from} record number, inclusive
+     * @param to optional zero-based {@code to} record number, exclusive
+     * @return fetch results
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws IllegalArgumentException if {@code from} is negative or if
+     * {@code from} is more than {@code to}
+     * @throws FetchException if storage layer throws an exception
+     * @since 1.2
+     */
+    Cursor<S> fetch(long from, Long to) throws FetchException;
+
     /**
      * Fetches results for this query after a given starting point, which is
      * useful for re-opening a cursor. This is only effective when query has
@@ -285,6 +302,31 @@ public interface Query<S extends Storable> {
      */
     Cursor<S> fetchAfter(S start) throws FetchException;
 
+    /**
+     * Fetches a slice of results for this query after a given starting point,
+     * which is useful for re-opening a cursor. This is only effective when
+     * query has been given an explicit {@link #orderBy ordering}. If not a
+     * total ordering, then returned cursor may start at an earlier position.
+     * It is strongly recommended that the query be given a total ordering in
+     * order for the slice results to be deterministic.
+     *
+     * <p>Note: This method can be very expensive to call repeatedly, if the
+     * query needs to perform a sort operation. Ideally, the query ordering
+     * should match the natural ordering of an index or key.
+     *
+     * @param start storable to attempt to start after; if null, fetch all results
+     * @param from zero-based {@code from} record number, inclusive
+     * @param to optional zero-based {@code to} record number, exclusive
+     * @return fetch results
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws IllegalArgumentException if {@code from} is negative or if
+     * {@code from} is more than {@code to}
+     * @throws FetchException if storage layer throws an exception
+     * @see Repository#enterTransaction(IsolationLevel)
+     * @since 1.2
+     */
+    Cursor<S> fetchAfter(S start, long from, Long to) throws FetchException;
+
     /**
      * Attempts to load exactly one matching object. If the number of matching
      * records is zero or exceeds one, then an exception is thrown instead.
diff --git a/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java b/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java
index 7639348..767b039 100644
--- a/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java
@@ -129,4 +129,26 @@ public abstract class AbstractQuery<S extends Storable> implements Query<S>, App
 
     @Override
     public abstract boolean equals(Object obj);
+
+    /**
+     * Called by sliced fetch to ensure that arguments are valid.
+     *
+     * @return false if from is 0 and to is null
+     * @throws IllegalArgumentException if arguments are invalid
+     * @since 1.2
+     */
+    protected boolean checkSliceArguments(long from, Long to) {
+        if (from < 0) {
+            throw new IllegalArgumentException("Slice from is negative: " + from);
+        }
+        if (to == null) {
+            if (from == 0) {
+                return false;
+            }
+        } else if (from > to) {
+            throw new IllegalArgumentException
+                ("Slice from is more than to: " + from + " > " + to);
+        }
+        return true;
+    }
 }
diff --git a/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java
index dc67fc1..8a8a127 100644
--- a/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/AbstractQueryExecutor.java
@@ -24,6 +24,9 @@ import com.amazon.carbonado.Cursor;
 import com.amazon.carbonado.FetchException;
 import com.amazon.carbonado.Storable;
 
+import com.amazon.carbonado.cursor.LimitCursor;
+import com.amazon.carbonado.cursor.SkipCursor;
+
 import com.amazon.carbonado.filter.FilterValues;
 
 /**
@@ -37,7 +40,25 @@ public abstract class AbstractQueryExecutor<S extends Storable> implements Query
     }
 
     /**
-     * Counts results by opening a cursor and skipping entries.
+     * Produces a slice via skip and limit cursors. Subclasses are encouraged
+     * to override with a more efficient implementation.
+     *
+     * @since 1.2
+     */
+    public Cursor<S> fetch(FilterValues<S> values, long from, Long to) throws FetchException {
+        Cursor<S> cursor = fetch(values);
+        if (from > 0) {
+            cursor = new SkipCursor<S>(cursor, from);
+        }
+        if (to != null) {
+            cursor = new LimitCursor<S>(cursor, to - from);
+        }
+        return cursor;
+    }
+
+    /**
+     * Counts results by opening a cursor and skipping entries. Subclasses are
+     * encouraged to override with a more efficient implementation.
      */
     public long count(FilterValues<S> values) throws FetchException {
         Cursor<S> cursor = fetch(values);
diff --git a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java
index b4bc0a3..3ec209d 100644
--- a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutor.java
@@ -95,6 +95,10 @@ public class DelegatedQueryExecutor<S extends Storable> implements QueryExecutor
         return applyFilterValues(values).fetch();
     }
 
+    public Cursor<S> fetch(FilterValues<S> values, long from, Long to) throws FetchException {
+        return applyFilterValues(values).fetch(from, to);
+    }
+
     public long count(FilterValues<S> values) throws FetchException {
         return applyFilterValues(values).count();
     }
diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
index f3d54f5..9754019 100644
--- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
@@ -212,6 +212,14 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
         return EmptyCursor.the();
     }
 
+    /**
+     * Always returns an {@link EmptyCursor}.
+     */
+    public Cursor<S> fetch(long from, Long to) {
+        checkSliceArguments(from, to);
+        return EmptyCursor.the();
+    }
+
     /**
      * Always returns an {@link EmptyCursor}.
      */
@@ -219,6 +227,14 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
         return EmptyCursor.the();
     }
 
+    /**
+     * Always returns an {@link EmptyCursor}.
+     */
+    public Cursor<S> fetchAfter(S start, long from, Long to) {
+        checkSliceArguments(from, to);
+        return EmptyCursor.the();
+    }
+
     /**
      * Always throws {@link PersistNoneException}.
      */
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java
index b0ab801..6b5a864 100644
--- a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java
@@ -45,6 +45,13 @@ public interface QueryExecutor<S extends Storable> {
      */
     Cursor<S> fetch(FilterValues<S> values) throws FetchException;
 
+    /**
+     * Returns a new cursor using the given filter values and slice.
+     *
+     * @since 1.2
+     */
+    Cursor<S> fetch(FilterValues<S> values, long from, Long to) throws FetchException;
+
     /**
      * Counts the query results using the given filter values.
      */
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
index 5596c32..10fdf43 100644
--- a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
@@ -217,6 +217,18 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
         }
     }
 
+    public Cursor<S> fetch(long from, Long to) throws FetchException {
+        if (!checkSliceArguments(from, to)) {
+            return fetch();
+        }
+        try {
+            QueryHints hints = QueryHints.emptyHints().with(QueryHint.CONSUME_SLICE);
+            return executorFactory().executor(mFilter, mOrdering, hints).fetch(mValues, from, to);
+        } catch (RepositoryException e) {
+            throw e.toFetchException();
+        }
+    }
+
     public Cursor<S> fetchAfter(S start) throws FetchException {
         OrderingList<S> orderings;
         if (start == null || (orderings = mOrdering).size() == 0) {
@@ -225,6 +237,17 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
         return buildAfter(start, orderings).fetch();
     }
 
+    public Cursor<S> fetchAfter(S start, long from, Long to) throws FetchException {
+        if (!checkSliceArguments(from, to)) {
+            return fetchAfter(start);
+        }
+        OrderingList<S> orderings;
+        if (start == null || (orderings = mOrdering).size() == 0) {
+            return fetch(from, to);
+        }
+        return buildAfter(start, orderings).fetch(from, to);
+    }
+
     private Query<S> buildAfter(S start, OrderingList<S> orderings) throws FetchException {
         Class<S> storableType = getStorableType();
         Filter<S> orderFilter = Filter.getClosedFilter(storableType);
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/H2ExceptionTransformer.java b/src/main/java/com/amazon/carbonado/repo/jdbc/H2ExceptionTransformer.java
new file mode 100644
index 0000000..e367cb0
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/H2ExceptionTransformer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * 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 com.amazon.carbonado.repo.jdbc;
+
+import java.sql.SQLException;
+
+/**
+ * 
+ *
+ * @author Brian S O'Neill
+ * @since 1.2
+ */
+class H2ExceptionTransformer extends JDBCExceptionTransformer {
+    public static int DUPLICATE_KEY = 23001;
+
+    public boolean isUniqueConstraintError(SQLException e) {
+        return DUPLICATE_KEY == e.getErrorCode();
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/H2SupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/H2SupportStrategy.java
new file mode 100644
index 0000000..6850fbc
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/H2SupportStrategy.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2008 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * 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 com.amazon.carbonado.repo.jdbc;
+
+/**
+ * 
+ *
+ * @author Brian S O'Neill
+ * @since 1.2
+ */
+class H2SupportStrategy extends JDBCSupportStrategy {
+    private static final String TRUNCATE_STATEMENT = "TRUNCATE TABLE %s";
+
+    protected H2SupportStrategy(JDBCRepository repo) {
+        super(repo);
+
+        setTruncateTableStatement(TRUNCATE_STATEMENT);
+    }
+
+    @Override
+    JDBCExceptionTransformer createExceptionTransformer() {
+        return new H2ExceptionTransformer();
+    }
+
+    @Override
+    SliceOption getSliceOption() {
+        return SliceOption.LIMIT_AND_OFFSET;
+    }
+
+    @Override
+    String buildSelectWithSlice(String select, boolean limit, boolean offset) {
+        if (limit) {
+            if (offset) {
+                return select.concat(" LIMIT ? OFFSET ?");
+            } else {
+                return select.concat(" LIMIT ?");
+            }
+        } else if (offset) {
+            return select.concat(" LIMIT 2147483647 OFFSET ?");
+        } else {
+            return select;
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
index 54ff24a..8810c09 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
@@ -44,6 +44,8 @@ import com.amazon.carbonado.SupportException;
 import com.amazon.carbonado.Transaction;
 import com.amazon.carbonado.Trigger;
 import com.amazon.carbonado.capability.IndexInfo;
+import com.amazon.carbonado.cursor.EmptyCursor;
+import com.amazon.carbonado.cursor.LimitCursor;
 import com.amazon.carbonado.filter.AndFilter;
 import com.amazon.carbonado.filter.Filter;
 import com.amazon.carbonado.filter.FilterValues;
@@ -646,6 +648,98 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
             }
         }
 
+        @Override
+        public Cursor<S> fetch(FilterValues<S> values, long from, Long to) throws FetchException {
+            if (to != null && (to - from) <= 0) {
+                return EmptyCursor.the();
+            }
+
+            JDBCSupportStrategy.SliceOption option = mSupportStrategy.getSliceOption();
+
+            String select;
+
+            switch (option) {
+            case NOT_SUPPORTED: default:
+                return super.fetch(values, from, to);
+            case LIMIT_ONLY:
+                if (from > 0 || to == null) {
+                    return super.fetch(values, from, to);
+                }
+                select = prepareSelect(values, false);
+                select = mSupportStrategy.buildSelectWithSlice(select, true, false);
+                break;
+            case OFFSET_ONLY:
+                if (from <= 0) {
+                    return super.fetch(values, from, to);
+                }
+                select = prepareSelect(values, false);
+                select = mSupportStrategy.buildSelectWithSlice(select, false, true);
+                break;
+            case LIMIT_AND_OFFSET:
+            case OFFSET_AND_LIMIT:
+                select = prepareSelect(values, false);
+                select = mSupportStrategy.buildSelectWithSlice(select, to != null, from > 0);
+                break;
+            }
+
+            if (mRepository.localTransactionScope().isForUpdate()) {
+                select = select.concat(" FOR UPDATE");
+            }
+
+            Connection con = mRepository.getConnection();
+            try {
+                PreparedStatement ps = con.prepareStatement(select);
+                Integer fetchSize = mRepository.getFetchSize();
+                if (fetchSize != null) {
+                    ps.setFetchSize(fetchSize);
+                }
+
+                try {
+                    int psOrdinal = setParameters(ps, values);
+
+                    if (from > 0) {
+                        if (to != null) {
+                            switch (option) {
+                            case OFFSET_ONLY:
+                                ps.setLong(psOrdinal, from);
+                                Cursor<S> c = new JDBCCursor<S>(JDBCStorage.this, con, ps);
+                                return new LimitCursor<S>(c, to - from);
+                            case LIMIT_AND_OFFSET:
+                                ps.setLong(psOrdinal, to - from);
+                                ps.setLong(psOrdinal + 1, from);
+                                break;
+                            case OFFSET_AND_LIMIT:
+                                ps.setLong(psOrdinal, from);
+                                ps.setLong(psOrdinal + 1, to - from);
+                            }
+                        } else {
+                            ps.setLong(psOrdinal, from);
+                        }
+                    } else if (to != null) {
+                        ps.setLong(psOrdinal, to);
+                    }
+
+                    return new JDBCCursor<S>(JDBCStorage.this, con, ps);
+                } catch (Exception e) {
+                    // in case of exception, close statement
+                    try {
+                        ps.close();
+                    } catch (SQLException e2) {
+                        // ignore and allow triggering exception to propagate
+                    }
+                    throw e;
+                }
+            } catch (Exception e) {
+                // in case of exception, yield connection
+                try {
+                    mRepository.yieldConnection(con);
+                } catch (FetchException e2) {
+                   // ignore and allow triggering exception to propagate
+                }
+                throw mRepository.toFetchException(e);
+            }
+        }
+
         @Override
         public long count(FilterValues<S> values) throws FetchException {
             Connection con = mRepository.getConnection();
@@ -758,13 +852,16 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
             return b.toString();
         }
 
-        private void setParameters(PreparedStatement ps, FilterValues<S> filterValues)
+        /**
+         * @return next value ordinal
+         */
+        private int setParameters(PreparedStatement ps, FilterValues<S> filterValues)
             throws Exception
         {
             PropertyFilter<S>[] propertyFilters = mPropertyFilters;
 
             if (propertyFilters == null) {
-                return;
+                return 1;
             }
 
             boolean[] propertyFilterNullable = mPropertyFilterNullable;
@@ -799,6 +896,8 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
 
                 ordinal++;
             }
+
+            return psOrdinal;
         }
     }
 
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java
index 3643c65..f3b0db5 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java
@@ -287,4 +287,39 @@ class JDBCSupportStrategy {
     void setTruncateTableStatement(String truncateTableStatement) {
         mTruncateTableStatement = truncateTableStatement;
     }
+
+    /**
+     * @since 1.2
+     */
+    enum SliceOption {
+        // Slice is always emulated
+        NOT_SUPPORTED,
+        // Slice is emulated if from is not zero
+        LIMIT_ONLY,
+        // Slice is emulated if to is not null
+        OFFSET_ONLY,
+        // Slice is fully supported with limit parameter first
+        LIMIT_AND_OFFSET,
+        // Slice is fully supported with offset parameter first
+        OFFSET_AND_LIMIT,
+    }
+
+    /**
+     * @since 1.2
+     */
+    SliceOption getSliceOption() {
+        return SliceOption.NOT_SUPPORTED;
+    }
+
+    /**
+     * @param select base select statement
+     * @param limit when true, select must support limit parameter
+     * @param offset when true, select must support offset parameter
+     * @return revised select statement
+     * @throws UnsupportedOperationException
+     * @since 1.2
+     */
+    String buildSelectWithSlice(String select, boolean limit, boolean offset) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java
index 27f813f..bf15ec6 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java
@@ -32,7 +32,28 @@ class MysqlSupportStrategy extends JDBCSupportStrategy {
         setTruncateTableStatement(TRUNCATE_STATEMENT);
     }
 
+    @Override
     JDBCExceptionTransformer createExceptionTransformer() {
         return new MysqlExceptionTransformer();
     }
+
+    @Override
+    SliceOption getSliceOption() {
+        return SliceOption.OFFSET_AND_LIMIT;
+    }
+
+    @Override
+    String buildSelectWithSlice(String select, boolean limit, boolean offset) {
+        if (limit) {
+            if (offset) {
+                return select.concat(" LIMIT ?,?");
+            } else {
+                return select.concat(" LIMIT ?");
+            }
+        } else if (offset) {
+            return select.concat(" LIMIT ?,18446744073709551615");
+        } else {
+            return select;
+        }
+    }
 }
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java
index 27ca7c5..582b299 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java
@@ -220,6 +220,28 @@ class OracleSupportStrategy extends JDBCSupportStrategy {
         }
     }
 
+    @Override
+    SliceOption getSliceOption() {
+        return SliceOption.OFFSET_AND_LIMIT;
+    }
+
+    @Override
+    String buildSelectWithSlice(String select, boolean limit, boolean offset) {
+        if (limit) {
+            if (offset) {
+                return "SELECT * FROM (SELECT ROW_.*, ROWNUM ROWNUM_ FROM(" +
+                    select + ") ROW_) WHERE ROWNUM_ > ? AND ROWNUM_ <= ?";
+            } else {
+                return "SELECT * FROM (" + select + ") WHERE ROWNUM <= ?";
+            }
+        } else if (offset) {
+            return "SELECT * FROM (SELECT ROW_.*, ROWNUM ROWNUM_ FROM(" +
+                select + ") ROW_) WHERE ROWNUM_ > ?";
+        } else {
+            return select;
+        }
+    }
+
     /* FIXME
     @Override
     boolean printPlan(Appendable app, int indentLevel, String statement)
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/PostgresqlSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/PostgresqlSupportStrategy.java
new file mode 100644
index 0000000..43d35c2
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/PostgresqlSupportStrategy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * 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 com.amazon.carbonado.repo.jdbc;
+
+/**
+ * 
+ *
+ * @author Brian S O'Neill
+ * @since 1.2
+ */
+class PostgresqlSupportStrategy extends JDBCSupportStrategy {
+    private static final String TRUNCATE_STATEMENT = "TRUNCATE TABLE %s";
+
+    protected PostgresqlSupportStrategy(JDBCRepository repo) {
+        super(repo);
+
+        setTruncateTableStatement(TRUNCATE_STATEMENT);
+    }
+
+    @Override
+    SliceOption getSliceOption() {
+        return SliceOption.LIMIT_AND_OFFSET;
+    }
+
+    @Override
+    String buildSelectWithSlice(String select, boolean limit, boolean offset) {
+        if (limit) {
+            if (offset) {
+                return select.concat(" LIMIT ? OFFSET ?");
+            } else {
+                return select.concat(" LIMIT ?");
+            }
+        } else if (offset) {
+            return select.concat(" LIMIT ALL OFFSET ?");
+        } else {
+            return select;
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingQuery.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingQuery.java
index d1f21ce..56e4e33 100644
--- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingQuery.java
+++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingQuery.java
@@ -137,6 +137,15 @@ class LoggingQuery<S extends Storable> implements Query<S> {
         return mQuery.fetch();
     }
 
+    public Cursor<S> fetch(long from, Long to) throws FetchException {
+        Log log = mStorage.mLog;
+        if (log.isEnabled()) {
+            log.write("Query.fetch(start, to) on " + this +
+                      ", from: " + from + ", to: " + to);
+        }
+        return mQuery.fetch(from, to);
+    }
+
     public Cursor<S> fetchAfter(S start) throws FetchException {
         Log log = mStorage.mLog;
         if (log.isEnabled()) {
@@ -145,6 +154,15 @@ class LoggingQuery<S extends Storable> implements Query<S> {
         return mQuery.fetchAfter(start);
     }
 
+    public Cursor<S> fetchAfter(S start, long from, Long to) throws FetchException {
+        Log log = mStorage.mLog;
+        if (log.isEnabled()) {
+            log.write("Query.fetchAfter(start, from, to) on " + this + ", start: " + start +
+                      ", from: " + from + ", to: " + to);
+        }
+        return mQuery.fetchAfter(start);
+    }
+
     public S loadOne() throws FetchException {
         Log log = mStorage.mLog;
         if (log.isEnabled()) {
-- 
cgit v1.2.3