results persistance

This commit is contained in:
Mehul Ahal 2023-01-12 10:54:33 +01:00 committed by LahaLuhem
parent b35ffac6fa
commit c7e9824208
10 changed files with 166 additions and 52 deletions

View file

@ -1,11 +1,12 @@
import data.dtos.QueryDTO; import data.dtos.QueryDTO;
import data.enums.CustomPreparedStatementsRead; import data.enums.CustomPreparedStatementsRead;
import data.models.QueryResultsModel; import data.repos.QueryResultsRepository;
import services.AppConfigService; import services.AppConfigService;
import services.DatabaseService; import services.DatabaseService;
import services.ReportGenerationService;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
@ -18,13 +19,33 @@ class App {
public App() { public App() {
databaseService = new DatabaseService(); databaseService = new DatabaseService();
appConfigService = new AppConfigService(); appConfigService = new AppConfigService();
reportGenerationService = new ReportGenerationService();
} }
final DatabaseService databaseService; final private DatabaseService databaseService;
final AppConfigService appConfigService; final private AppConfigService appConfigService;
final private ReportGenerationService reportGenerationService;
private static final CustomPreparedStatementsRead[] reportEndpoints = {
CustomPreparedStatementsRead.HighestAndLowest10Vaccination,
CustomPreparedStatementsRead.HighestInfectionsPer100K,
//CustomPreparedStatementsRead.CountriesLaggingBehind,
};
private static final CustomPreparedStatementsRead[] queryEndpoints = {
CustomPreparedStatementsRead.DailyInfectionsAndDeathAggregate,
};
public void run() { public void run() {
//appConfigService.promptUserArguments(); //appConfigService.promptUserArguments();
final QueryResultsModel resultSetsQuery1 = databaseService.executeReadReportsEndpoint(new QueryDTO(CustomPreparedStatementsRead.HighestAndLowest10Vaccination)); try {
QueryResultsRepository resultsRepository;
for (final CustomPreparedStatementsRead query : reportEndpoints) {
resultsRepository = databaseService.executeReadReportsEndpoint(new QueryDTO(query));
reportGenerationService.reportResults(resultsRepository);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
} }
} }

View file

@ -1,20 +1,42 @@
package apis; package apis;
import data.dtos.QueryDTO; import data.dtos.QueryDTO;
import org.postgresql.util.PSQLException;
import java.sql.*; import java.sql.*;
import java.util.Date;
import java.util.MissingFormatArgumentException;
/**
* Communicates with the database to perform final CRUD operations, returning any results.
* When there is a need for more than a single READ action, the Api can be abstracted, and implement into constituent action-specific APIs.
*/
public class DatabaseApi { public class DatabaseApi {
public PreparedStatement performReadQuery(QueryDTO queryDTO, Connection connection) throws SQLException { public PreparedStatement performReadQuery(QueryDTO queryDTO, Connection connection) throws SQLException {
// Set template // Set template
PreparedStatement statement = connection.prepareStatement(queryDTO.statement().statementTemplate); PreparedStatement statement = connection.prepareStatement(queryDTO.statement().statementTemplate);
// Add in arguments try {
for(int argIndex = 0; argIndex < queryDTO.templateArgs().length; argIndex++) // Add in arguments
// parameter indexing starts at 1 for(int argIndex = 0; argIndex < queryDTO.templateArgs().length; argIndex++) {
statement.setString(argIndex + 1, queryDTO.templateArgs()[argIndex].toString()); final Object curentArg = queryDTO.templateArgs()[argIndex];
// parameter indexing starts at 1
if (curentArg instanceof Date)
statement.setDate(argIndex + 1, new java.sql.Date(((Date) curentArg).getTime()));
else
statement.setString(argIndex + 1, curentArg.toString());
}
statement.execute();
} catch (PSQLException e) {
if (e.getMessage().startsWith("No value specified for parameter")) throw new MissingFormatArgumentException("The templates present in the query, were not correctly mapped to their replacement variables.");
else if (e.getMessage().startsWith("The column index is out of range:")) throw new MissingFormatArgumentException("No replacement variables needed, but were they were still supplied.");
throw e;
}
statement.execute();
return statement; return statement;
} }
} }

View file

