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.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import java.sql.*;
023import java.util.*;
024
025import org.apache.juneau.commons.collections.*;
026
027/**
028 * Transforms an SQL {@link ResultSet ResultSet} into a list of maps.
029 * <p>
030 * Loads the entire result set into an in-memory data structure, and then closes the result set object.
031 *
032 *
033 * @serial exclude
034 */
035public class ResultSetList extends LinkedList<Map<String,Object>> {
036
037   private static final long serialVersionUID = 1L;
038
039   /**
040    * Reads the specified column from the current row in the result set.
041    *
042    * <p>
043    * Subclasses can override this method to handle specific data types in special ways.
044    *
045    * @param rs The result set to read from.
046    * @param col The column number (indexed by 1).
047    * @param dataType The {@link Types type} of the entry.
048    * @return The entry as an Object.
049    */
050   static Object readEntry(ResultSet rs, int col, int dataType) {
051      try {
052         return switch (dataType) {
053            case Types.BLOB -> {
054               var b = rs.getBlob(col);
055               yield "blob[" + b.length() + "]";
056            }
057            case Types.CLOB -> {
058               var c = rs.getClob(col);
059               yield "clob[" + c.length() + "]";
060            }
061            case Types.LONGVARBINARY -> "longvarbinary[" + count(rs.getBinaryStream(col)) + "]";
062            case Types.LONGVARCHAR -> "longvarchar[" + count(rs.getAsciiStream(col)) + "]";
063            case Types.LONGNVARCHAR -> "longnvarchar[" + count(rs.getCharacterStream(col)) + "]";
064            case Types.TIMESTAMP -> rs.getTimestamp(col); // Oracle returns com.oracle.TIMESTAMP objects from getObject()
065            default -> rs.getObject(col);
066         };
067      } catch (Exception e) {
068         return lm(e);
069      }
070   }
071
072   /**
073    * Constructor.
074    *
075    * @param rs The result set to load into this DTO.
076    * @param pos The start position (zero-indexed).
077    * @param limit The maximum number of rows to retrieve.
078    * @param includeRowNums Make the first column be the row number.
079    * @throws SQLException Database error.
080    */
081   public ResultSetList(ResultSet rs, int pos, int limit, boolean includeRowNums) throws SQLException {
082      try {
083         var rowNum = pos;
084
085         // Get the column names.
086         var rsmd = rs.getMetaData();
087         var offset = (includeRowNums ? 1 : 0);
088         var cc = rsmd.getColumnCount();
089         var columns = new String[cc + offset];
090         if (includeRowNums)
091            columns[0] = "ROW";
092         var colTypes = new int[cc];
093
094         for (var i = 0; i < cc; i++) {
095            columns[i + offset] = rsmd.getColumnName(i + 1);
096            colTypes[i] = rsmd.getColumnType(i + 1);
097         }
098
099         while (--pos > 0 && rs.next()) { /* Skip to the specified position. */ }
100
101         // Get the rows.
102         while (limit-- > 0 && rs.next()) {
103            var row = new Object[cc + offset];
104            if (includeRowNums)
105               row[0] = rowNum++;
106            for (var i = 0; i < cc; i++) {
107               var o = readEntry(rs, i + 1, colTypes[i]);
108               row[i + offset] = o;
109            }
110            add(new SimpleMap<>(columns, row));
111         }
112      } finally {
113         rs.close();
114      }
115   }
116}