diff --git a/docs/src/main/asciidoc/spanner.adoc b/docs/src/main/asciidoc/spanner.adoc
index 492cb7a06a4..b05e67c8042 100644
--- a/docs/src/main/asciidoc/spanner.adoc
+++ b/docs/src/main/asciidoc/spanner.adoc
@@ -1170,13 +1170,14 @@ The SQL query for the method can be mapped to repository methods in one of two w
The names of the tags of the SQL correspond to the `@Param` annotated names of the method parameters.
Interleaved properties are loaded eagerly, unless they are annotated with `@Interleaved(lazy = true)`.
+For a custom SQL query without a `Sort` or `Pageable` parameter, eager interleaved properties are loaded after the parent query so that the query's `ORDER BY`, `LIMIT`, and `OFFSET` clauses retain their Cloud Spanner semantics.
+This may execute additional queries to load the interleaved properties.
Custom SQL query methods can accept a single `Sort` or `Pageable` parameter that is applied on top of the specified custom query.
-It is the recommended way to control the sort order of the results, which is not guaranteed by the `ORDER BY` clause in the SQL query.
-This is due to the fact that the user-provided query is used as a sub-query, and Cloud Spanner doesn't preserve order in subquery results.
+When supplied, it is the recommended way to control the final sort order because it is applied to the outer query.
You might want to use `ORDER BY` with `LIMIT` to obtain the top records, according to a specified order.
-However, to ensure the correct sort order of the final result set, sort options have to be passed in with a `Pageable`.
+If a `Pageable` parameter is also used, include the desired sort options in the `Pageable`.
[source, java]
----
diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/query/SpannerStatementQueryExecutor.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/query/SpannerStatementQueryExecutor.java
index b4d38bd3837..45ac47203eb 100644
--- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/query/SpannerStatementQueryExecutor.java
+++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/query/SpannerStatementQueryExecutor.java
@@ -172,6 +172,7 @@ public static List executeQuery(
* eager-Interleaved lists with a single query. Please note, it doesn't make sense to pass it
* as {@code true} when the {@code sql} already contains a complete lists of all
* eager-Interleaved properties generated by the method {@link #getColumnsStringForSelect}.
+ * This option is ignored when no sorting or paging option requires wrapping the query.
* @param the domain type.
* @return the final SQL string with paging and sorting applied.
* @see #getColumnsStringForSelect
@@ -182,13 +183,12 @@ public static String applySortingPagingQueryOptions(
String sql,
SpannerMappingContext mappingContext,
boolean fetchInterleaved) {
- // Cloud Spanner does not preserve the order of derived tables so we must not wrap the
- // derived table
- // in SELECT * FROM () if there is no overriding pageable param.
+ // Cloud Spanner does not preserve the order of derived tables, so do not wrap a custom query
+ // solely to fetch interleaved properties. SpannerTemplate resolves properties that are absent
+ // from the result after mapping, which preserves ORDER BY, LIMIT, and OFFSET in the custom SQL.
if ((options.getSort() == null || options.getSort().isUnsorted())
&& options.getLimit() == null
- && options.getOffset() == null
- && !fetchInterleaved) {
+ && options.getOffset() == null) {
return sql;
}
diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java
index 79e952362ba..b38e93015cb 100644
--- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java
+++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java
@@ -156,15 +156,9 @@ private SqlSpannerQuery createQuery(
void noPageableParamQueryTest(boolean useValueExpressionDelegate) throws NoSuchMethodException {
String sql =
"SELECT DISTINCT * FROM "
- + ":com.google.cloud.spring.data.spanner.repository.query.SqlSpannerQueryTests$Trade:";
- // @formatter:off
- String entityResolvedSql =
- "SELECT *, ARRAY (SELECT AS STRUCT disabled, id, childId, value, ARRAY (SELECT AS STRUCT"
- + " canceled, documentId, id, childId, content FROM documents WHERE (documents.id ="
- + " children.id AND documents.childId = children.childId) AND (canceled = false)) AS"
- + " documents FROM children WHERE (children.id = trades.id) AND (disabled = false)) AS"
- + " children FROM (SELECT DISTINCT * FROM trades) trades";
- // @formatter:on
+ + ":com.google.cloud.spring.data.spanner.repository.query.SqlSpannerQueryTests$Trade:"
+ + " ORDER BY id DESC LIMIT 3";
+ String entityResolvedSql = "SELECT DISTINCT * FROM trades ORDER BY id DESC LIMIT 3";
final Class toReturn = Trade.class;
when(queryMethod.isCollectionQuery()).thenReturn(false);