@ -2,6 +2,11 @@ package data.dtos;
import data.enums.CustomPreparedStatementsRead; import data.enums.CustomPreparedStatementsRead;
/**
* Container for storing SQL statements to be executed, along with their aruments.
* @param statement
* @param templateArgs
*/
public record QueryDTO(CustomPreparedStatementsRead statement, Object[] templateArgs) { public record QueryDTO(CustomPreparedStatementsRead statement, Object[] templateArgs) {
public QueryDTO(CustomPreparedStatementsRead customPreparedStatementsRead) { public QueryDTO(CustomPreparedStatementsRead customPreparedStatementsRead) {
this(customPreparedStatementsRead, new Object[]{}); this(customPreparedStatementsRead, new Object[]{});

View file

@ -7,15 +7,17 @@ package data.enums;
public enum CustomPreparedStatementsRead { public enum CustomPreparedStatementsRead {
DailyInfectionsAndDeathAggregate(""" DailyInfectionsAndDeathAggregate("""
WITH matched_countries AS ( WITH matched_countries AS (
SELECT c.name, c.code\s SELECT c.name, c.code
FROM countries c FROM countries c
WHERE c.name LIKE CONCAT('%',?, '%') WHERE c.name LIKE CONCAT('%',?, '%')
) )
SELECT recorded_date, infections, deaths SELECT recorded_date, infections, deaths
FROM cases FROM cases
WHERE iso_country IN (SELECT code FROM matched_countries) AND recorded_date <= ? WHERE iso_country IN (SELECT code FROM matched_countries) AND recorded_date <= ?
ORDER BY recorded_date; ORDER BY recorded_date
""" ;
""",
"10 countries with the highest vaccinations (with count), and countries with lowest number of vaccinations."
), ),
HighestAndLowest10Vaccination(""" HighestAndLowest10Vaccination("""
@ -42,7 +44,8 @@ public enum CustomPreparedStatementsRead {
ORDER BY total_vaccinations ASC ORDER BY total_vaccinations ASC
LIMIT 10 LIMIT 10
; ;
""" """,
"10 countries with the highest infections per 100k inhabitants (with count)"
), ),
HighestInfectionsPer100K(""" HighestInfectionsPer100K("""
@ -53,7 +56,8 @@ public enum CustomPreparedStatementsRead {
ORDER BY infections_per_100k DESC ORDER BY infections_per_100k DESC
LIMIT 10 LIMIT 10
; ;
""" """,
""
), ),
CountriesLaggingBehind(""" CountriesLaggingBehind("""
@ -63,12 +67,15 @@ public enum CustomPreparedStatementsRead {
GROUP BY c.name GROUP BY c.name
HAVING SUM(v.daily_vaccinations) < c.population HAVING SUM(v.daily_vaccinations) < c.population
; ;
""" """,
""
); );
public final String statementTemplate; public final String statementTemplate;
public final String description;
private CustomPreparedStatementsRead(String statementTemplate) { private CustomPreparedStatementsRead(String statementTemplate, String description) {
this.statementTemplate = statementTemplate; this.statementTemplate = statementTemplate;
this.description = description;
} }
} }

View file

@ -0,0 +1,41 @@
package data.models;
import java.sql.Array;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
public class PersistentResultModel {
/**
* Factory constructor, that adapts a given ResultSet to PersistentResultModel.
* @param resultSet
* @throws SQLException
*/
public PersistentResultModel (ResultSet resultSet) throws SQLException {
final ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
final int columnCount = resultSetMetaData.getColumnCount();
final ArrayList<Object[]> grwingResultsGrid = new ArrayList<>();
while (resultSet.next()) {
final Object[] currentRow = new Object[columnCount];
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
currentRow[columnIndex] = resultSet.getObject(columnIndex+1);
}
grwingResultsGrid.add(currentRow);
}
resultColumns = new ResultColumnModel[columnCount];
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
int finalColumnIndex = columnIndex;
final var entries = grwingResultsGrid.stream().map(row -> row[finalColumnIndex]).toList().toArray();
resultColumns[columnIndex] = new ResultColumnModel(resultSetMetaData.getColumnName(columnIndex+1), entries);
}
}
final public ResultColumnModel[] resultColumns;
public record ResultColumnModel(String columnName, Object[] entries){}
}

View file

@ -1,11 +0,0 @@
package data.models;
import java.sql.ResultSet;
/**
* Stores the results obtained from executing an SQL query.
* Essentially a type-def, which is not natively supported in Java.
* @param resultSets Result of the query
*/
public record QueryResultsModel(java.util.ArrayList<ResultSet> resultSets) {
}

View file

@ -0,0 +1,28 @@
package data.repos;
import data.models.PersistentResultModel;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
/**
* Stores the results obtained from executing an SQL query.
*/
public class QueryResultsRepository {
/**
* Store for the results
*/
final private ArrayList<PersistentResultModel> persistentResults = new ArrayList<>();
public PersistentResultModel[] getResults () {return persistentResults.toArray(new PersistentResultModel[]{});}
/**
* Adds a ResultSet to the store to persist.
* @param resultSet
* @throws SQLException
*/
public void addResult(ResultSet resultSet) throws SQLException {
persistentResults.add(new PersistentResultModel(resultSet));
}
}

View file

@ -12,8 +12,8 @@ import java.util.Scanner;
* Prompts and stores the configuration that the app needs to be run with. * Prompts and stores the configuration that the app needs to be run with.
*/ */
public class AppConfigService { public class AppConfigService {
String countryName; public String countryName;
Date date; public Date date;
public void promptUserArguments() { public void promptUserArguments() {
final Scanner scanner = new Scanner(System.in); final Scanner scanner = new Scanner(System.in);

View file

@ -2,14 +2,12 @@ package services;
import apis.DatabaseApi; import apis.DatabaseApi;
import data.dtos.QueryDTO; import data.dtos.QueryDTO;
import data.enums.CustomPreparedStatementsRead; import data.repos.QueryResultsRepository;
import data.models.QueryResultsModel;
import java.sql.*; import java.sql.*;
import java.util.ArrayList;
/** /**
* Stores the active connection between the app and the local SQL Server. * Stores the active connection between the app and the SQL Server.
*/ */
public class DatabaseService { public class DatabaseService {
private static final String url = "jdbc:postgresql://localhost:5432/lunatech_covid"; private static final String url = "jdbc:postgresql://localhost:5432/lunatech_covid";
@ -20,25 +18,27 @@ public class DatabaseService {
private final Connection connection; private final Connection connection;
public DatabaseService() { public DatabaseService() {
connection = connect(); connection = getConnection();
databaseApi = new DatabaseApi(); databaseApi = new DatabaseApi();
} }
public QueryResultsModel executeReadReportsEndpoint(QueryDTO queryDTO) { /**
try { * Fetches read-query results and packages them to persist them (and so free the connection).
final PreparedStatement resultStatement = databaseApi.performReadQuery(queryDTO, connection); *
* @param queryDTO The query with its arguments.
* @return Persisting (non-lazy) results from executing the query.
*/
public QueryResultsRepository executeReadReportsEndpoint(QueryDTO queryDTO) throws SQLException {
final PreparedStatement resultStatement = databaseApi.performReadQuery(queryDTO, connection);
final ArrayList<ResultSet> resultSets = new ArrayList<>(); final QueryResultsRepository resultSets = new QueryResultsRepository();
do { do {
// execute and persist results in a model final ResultSet resultSet = resultStatement.getResultSet();
resultSets.add(resultStatement.getResultSet()); resultSets.addResult(resultSet);
}
while (resultStatement.getMoreResults());
return new QueryResultsModel(resultSets);
} catch (SQLException e) {
throw new RuntimeException(e);
} }
while (resultStatement.getMoreResults());
return resultSets;
} }
/** /**
@ -46,13 +46,14 @@ public class DatabaseService {
* *
* @return a Connection object * @return a Connection object
*/ */
private Connection connect() { private Connection getConnection() {
Connection connection = null; Connection connection = null;
try { try {
connection = DriverManager.getConnection(url, user, password); connection = DriverManager.getConnection(url, user, password);
System.out.println("Connected to the PostgreSQL server successfully."); System.out.println("Connected to the PostgreSQL server successfully.");
} catch (SQLException e) { } catch (SQLException e) {
System.out.println(e.getMessage()); System.out.println("Unable to connect to PostgreSQL server.");
throw new RuntimeException();
} }
return connection; return connection;

View file

@ -1,9 +1,9 @@
package services; package services;
import data.models.QueryResultsModel; import data.repos.QueryResultsRepository;
public class ReportGenerationService { public class ReportGenerationService {
public static void printResults(QueryResultsModel queryResultsModel) { public void reportResults(QueryResultsRepository queryResultsRepository) {
//queryResultsModel. //queryResultsModel.
} }
} }