1 | /* |
2 | * Copyright 2006-2012 the original author or authors. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | package org.springframework.batch.item.database.support; |
18 | |
19 | import java.util.LinkedHashMap; |
20 | import java.util.Map; |
21 | |
22 | import org.springframework.batch.item.database.Order; |
23 | import org.springframework.util.StringUtils; |
24 | |
25 | /** |
26 | * Generic Paging Query Provider using standard SQL:2003 windowing functions. |
27 | * These features are supported by DB2, Oracle, SQL Server 2005, Sybase and |
28 | * Apache Derby version 10.4.1.3 |
29 | * |
30 | * @author Thomas Risberg |
31 | * @author Michael Minella |
32 | * @since 2.0 |
33 | */ |
34 | public class SqlWindowingPagingQueryProvider extends AbstractSqlPagingQueryProvider { |
35 | |
36 | @Override |
37 | public String generateFirstPageQuery(int pageSize) { |
38 | StringBuilder sql = new StringBuilder(); |
39 | sql.append("SELECT * FROM ( "); |
40 | sql.append("SELECT ").append(StringUtils.hasText(getOrderedQueryAlias()) ? getOrderedQueryAlias() + ".*, " : "*, "); |
41 | sql.append("ROW_NUMBER() OVER (").append(getOverClause()); |
42 | sql.append(") AS ROW_NUMBER"); |
43 | sql.append(getOverSubstituteClauseStart()); |
44 | sql.append(" FROM ").append(getFromClause()).append( |
45 | getWhereClause() == null ? "" : " WHERE " + getWhereClause()); |
46 | sql.append(getGroupClause() == null ? "" : " GROUP BY " + getGroupClause()); |
47 | sql.append(getOverSubstituteClauseEnd()); |
48 | sql.append(") ").append(getSubQueryAlias()).append("WHERE ").append(extractTableAlias()).append( |
49 | "ROW_NUMBER <= ").append(pageSize); |
50 | sql.append(" ORDER BY ").append(SqlPagingQueryUtils.buildSortClause(this)); |
51 | |
52 | return sql.toString(); |
53 | } |
54 | |
55 | protected String getOrderedQueryAlias() { |
56 | return ""; |
57 | } |
58 | |
59 | protected Object getSubQueryAlias() { |
60 | return "AS TMP_SUB "; |
61 | } |
62 | |
63 | protected Object extractTableAlias() { |
64 | String alias = "" + getSubQueryAlias(); |
65 | if (StringUtils.hasText(alias) && alias.toUpperCase().startsWith("AS")) { |
66 | alias = alias.substring(3).trim() + "."; |
67 | } |
68 | return alias; |
69 | } |
70 | |
71 | @Override |
72 | public String generateRemainingPagesQuery(int pageSize) { |
73 | StringBuilder sql = new StringBuilder(); |
74 | sql.append("SELECT * FROM ( "); |
75 | sql.append("SELECT ").append(StringUtils.hasText(getOrderedQueryAlias()) ? getOrderedQueryAlias() + ".*, " : "*, "); |
76 | sql.append("ROW_NUMBER() OVER (").append(getOverClause()); |
77 | sql.append(") AS ROW_NUMBER"); |
78 | sql.append(getOverSubstituteClauseStart()); |
79 | sql.append(" FROM ").append(getFromClause()); |
80 | if (getWhereClause() != null) { |
81 | sql.append(" WHERE "); |
82 | sql.append(getWhereClause()); |
83 | } |
84 | |
85 | sql.append(getGroupClause() == null ? "" : " GROUP BY " + getGroupClause()); |
86 | sql.append(getOverSubstituteClauseEnd()); |
87 | sql.append(") ").append(getSubQueryAlias()).append("WHERE ").append(extractTableAlias()).append( |
88 | "ROW_NUMBER <= ").append(pageSize); |
89 | sql.append(" AND "); |
90 | SqlPagingQueryUtils.buildSortConditions(this, sql); |
91 | sql.append(" ORDER BY ").append(SqlPagingQueryUtils.buildSortClause(this)); |
92 | |
93 | return sql.toString(); |
94 | } |
95 | |
96 | @Override |
97 | public String generateJumpToItemQuery(int itemIndex, int pageSize) { |
98 | int page = itemIndex / pageSize; |
99 | int lastRowNum = (page * pageSize); |
100 | if (lastRowNum <= 0) { |
101 | lastRowNum = 1; |
102 | } |
103 | |
104 | StringBuilder sql = new StringBuilder(); |
105 | sql.append("SELECT "); |
106 | buildSortKeySelect(sql, getSortKeysReplaced(extractTableAlias())); |
107 | sql.append(" FROM ( "); |
108 | sql.append("SELECT "); |
109 | buildSortKeySelect(sql); |
110 | sql.append(", ROW_NUMBER() OVER (").append(getOverClause()); |
111 | sql.append(") AS ROW_NUMBER"); |
112 | sql.append(getOverSubstituteClauseStart()); |
113 | sql.append(" FROM ").append(getFromClause()); |
114 | sql.append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); |
115 | sql.append(getGroupClause() == null ? "" : " GROUP BY " + getGroupClause()); |
116 | sql.append(getOverSubstituteClauseEnd()); |
117 | sql.append(") ").append(getSubQueryAlias()).append("WHERE ").append(extractTableAlias()).append( |
118 | "ROW_NUMBER = ").append(lastRowNum); |
119 | sql.append(" ORDER BY ").append(SqlPagingQueryUtils.buildSortClause(getSortKeysReplaced(extractTableAlias()))); |
120 | |
121 | return sql.toString(); |
122 | } |
123 | |
124 | private Map<String, Order> getSortKeysReplaced(Object qualifierReplacement) { |
125 | final String newQualifier = "" + qualifierReplacement; |
126 | final Map<String, Order> sortKeys = new LinkedHashMap<String, Order>(); |
127 | for (Map.Entry<String, Order> sortKey : getSortKeys().entrySet()) { |
128 | sortKeys.put(sortKey.getKey().replaceFirst("^.*\\.", newQualifier), sortKey.getValue()); |
129 | } |
130 | return sortKeys; |
131 | } |
132 | |
133 | private void buildSortKeySelect(StringBuilder sql) { |
134 | buildSortKeySelect(sql, null); |
135 | } |
136 | |
137 | private void buildSortKeySelect(StringBuilder sql, Map<String, Order> sortKeys) { |
138 | String prefix = ""; |
139 | if (sortKeys == null) { |
140 | sortKeys = getSortKeys(); |
141 | } |
142 | for (Map.Entry<String, Order> sortKey : sortKeys.entrySet()) { |
143 | sql.append(prefix); |
144 | prefix = ", "; |
145 | sql.append(sortKey.getKey()); |
146 | } |
147 | } |
148 | |
149 | protected String getOverClause() { |
150 | StringBuilder sql = new StringBuilder(); |
151 | |
152 | sql.append(" ORDER BY ").append(SqlPagingQueryUtils.buildSortClause(this)); |
153 | |
154 | return sql.toString(); |
155 | } |
156 | |
157 | protected String getOverSubstituteClauseStart() { |
158 | return ""; |
159 | } |
160 | |
161 | protected String getOverSubstituteClauseEnd() { |
162 | return ""; |
163 | } |
164 | } |