Change the way sort clause is added for primary key when no sort clause is requested.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / database / ListDBAccess.java
1 /*
2  * Copyright (C) 2011 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.server.database;
21
22 import java.sql.Connection;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Statement;
26 import java.util.ArrayList;
27
28 import org.glom.libglom.Document;
29 import org.glom.libglom.Field;
30 import org.glom.libglom.LayoutFieldVector;
31 import org.glom.libglom.LayoutItem_Field;
32 import org.glom.libglom.SortClause;
33 import org.glom.libglom.SortFieldPair;
34 import org.glom.web.server.Log;
35 import org.glom.web.server.Utils;
36 import org.glom.web.shared.GlomField;
37
38 import com.mchange.v2.c3p0.ComboPooledDataSource;
39
40 /**
41  * @author Ben Konrath <ben@bagu.org>
42  * 
43  */
44 public abstract class ListDBAccess extends DBAccess {
45         protected LayoutFieldVector fieldsToGet;
46
47         protected ListDBAccess(Document document, String documentID, ComboPooledDataSource cpds, String tableName) {
48                 super(document, documentID, cpds, tableName);
49         }
50
51         public abstract int getExpectedResultSize();
52
53         protected abstract String getSelectQuery(SortClause sortClause);
54
55         protected abstract String getCountQuery();
56
57         protected ArrayList<GlomField[]> getListData(int start, int length, boolean useSortClause, int sortColumnIndex,
58                         boolean isAscending) {
59
60                 // Add a LayoutItem_Field for the primary key to the end of the LayoutFieldVector if it doesn't already contain
61                 // a primary key.
62                 if (getPrimaryKeyIndex() < 0) {
63                         fieldsToGet.add(getPrimaryKeyLayoutItemField());
64                 }
65
66                 // create a sort clause for the column we've been asked to sort
67                 SortClause sortClause = new SortClause();
68                 if (useSortClause) {
69                         org.glom.libglom.LayoutItem item = fieldsToGet.get(sortColumnIndex);
70                         LayoutItem_Field layoutItemField = LayoutItem_Field.cast_dynamic(item);
71                         if (layoutItemField != null)
72                                 sortClause.addLast(new SortFieldPair(layoutItemField, isAscending));
73                         else {
74                                 Log.error(documentID, tableName, "Error getting LayoutItem_Field for column index " + sortColumnIndex
75                                                 + ". Cannot create a sort clause for this column.");
76                         }
77                 } else {
78                         // create a sort clause for the primary key if we're not asked to sort a specific column
79                         int numItems = Utils.safeLongToInt(fieldsToGet.size());
80                         for (int i = 0; i < numItems; i++) {
81                                 LayoutItem_Field layoutItem = fieldsToGet.get(i);
82                                 Field details = layoutItem.get_full_field_details();
83                                 if (details != null && details.get_primary_key()) {
84                                         sortClause.addLast(new SortFieldPair(layoutItem, true)); // ascending
85                                         break;
86                                 }
87                         }
88                 }
89
90                 ArrayList<GlomField[]> rowsList = new ArrayList<GlomField[]>();
91                 Connection conn = null;
92                 Statement st = null;
93                 ResultSet rs = null;
94                 try {
95                         // Setup the JDBC driver and get the query. Special care needs to be take to ensure that the results will be
96                         // based on a cursor so that large amounts of memory are not consumed when the query retrieve a large amount
97                         // of data. Here's the relevant PostgreSQL documentation:
98                         // http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor
99                         conn = cpds.getConnection();
100                         conn.setAutoCommit(false);
101                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
102                         st.setFetchSize(length);
103                         String query = getSelectQuery(sortClause) + " OFFSET " + start;
104                         // TODO Test memory usage before and after we execute the query that would result in a large ResultSet.
105                         // We need to ensure that the JDBC driver is in fact returning a cursor based result set that has a low
106                         // memory footprint. Check the difference between this value before and after the query:
107                         // Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
108                         // Test the execution time at the same time (see the todo item in getLayoutListTable()).
109                         rs = st.executeQuery(query);
110
111                         // get the results from the ResultSet
112                         rowsList = convertResultSetToDTO(length, fieldsToGet, rs);
113                 } catch (SQLException e) {
114                         Log.error(documentID, tableName, "Error executing database query.", e);
115                         // TODO: somehow notify user of problem
116                 } finally {
117                         // cleanup everything that has been used
118                         try {
119                                 if (rs != null)
120                                         rs.close();
121                                 if (st != null)
122                                         st.close();
123                                 if (conn != null)
124                                         conn.close();
125                         } catch (Exception e) {
126                                 Log.error(documentID, tableName,
127                                                 "Error closing database resources. Subsequent database queries may not work.", e);
128                         }
129                 }
130                 return rowsList;
131         }
132
133         /*
134          * Get the number of rows a query with the table name and layout fields would return. This is needed for the /* list
135          * view pager.
136          */
137         protected int getResultSizeOfSQLQuery() {
138
139                 // Add a LayoutItem_Field for the primary key to the end of the LayoutFieldVector if it doesn't already contain
140                 // a primary key.
141                 if (getPrimaryKeyIndex() < 0) {
142                         fieldsToGet.add(getPrimaryKeyLayoutItemField());
143                 }
144
145                 Connection conn = null;
146                 Statement st = null;
147                 ResultSet rs = null;
148                 try {
149                         // Setup and execute the count query. Special care needs to be take to ensure that the results will be based
150                         // on a cursor so that large amounts of memory are not consumed when the query retrieve a large amount of
151                         // data. Here's the relevant PostgreSQL documentation:
152                         // http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor
153                         conn = cpds.getConnection();
154                         conn.setAutoCommit(false);
155                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
156                         String query = getCountQuery();
157                         // TODO Test execution time of this query with when the number of rows in the table is large (say >
158                         // 1,000,000). Test memory usage at the same time (see the todo item in getTableData()).
159                         rs = st.executeQuery(query);
160
161                         // get the number of rows in the query
162                         rs.next();
163                         return rs.getInt(1);
164
165                 } catch (SQLException e) {
166                         Log.error(documentID, tableName, "Error calculating number of rows in the query.", e);
167                         return -1;
168                 } finally {
169                         // cleanup everything that has been used
170                         try {
171                                 if (rs != null)
172                                         rs.close();
173                                 if (st != null)
174                                         st.close();
175                                 if (conn != null)
176                                         conn.close();
177                         } catch (Exception e) {
178                                 Log.error(documentID, tableName,
179                                                 "Error closing database resources. Subsequent database queries may not work.", e);
180                         }
181                 }
182         }
183
184         /**
185          * Gets the primary key index of this list layout.
186          * 
187          * @return index of primary key or -1 if a primary key was not found
188          */
189         public int getPrimaryKeyIndex() {
190                 for (int i = 0; i < fieldsToGet.size(); i++) {
191                         LayoutItem_Field layoutItemField = fieldsToGet.get(i);
192                         Field field = layoutItemField.get_full_field_details();
193                         if (field != null && field.get_primary_key())
194                                 return i;
195                 }
196                 return -1;
197         }
198 }