PrometheusTableDataItem.java
/*
* Prometheus: Application Framework
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.prometheus.database;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
import io.github.tonywasher.joceanus.metis.data.MetisDataState;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionValues;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusIOException;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.ListIterator;
/**
* Database Table class. This controls should be extended for each DataType/Table.
*
* @param <T> the DataType
*/
public abstract class PrometheusTableDataItem<T extends PrometheusDataItem> {
/**
* The Database control.
*/
private final PrometheusDataStore theDatabase;
/**
* The Database connection.
*/
private final Connection theConn;
/**
* The list of items for this table.
*/
private PrometheusDataList<T> theList;
/**
* The prepared statement.
*/
private PreparedStatement theStmt;
/**
* Do we have batched updated in the prepared statement?
*/
private boolean hasBatchedUpdates;
/**
* The result set.
*/
private ResultSet theResults;
/**
* The table definition.
*/
private final PrometheusTableDefinition theTable;
/**
* Constructor.
*
* @param pDatabase the database control
* @param pTable the table name
*/
protected PrometheusTableDataItem(final PrometheusDataStore pDatabase,
final String pTable) {
/* Set the table */
theDatabase = pDatabase;
theConn = theDatabase.getConn();
theTable = new PrometheusTableDefinition(theDatabase.getDriver(), pTable);
}
/**
* Obtain database.
*
* @return the database
*/
protected PrometheusDataStore getDatabase() {
return theDatabase;
}
/**
* Obtain table definition.
*
* @return the table definition
*/
protected PrometheusTableDefinition getTableDef() {
return theTable;
}
/**
* Access the table name.
*
* @return the table name
*/
protected String getTableName() {
return theTable.getTableName();
}
/**
* Access the table definition.
*
* @return the table definition
*/
protected PrometheusTableDefinition getDefinition() {
return theTable;
}
/**
* Close the result set and statement.
*
* @throws SQLException on error
*/
protected void closeStmt() throws SQLException {
executeBatch();
theTable.clearValues();
if (theResults != null) {
theResults.close();
}
if (theStmt != null) {
theStmt.close();
}
}
/**
* Shift to next line in result set.
*
* @return is there a next line
* @throws SQLException on error
*/
private boolean nextLine() throws SQLException {
return theResults.next();
}
/**
* Prepare the statement.
*
* @param pStatement the statement to prepare
* @throws SQLException on error
*/
private void prepareStatement(final String pStatement) throws SQLException {
theStmt = theConn.prepareStatement(pStatement);
}
/**
* Execute the prepared statement.
*
* @throws SQLException on error
*/
private void execute() throws SQLException {
theStmt.executeUpdate();
theTable.clearValues();
}
/**
* Add to the batched statement.
*
* @throws SQLException on error
*/
private void addToBatch() throws SQLException {
theStmt.addBatch();
theTable.clearValues();
hasBatchedUpdates = true;
}
/**
* Execute the batched statement.
*
* @throws SQLException on error
*/
private void executeBatch() throws SQLException {
if (hasBatchedUpdates) {
theStmt.executeBatch();
hasBatchedUpdates = false;
}
}
/**
* Query the prepared statement.
*
* @throws SQLException on error
*/
private void executeQuery() throws SQLException {
theTable.clearValues();
theResults = theStmt.executeQuery();
}
/**
* Commit the update.
*
* @throws SQLException on error
*/
private void commit() throws SQLException {
theConn.commit();
}
/**
* Execute a statement.
*
* @param pStatement the statement
* @throws SQLException on error
*/
private void executeStatement(final String pStatement) throws SQLException {
/* Prepare the statement */
prepareStatement(pStatement);
/* Execute the delete */
execute();
commit();
/* Close the Statement */
closeStmt();
}
/**
* Count the number of items to be loaded.
*
* @return the count of items
* @throws SQLException on error
*/
protected int countLoadItems() throws SQLException {
int myCount = 0;
final String myString = theTable.getCountString();
prepareStatement(myString);
executeQuery();
/* Loop through the results */
while (theResults.next()) {
/* Get the count */
myCount = theResults.getInt(1);
}
/* Close the Statement */
closeStmt();
/* Return the count */
return myCount;
}
/**
* Declare DataSet.
*
* @param pData the Data set
*/
protected abstract void declareData(PrometheusDataSet pData);
/**
* Set the list of items.
*
* @param pList the list of items
*/
protected void setList(final PrometheusDataList<T> pList) {
theList = pList;
}
/**
* Obtain the list of items.
*
* @return the list of items
*/
protected PrometheusDataList<T> getList() {
return theList;
}
/**
* Load an individual item from the result set.
*
* @return the values for the row
* @throws OceanusException on error
*/
protected abstract PrometheusDataValues loadValues() throws OceanusException;
/**
* Set a field value for an item.
*
* @param pItem the item to insert
* @param pField the field id
* @throws OceanusException on error
*/
protected void setFieldValue(final T pItem,
final MetisDataFieldId pField) throws OceanusException {
/* Switch on field id */
if (pField.equals(MetisDataResource.DATA_ID)) {
theTable.setIntegerValue(MetisDataResource.DATA_ID, pItem.getIndexedId());
}
}
/**
* Post-Process on a load operation.
*
* @throws OceanusException on error
*/
protected void postProcessOnLoad() throws OceanusException {
/* PostProcess the list */
theList.postProcessOnLoad();
}
/**
* Load items from the list into the table.
*
* @param pReport the report
* @param pData the data
* @throws OceanusException on error
*/
protected void loadItems(final TethysUIThreadStatusReport pReport,
final PrometheusDataSet pData) throws OceanusException {
/* Declare the new stage */
pReport.setNewStage(getTableName());
/* Declare the Data */
declareData(pData);
/* Protect the load */
try {
/* Count the Items to be loaded */
pReport.setNumSteps(countLoadItems());
/* Load the items from the table */
final String myQuery = theTable.getLoadString();
prepareStatement(myQuery);
executeQuery();
/* Loop through the results */
while (nextLine()) {
/* Read in the results */
theTable.loadResults(theResults);
/* Load the next item */
theList.addValuesItem(loadValues());
/* Report the progress */
pReport.setNextStep();
}
/* Close the Statement */
closeStmt();
/* Perform post process */
postProcessOnLoad();
} catch (SQLException e) {
throw new PrometheusIOException("Failed to load " + getTableName(), e);
}
}
/**
* Determine the count of items that are in a particular state.
*
* @param pState the particular state
* @return the count of items
*/
private int countStateItems(final MetisDataState pState) {
/* Initialise the count */
int iCount = 0;
/* Loop through the list */
final Iterator<T> myIterator = theList.iterator();
while (myIterator.hasNext()) {
final T myCurr = myIterator.next();
/* Ignore items that are not this type */
if (myCurr.getState() != pState) {
continue;
}
/* Increment count */
++iCount;
}
/* Return count */
return iCount;
}
/**
* Insert new items from the list.
*
* @param pReport the report
* @param pData the data
* @param pBatch the batch control
* @throws OceanusException on error
*/
protected void insertItems(final TethysUIThreadStatusReport pReport,
final PrometheusDataSet pData,
final PrometheusBatchControl pBatch) throws OceanusException {
/* Declare the new stage */
pReport.setNewStage("Inserting " + getTableName());
/* Declare the Data */
declareData(pData);
/* Protect the insert */
T myCurr = null;
try {
/* Declare the number of steps */
final int mySteps = countStateItems(MetisDataState.NEW);
pReport.setNumSteps(mySteps);
if (mySteps == 0) {
return;
}
/* Declare the table and mode */
pBatch.setCurrentTable(this, MetisDataState.NEW);
/* Prepare the insert statement */
final String myInsert = theTable.getInsertString();
prepareStatement(myInsert);
/* Loop through the list */
final Iterator<T> myIterator = theList.iterator();
while (myIterator.hasNext()) {
/* Ignore non-new items */
myCurr = myIterator.next();
if (myCurr.getState() != MetisDataState.NEW) {
continue;
}
/* Loop through the columns */
for (PrometheusColumnDefinition myCol : theTable.getColumns()) {
/* Access the column id */
final MetisDataFieldId iField = myCol.getColumnId();
/* Set the field value */
setFieldValue(myCurr, iField);
}
/* Apply the values */
theTable.insertValues(theStmt);
pBatch.addBatchItem();
/* Add to the statement batch */
addToBatch();
myCurr = null;
/* If we have no further space in the batch */
if (pBatch.isFull()) {
/* ExecuteBatch and commit the database */
executeBatch();
commit();
/* Commit the batch */
pBatch.commitItems();
}
/* Report the progress */
pReport.setNextStep();
}
/* Close the Statement */
closeStmt();
} catch (SQLException e) {
throw new PrometheusDataException(myCurr, "Failed to insert " + getTableName(), e);
}
}
/**
* Update items from the list.
*
* @param pReport the report
* @param pBatch the batch control
* @throws OceanusException on error
*/
protected void updateItems(final TethysUIThreadStatusReport pReport,
final PrometheusBatchControl pBatch) throws OceanusException {
/* Declare the new stage */
pReport.setNewStage("Updating " + getTableName());
/* Protect the update */
T myCurr = null;
try {
/* Declare the number of steps */
final int mySteps = countStateItems(MetisDataState.CHANGED);
pReport.setNumSteps(mySteps);
if (mySteps == 0) {
return;
}
/* Declare the table and mode */
pBatch.setCurrentTable(this, MetisDataState.CHANGED);
/* Loop through the list */
final Iterator<T> myIterator = theList.iterator();
while (myIterator.hasNext()) {
/* Ignore non-changed items */
myCurr = myIterator.next();
if ((myCurr.getState() != MetisDataState.CHANGED)
|| !updateItem(myCurr)) {
continue;
}
/* Record the id and access the update string */
theTable.setIntegerValue(MetisDataResource.DATA_ID, myCurr.getIndexedId());
final String myUpdate = theTable.getUpdateString();
/* Prepare the statement and declare values */
prepareStatement(myUpdate);
theTable.updateValues(theStmt);
pBatch.addBatchItem();
/* Execute the update */
execute();
myCurr = null;
/* Close the Statement */
closeStmt();
/* If we have no further space in the batch */
if (pBatch.isFull()) {
/* Commit the database */
commit();
/* Commit the batch */
pBatch.commitItems();
}
/* Report the progress */
pReport.setNextStep();
}
} catch (SQLException e) {
throw new PrometheusDataException(myCurr, "Failed to update " + getTableName(), e);
}
}
/**
* Update the item.
*
* @param pItem the item
* @return Continue <code>true/false</code>
* @throws OceanusException on error
*/
private boolean updateItem(final T pItem) throws OceanusException {
/* Access the object and base */
final MetisFieldVersionValues myCurr = pItem.getValues();
final MetisFieldVersionValues myBase = pItem.getOriginalValues();
boolean isUpdated = false;
/* Loop through the fields */
for (PrometheusColumnDefinition myCol : theTable.getColumns()) {
/* Skip null and Id columns */
if (myCol == null) {
continue;
}
/* Access the column id */
final MetisDataFieldId iField = myCol.getColumnId();
/* If the non-Id field has changed */
if (!MetisDataResource.DATA_ID.equals(myCol.getColumnId())
&& myCurr.fieldChanged(iField, myBase).isDifferent()) {
/* Record the change */
isUpdated = true;
setFieldValue(pItem, iField);
}
}
/* Return to caller */
return isUpdated;
}
/**
* Delete items from the list.
*
* @param pReport the report
* @param pBatch the batch control
* @throws OceanusException on error
*/
protected void deleteItems(final TethysUIThreadStatusReport pReport,
final PrometheusBatchControl pBatch) throws OceanusException {
/* Declare the new stage */
pReport.setNewStage("Deleting " + getTableName());
/* Protect the delete */
T myCurr = null;
try {
/* Declare the number of steps */
int mySteps = countStateItems(MetisDataState.DELETED);
mySteps += countStateItems(MetisDataState.DELNEW);
pReport.setNumSteps(mySteps);
if (mySteps == 0) {
return;
}
/* Declare the table and mode */
pBatch.setCurrentTable(this, MetisDataState.DELETED);
/* Prepare the delete statement */
final String myDelete = theTable.getDeleteString();
prepareStatement(myDelete);
/* Access the iterator */
final ListIterator<T> myIterator = theList.listIterator(theList.size());
/* Loop through the list in reverse order */
while (myIterator.hasPrevious()) {
/* Ignore non-deleted items */
myCurr = myIterator.previous();
final MetisDataState myState = myCurr.getState();
if ((myState != MetisDataState.DELETED)
&& (myState != MetisDataState.DELNEW)) {
continue;
}
/* Declare the item in the batch */
pBatch.addBatchItem();
/* Ignore DelNew items as far as the database is concerned */
if (myState != MetisDataState.DELNEW) {
/* Apply the id */
theTable.setIntegerValue(MetisDataResource.DATA_ID, myCurr.getIndexedId());
theTable.updateValues(theStmt);
/* Add to the statement batch */
addToBatch();
}
/* If we have no further space in the batch */
if (pBatch.isFull()) {
/* ExecuteBatch and commit the database */
executeBatch();
commit();
/* Commit the batch */
pBatch.commitItems();
}
/* Report the progress */
pReport.setNextStep();
}
/* Close the Statement */
closeStmt();
} catch (SQLException e) {
throw new PrometheusDataException(myCurr, "Failed to delete " + getTableName(), e);
}
}
/**
* Create the table.
*
* @throws OceanusException on error
*/
protected void createTable() throws OceanusException {
/* Protect the create */
try {
/* Execute the create index statement */
String myCreate = theTable.getCreateTableString();
executeStatement(myCreate);
/* If the table has an index */
if (theTable.isIndexed()) {
/* Prepare the create index statement */
myCreate = theTable.getCreateIndexString();
executeStatement(myCreate);
}
} catch (SQLException e) {
throw new PrometheusIOException("Failed to create " + getTableName(), e);
}
}
/**
* Create the database.
*
* @param pDatabase the database name
* @throws OceanusException on error
*/
void createDatabase(final String pDatabase) throws OceanusException {
/* Protect the statements */
try {
/* Execute the drop and create database statements */
theConn.setAutoCommit(true);
final String myDrop = "DROP DATABASE IF EXISTS " + pDatabase;
prepareStatement(myDrop);
theStmt.execute();
theStmt.close();
final String myCreate = "CREATE DATABASE " + pDatabase;
prepareStatement(myCreate);
theStmt.execute();
theStmt.close();
theConn.setAutoCommit(false);
} catch (SQLException e) {
throw new PrometheusIOException("Failed to create database", e);
}
}
/**
* Drop the table.
*
* @throws OceanusException on error
*/
protected void dropTable() throws OceanusException {
/* Protect the drop */
try {
/* If we should drop the index */
String myDrop = theTable.getDropIndexString();
if (myDrop != null) {
/* Execute the drop index statement */
executeStatement(myDrop);
}
/* Execute the drop table statement */
myDrop = theTable.getDropTableString();
executeStatement(myDrop);
} catch (SQLException e) {
throw new PrometheusIOException("Failed to drop " + getTableName(), e);
}
}
/**
* Truncate the table.
*
* @throws OceanusException on error
*/
protected void purgeTable() throws OceanusException {
/* Protect the truncate */
try {
/* Execute the purge statement */
final String myTrunc = theTable.getPurgeString();
executeStatement(myTrunc);
} catch (SQLException e) {
throw new PrometheusIOException("Failed to purge " + getTableName(), e);
}
}
/**
* Obtain row values.
*
* @param pName the name of the item
* @return the row values.
* @throws OceanusException on error
*/
protected PrometheusDataValues getRowValues(final String pName) throws OceanusException {
/* Allocate the values */
final PrometheusDataValues myValues = new PrometheusDataValues(pName);
/* Add the id and return the new values */
myValues.addValue(MetisDataResource.DATA_ID, theTable.getIntegerValue(MetisDataResource.DATA_ID));
return myValues;
}
}