001package org.hl7.fhir.r5.testfactory.dataprovider;
002
003import java.io.IOException;
004import java.sql.Connection;
005import java.sql.ResultSet;
006import java.sql.ResultSetMetaData;
007import java.sql.SQLException;
008import java.sql.Statement;
009import java.util.ArrayList;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013
014import org.hl7.fhir.exceptions.FHIRException;
015import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
016
017/**
018 * Concrete implementation of TableDataProvider that reads data from a SQL database table.
019 */
020@MarkedToMoveToAdjunctPackage
021public class SQLDataProvider extends TableDataProvider {
022
023  private Connection connection;
024  private String tableName;
025  private List<String> columnHeaders;
026  private ResultSet resultSet;
027  private ResultSetMetaData metaData;
028  private Map<String, Integer> columnIndexMap = new HashMap<>();
029  private int counter;
030
031  /**
032   * Constructs an SQLDataProvider.
033   *
034   * @param connection The SQL database connection.
035   * @param tableName  The name of the table to read.
036   * @throws IOException If a database access error occurs.
037   */
038  public SQLDataProvider(Connection connection, String tableName) throws IOException {
039    this.connection = connection;
040    this.tableName = tableName;
041
042    reset();
043  }
044
045  /**
046   * Loads the column headers from the table's metadata.
047   *
048   * @throws IOException If a database access error occurs.
049   */
050  private void loadColumnHeaders() throws IOException {
051    try (Statement statement = connection.createStatement();
052        ResultSet rs = statement.executeQuery("SELECT * FROM " + tableName + " WHERE 1=0")) {
053      metaData = rs.getMetaData();
054      columnHeaders = new ArrayList<>();
055      columnHeaders.add("counter");
056      for (int i = 1; i <= metaData.getColumnCount(); i++) {
057        String columnName = metaData.getColumnName(i);
058        columnHeaders.add(columnName);
059        columnIndexMap.put(columnName, i);
060      }
061    } catch (SQLException e) {
062      throw new FHIRException("Error loading column headers: " + e.getMessage(), e);
063    }
064  }
065
066  /**
067   * Prepares the ResultSet for iterating over the table's rows.
068   *
069   * @throws IOException If a database access error occurs.
070   */
071  private void prepareResultSet() throws IOException {
072    try {
073      Statement statement = connection.createStatement();
074      resultSet = statement.executeQuery("SELECT * FROM " + tableName);
075    } catch (SQLException e) {
076      throw new IOException("Error preparing result set: " + e.getMessage(), e);
077    }
078  }
079
080  @Override
081  public List<String> columns() {
082    return columnHeaders;
083  }
084
085  @Override
086  public boolean nextRow() throws FHIRException {
087    try {
088      counter++;
089      return resultSet.next();
090    } catch (SQLException e) {
091      throw new FHIRException("Error moving to next row: " + e.getMessage(), e);
092    }
093  }
094
095  @Override
096  public List<String> cells() throws FHIRException {
097    try {
098      List<String> cellValues = new ArrayList<>();
099      cellValues.add(""+counter);
100      for (int i = 1; i <= metaData.getColumnCount(); i++) {
101        cellValues.add(resultSet.getString(i));
102      }
103      return cellValues;
104    } catch (SQLException e) {
105      throw new FHIRException("Error retrieving row cells: " + e.getMessage(), e);
106    }
107  }
108
109  @Override
110  public String cell(String name) throws FHIRException {
111    if ("counter".equals(name)) {
112      return ""+counter;      
113    } else {
114      try {
115        Integer columnIndex = columnIndexMap.get(name);
116        if (columnIndex == null) {
117          return null;
118        }
119        return resultSet.getString(columnIndex);
120      } catch (SQLException e) {
121        throw new FHIRException("Error retrieving cell value: " + e.getMessage(), e);
122      }
123    }
124  }
125
126  /**
127   * Closes the ResultSet and releases the database resources.
128   *
129   * @throws IOException If a database access error occurs.
130   */
131  public void close() throws IOException {
132    try {
133      if (resultSet != null) {
134        resultSet.close();
135      }
136      if (connection != null) {
137        connection.close();
138      }
139    } catch (SQLException e) {
140      throw new IOException("Error closing resources: " + e.getMessage(), e);
141    }
142  }
143
144  @Override
145  public void reset() throws FHIRException {
146    try {
147      loadColumnHeaders();
148      prepareResultSet();
149    } catch (Exception e) {
150      throw new FHIRException("Error closing resources: " + e.getMessage(), e);
151    }
152  }
153}