001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.bean;
018
019import static org.apache.juneau.common.utils.IOUtils.*;
020
021import java.sql.*;
022import java.util.*;
023
024import org.apache.juneau.internal.*;
025
026/**
027 * Transforms an SQL {@link ResultSet ResultSet} into a list of maps.
028 * <p>
029 * Loads the entire result set into an in-memory data structure, and then closes the result set object.
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032
033 * </ul>
034 *
035 * @serial exclude
036 */
037public class ResultSetList extends LinkedList<Map<String,Object>> {
038
039   private static final long serialVersionUID = 1L;
040
041   /**
042    * Constructor.
043    *
044    * @param rs The result set to load into this DTO.
045    * @param pos The start position (zero-indexed).
046    * @param limit The maximum number of rows to retrieve.
047    * @param includeRowNums Make the first column be the row number.
048    * @throws SQLException Database error.
049    */
050   public ResultSetList(ResultSet rs, int pos, int limit, boolean includeRowNums) throws SQLException {
051      try {
052         var rowNum = pos;
053
054         // Get the column names.
055         var rsmd = rs.getMetaData();
056         var offset = (includeRowNums ? 1 : 0);
057         var cc = rsmd.getColumnCount();
058         var columns = new String[cc + offset];
059         if (includeRowNums)
060            columns[0] = "ROW";
061         var colTypes = new int[cc];
062
063         for (var i = 0; i < cc; i++) {
064            columns[i+offset] = rsmd.getColumnName(i+1);
065            colTypes[i] = rsmd.getColumnType(i+1);
066         }
067
068         while (--pos > 0 && rs.next()) { /* Skip to the specified position. */ }
069
070         // Get the rows.
071         while (limit-- > 0 && rs.next()) {
072            var row = new Object[cc + offset];
073            if (includeRowNums)
074               row[0] = rowNum++;
075            for (var i = 0; i < cc; i++) {
076               var o = readEntry(rs, i+1, colTypes[i]);
077               row[i+offset] = o;
078            }
079            add(new SimpleMap<>(columns, row));
080         }
081      } finally {
082         rs.close();
083      }
084   }
085
086   /**
087    * Reads the specified column from the current row in the result set.
088    *
089    * <p>
090    * Subclasses can override this method to handle specific data types in special ways.
091    *
092    * @param rs The result set to read from.
093    * @param col The column number (indexed by 1).
094    * @param dataType The {@link Types type} of the entry.
095    * @return The entry as an Object.
096    */
097   static Object readEntry(ResultSet rs, int col, int dataType) {
098      try {
099         return switch (dataType) {
100            case Types.BLOB -> {
101               var b = rs.getBlob(col);
102               yield "blob[" + b.length() + "]";
103            }
104            case Types.CLOB -> {
105               var c = rs.getClob(col);
106               yield "clob[" + c.length() + "]";
107            }
108            case Types.LONGVARBINARY -> "longvarbinary[" + count(rs.getBinaryStream(col)) + "]";
109            case Types.LONGVARCHAR -> "longvarchar[" + count(rs.getAsciiStream(col)) + "]";
110            case Types.LONGNVARCHAR -> "longnvarchar[" + count(rs.getCharacterStream(col)) + "]";
111            case Types.TIMESTAMP -> rs.getTimestamp(col); // Oracle returns com.oracle.TIMESTAMP objects from getObject()
112            default -> rs.getObject(col); };
113      } catch (Exception e) {
114         return e.getLocalizedMessage();
115      }
116   }
117}