View Javadoc
1   /*
2    * Prometheus: Application Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.prometheus.database;
18  
19  import io.github.tonywasher.joceanus.gordianknot.util.GordianUtilities;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
22  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
23  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
24  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
25  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
26  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
27  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
28  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
29  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusBinaryColumn;
30  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusBooleanColumn;
31  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusDateColumn;
32  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusIdColumn;
33  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusIntegerColumn;
34  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusLongColumn;
35  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusMoneyColumn;
36  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusPriceColumn;
37  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusRateColumn;
38  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusRatioColumn;
39  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusReferenceColumn;
40  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusSortOrder;
41  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusStringColumn;
42  import io.github.tonywasher.joceanus.prometheus.database.PrometheusColumnDefinition.PrometheusUnitsColumn;
43  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusLogicException;
44  
45  import java.sql.PreparedStatement;
46  import java.sql.ResultSet;
47  import java.sql.SQLException;
48  import java.util.ArrayList;
49  import java.util.HashMap;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Map;
53  
54  /**
55   * Database field definition class. Maps each dataType to a database field.
56   */
57  public class PrometheusTableDefinition
58          implements PrometheusTableControl {
59      /**
60       * The index prefix.
61       */
62      private static final String PREFIX_INDEX = "idx_";
63  
64      /**
65       * The quote string.
66       */
67      protected static final String QUOTE_STRING = "\"";
68  
69      /**
70       * The Descending string.
71       */
72      protected static final String STR_DESC = " DESC";
73  
74      /**
75       * The Buffer length.
76       */
77      private static final int BUFFER_LEN = 1000;
78  
79      /**
80       * The Table name.
81       */
82      private final String theTableName;
83  
84      /**
85       * The Database driver.
86       */
87      private final PrometheusJDBCDriver theDriver;
88  
89      /**
90       * The Column Definitions.
91       */
92      private final List<PrometheusColumnDefinition> theList;
93  
94      /**
95       * The Sort List.
96       */
97      private final List<PrometheusColumnControl> theSortList;
98  
99      /**
100      * Are we sorting on a reference column.
101      */
102     private boolean sortOnReference;
103 
104     /**
105      * The array list for the columns.
106      */
107     private final Map<MetisDataFieldId, PrometheusColumnControl> theMap;
108 
109     /**
110      * The prepared statement for the insert/update.
111      */
112     private PreparedStatement theStatement;
113 
114     /**
115      * Constructor.
116      *
117      * @param pDriver the driver
118      * @param pName   the table name
119      */
120     protected PrometheusTableDefinition(final PrometheusJDBCDriver pDriver,
121                                         final String pName) {
122         /* Record the name and driver */
123         theTableName = pName;
124         theDriver = pDriver;
125 
126         /* Create the column list */
127         theList = new ArrayList<>();
128 
129         /* Create the sort list */
130         theSortList = new ArrayList<>();
131 
132         /* Create the initial column map */
133         theMap = new HashMap<>();
134 
135         /* Add an Id column */
136         theList.add(new PrometheusIdColumn(this));
137     }
138 
139     /**
140      * Obtain the table name.
141      *
142      * @return the table name
143      */
144     protected String getTableName() {
145         return theTableName;
146     }
147 
148     @Override
149     public Map<MetisDataFieldId, PrometheusColumnControl> getMap() {
150         return theMap;
151     }
152 
153     /**
154      * Is the table indexed.
155      *
156      * @return true/false
157      */
158     protected boolean isIndexed() {
159         return !theSortList.isEmpty();
160     }
161 
162     /**
163      * Column Definitions array.
164      *
165      * @return the columns
166      */
167     protected List<PrometheusColumnDefinition> getColumns() {
168         return theList;
169     }
170 
171     @Override
172     public List<PrometheusColumnControl> getSortList() {
173         return theSortList;
174     }
175 
176     @Override
177     public PrometheusJDBCDriver getDriver() {
178         return theDriver;
179     }
180 
181     @Override
182     public void setSortOnReference() {
183         sortOnReference = true;
184     }
185 
186     /**
187      * Add a reference column.
188      *
189      * @param pId  the column id
190      * @param pRef the reference table
191      * @return the reference column
192      */
193     public PrometheusReferenceColumn addReferenceColumn(final MetisDataFieldId pId,
194                                                         final String pRef) {
195         /* Create the new reference column */
196         final PrometheusReferenceColumn myColumn = new PrometheusReferenceColumn(this, pId, pRef);
197 
198         /* Add it to the list and return it */
199         theList.add(myColumn);
200         return myColumn;
201     }
202 
203     /**
204      * Add a reference column, which can be null.
205      *
206      * @param pId  the column id
207      * @param pRef the reference table
208      * @return the reference column
209      */
210     public PrometheusReferenceColumn addNullReferenceColumn(final MetisDataFieldId pId,
211                                                             final String pRef) {
212         final PrometheusReferenceColumn myColumn = addReferenceColumn(pId, pRef);
213         myColumn.setNullable();
214         return myColumn;
215     }
216 
217     /**
218      * Add an integer column.
219      *
220      * @param pId the column id
221      * @return the integer column
222      */
223     public PrometheusIntegerColumn addIntegerColumn(final MetisDataFieldId pId) {
224         /* Create the new integer column */
225         final PrometheusIntegerColumn myColumn = new PrometheusIntegerColumn(this, pId);
226 
227         /* Add it to the list and return it */
228         theList.add(myColumn);
229         return myColumn;
230     }
231 
232     /**
233      * Add an integer column, which can be null.
234      *
235      * @param pId the column id
236      * @return the integer column
237      */
238     public PrometheusIntegerColumn addNullIntegerColumn(final MetisDataFieldId pId) {
239         final PrometheusIntegerColumn myColumn = addIntegerColumn(pId);
240         myColumn.setNullable();
241         return myColumn;
242     }
243 
244     /**
245      * Add a long column.
246      *
247      * @param pId the column id
248      * @return the long column
249      */
250     public PrometheusLongColumn addLongColumn(final MetisDataFieldId pId) {
251         /* Create the new long column */
252         final PrometheusLongColumn myColumn = new PrometheusLongColumn(this, pId);
253 
254         /* Add it to the list and return it */
255         theList.add(myColumn);
256         return myColumn;
257     }
258 
259     /**
260      * Add a long column, which can be null.
261      *
262      * @param pId the column id
263      * @return the long column
264      */
265     public PrometheusLongColumn addNullLongColumn(final MetisDataFieldId pId) {
266         final PrometheusLongColumn myColumn = addLongColumn(pId);
267         myColumn.setNullable();
268         return myColumn;
269     }
270 
271     /**
272      * Add a boolean column.
273      *
274      * @param pId the column id
275      * @return the boolean column
276      */
277     public PrometheusBooleanColumn addBooleanColumn(final MetisDataFieldId pId) {
278         /* Create the new boolean column */
279         final PrometheusBooleanColumn myColumn = new PrometheusBooleanColumn(this, pId);
280 
281         /* Add it to the list and return it */
282         theList.add(myColumn);
283         return myColumn;
284     }
285 
286     /**
287      * Add a boolean column, which can be null.
288      *
289      * @param pId the column id
290      * @return the boolean column
291      */
292     public PrometheusBooleanColumn addNullBooleanColumn(final MetisDataFieldId pId) {
293         final PrometheusBooleanColumn myColumn = addBooleanColumn(pId);
294         myColumn.setNullable();
295         return myColumn;
296     }
297 
298     /**
299      * Add a date column.
300      *
301      * @param pId the column id
302      * @return the date column
303      */
304     public PrometheusDateColumn addDateColumn(final MetisDataFieldId pId) {
305         /* Create the new date column */
306         final PrometheusDateColumn myColumn = new PrometheusDateColumn(this, pId);
307 
308         /* Add it to the list and return it */
309         theList.add(myColumn);
310         return myColumn;
311     }
312 
313     /**
314      * Add a date column, which can be null.
315      *
316      * @param pId the column id
317      * @return the date column
318      */
319     public PrometheusDateColumn addNullDateColumn(final MetisDataFieldId pId) {
320         final PrometheusDateColumn myColumn = addDateColumn(pId);
321         myColumn.setNullable();
322         return myColumn;
323     }
324 
325     /**
326      * Add a money column.
327      *
328      * @param pId the column id
329      * @return the money column
330      */
331     public PrometheusMoneyColumn addMoneyColumn(final MetisDataFieldId pId) {
332         /* Create the new money column */
333         final PrometheusMoneyColumn myColumn = new PrometheusMoneyColumn(this, pId);
334 
335         /* Add it to the list and return it */
336         theList.add(myColumn);
337         return myColumn;
338     }
339 
340     /**
341      * Add a money column, which can be null.
342      *
343      * @param pId the column id
344      * @return the money column
345      */
346     public PrometheusMoneyColumn addNullMoneyColumn(final MetisDataFieldId pId) {
347         final PrometheusMoneyColumn myColumn = addMoneyColumn(pId);
348         myColumn.setNullable();
349         return myColumn;
350     }
351 
352     /**
353      * Add a rate column.
354      *
355      * @param pId the column id
356      * @return the rate column
357      */
358     public PrometheusRateColumn addRateColumn(final MetisDataFieldId pId) {
359         /* Create the new rate column */
360         final PrometheusRateColumn myColumn = new PrometheusRateColumn(this, pId);
361 
362         /* Add it to the list and return it */
363         theList.add(myColumn);
364         return myColumn;
365     }
366 
367     /**
368      * Add a rate column, which can be null.
369      *
370      * @param pId the column id
371      * @return the rate column
372      */
373     public PrometheusRateColumn addNullRateColumn(final MetisDataFieldId pId) {
374         final PrometheusRateColumn myColumn = addRateColumn(pId);
375         myColumn.setNullable();
376         return myColumn;
377     }
378 
379     /**
380      * Add a rate column.
381      *
382      * @param pId the column id
383      * @return the rate column
384      */
385     public PrometheusRatioColumn addRatioColumn(final MetisDataFieldId pId) {
386         /* Create the new rate column */
387         final PrometheusRatioColumn myColumn = new PrometheusRatioColumn(this, pId);
388 
389         /* Add it to the list and return it */
390         theList.add(myColumn);
391         return myColumn;
392     }
393 
394     /**
395      * Add a rate column, which can be null.
396      *
397      * @param pId the column id
398      * @return the rate column
399      */
400     public PrometheusRatioColumn addNullRatioColumn(final MetisDataFieldId pId) {
401         final PrometheusRatioColumn myColumn = addRatioColumn(pId);
402         myColumn.setNullable();
403         return myColumn;
404     }
405 
406     /**
407      * Add a binary column.
408      *
409      * @param pId     the column id
410      * @param pLength the underlying (character) length
411      * @return the binary column
412      */
413     public PrometheusBinaryColumn addBinaryColumn(final MetisDataFieldId pId,
414                                                   final int pLength) {
415         /* Create the new binary column */
416         final PrometheusBinaryColumn myColumn = new PrometheusBinaryColumn(this, pId, pLength);
417 
418         /* Add it to the list and return it */
419         theList.add(myColumn);
420         return myColumn;
421     }
422 
423     /**
424      * Add a binary column, which can be null.
425      *
426      * @param pId     the column id
427      * @param pLength the underlying (character) length
428      * @return the binary column
429      */
430     public PrometheusBinaryColumn addNullBinaryColumn(final MetisDataFieldId pId,
431                                                       final int pLength) {
432         final PrometheusBinaryColumn myColumn = addBinaryColumn(pId, pLength);
433         myColumn.setNullable();
434         return myColumn;
435     }
436 
437     /**
438      * Add an encrypted column.
439      *
440      * @param pId     the column id
441      * @param pLength the underlying (character) length
442      * @return the binary column
443      */
444     public PrometheusBinaryColumn addEncryptedColumn(final MetisDataFieldId pId,
445                                                      final int pLength) {
446         /* Create the new binary column */
447         final PrometheusBinaryColumn myColumn = new PrometheusBinaryColumn(this, pId, GordianUtilities.getKeySetEncryptionLength(pLength));
448 
449         /* Add it to the list and return it */
450         theList.add(myColumn);
451         return myColumn;
452     }
453 
454     /**
455      * Add an encrypted column, which can be null.
456      *
457      * @param pId     the column id
458      * @param pLength the underlying (character) length
459      * @return the binary column
460      */
461     public PrometheusBinaryColumn addNullEncryptedColumn(final MetisDataFieldId pId,
462                                                          final int pLength) {
463         final PrometheusBinaryColumn myColumn = addEncryptedColumn(pId, pLength);
464         myColumn.setNullable();
465         return myColumn;
466     }
467 
468     /**
469      * Add a string column.
470      *
471      * @param pId     the column id
472      * @param pLength the character length
473      * @return the binary column
474      */
475     public PrometheusStringColumn addStringColumn(final MetisDataFieldId pId,
476                                                   final int pLength) {
477         /* Create the new string column */
478         final PrometheusStringColumn myColumn = new PrometheusStringColumn(this, pId, pLength);
479 
480         /* Add it to the list and return it */
481         theList.add(myColumn);
482         return myColumn;
483     }
484 
485     /**
486      * Add a string column, which can be null.
487      *
488      * @param pId     the column id
489      * @param pLength the character length
490      * @return the binary column
491      */
492     public PrometheusStringColumn addNullStringColumn(final MetisDataFieldId pId,
493                                                       final int pLength) {
494         final PrometheusStringColumn myColumn = addStringColumn(pId, pLength);
495         myColumn.setNullable();
496         return myColumn;
497     }
498 
499     /**
500      * Load results.
501      *
502      * @param pResults the result set
503      * @throws SQLException on error
504      */
505     protected void loadResults(final ResultSet pResults) throws SQLException {
506         /* clear values */
507         clearValues();
508 
509         /* Create the iterator */
510         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
511         int myIndex = 1;
512 
513         /* Loop through the columns */
514         while (myIterator.hasNext()) {
515             final PrometheusColumnDefinition myDef = myIterator.next();
516             myDef.loadValue(pResults, myIndex++);
517         }
518     }
519 
520     /**
521      * Build column error string.
522      *
523      * @param pCol the column definition.
524      * @return the error string
525      */
526     private String getColumnError(final PrometheusColumnDefinition pCol) {
527         return "Column " + pCol.getColumnName() + " in table " + theTableName;
528     }
529 
530     /**
531      * Insert values.
532      *
533      * @param pStmt the statement
534      * @throws SQLException     on error
535      * @throws OceanusException on error
536      */
537     protected void insertValues(final PreparedStatement pStmt) throws SQLException, OceanusException {
538         /* Store the Statement */
539         theStatement = pStmt;
540 
541         /* Create the iterator */
542         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
543         int myIndex = 1;
544 
545         /* Loop through the columns */
546         while (myIterator.hasNext()) {
547             final PrometheusColumnDefinition myDef = myIterator.next();
548 
549             /* Reject if the value is not set */
550             if (!myDef.isValueSet()) {
551                 throw new PrometheusLogicException(getColumnError(myDef) + " has no value for insert");
552             }
553 
554             myDef.storeValue(theStatement, myIndex++);
555         }
556     }
557 
558     /**
559      * Update values.
560      *
561      * @param pStmt the statement
562      * @throws SQLException on error
563      */
564     protected void updateValues(final PreparedStatement pStmt) throws SQLException {
565         PrometheusColumnDefinition myId = null;
566 
567         /* Store the Statement */
568         theStatement = pStmt;
569 
570         /* Create the iterator */
571         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
572         int myIndex = 1;
573 
574         /* Loop through the columns */
575         while (myIterator.hasNext()) {
576             final PrometheusColumnDefinition myDef = myIterator.next();
577 
578             /* If this is the Id record */
579             if (myDef instanceof PrometheusIdColumn) {
580                 /* Remember the column */
581                 myId = myDef;
582 
583                 /* Store value if it has been set */
584             } else if (myDef.isValueSet()) {
585                 myDef.storeValue(theStatement, myIndex++);
586             }
587         }
588 
589         /* Store the Id */
590         if (myId != null) {
591             myId.storeValue(theStatement, myIndex);
592         }
593     }
594 
595     /**
596      * Clear values for table.
597      */
598     protected void clearValues() {
599         /* Loop over the Column Definitions */
600         for (PrometheusColumnDefinition myDef : theList) {
601             /* Clear value */
602             myDef.clearValue();
603         }
604     }
605 
606     /**
607      * Get Integer value for column.
608      *
609      * @param pId the column id
610      * @return the integer value
611      * @throws OceanusException on error
612      */
613     public Integer getIntegerValue(final MetisDataFieldId pId) throws OceanusException {
614         /* Obtain the correct id */
615         final PrometheusColumnDefinition myCol = getColumnForId(pId);
616 
617         /* Reject if this is not an integer column */
618         if (!(myCol instanceof PrometheusIntegerColumn myIntCol)) {
619             throw new PrometheusLogicException(getColumnError(myCol) + " is not Integer type");
620         }
621 
622         /* Return the value */
623         return myIntCol.getValue();
624     }
625 
626     /**
627      * Get Long value for column.
628      *
629      * @param pId the column id
630      * @return the long value
631      * @throws OceanusException on error
632      */
633     public Long getLongValue(final MetisDataFieldId pId) throws OceanusException {
634         /* Obtain the correct id */
635         final PrometheusColumnDefinition myCol = getColumnForId(pId);
636 
637         /* Reject if this is not a long column */
638         if (!(myCol instanceof PrometheusLongColumn myLongCol)) {
639             throw new PrometheusLogicException(getColumnError(myCol) + " is not Long type");
640         }
641 
642         /* Return the value */
643         return myLongCol.getValue();
644     }
645 
646     /**
647      * Get Date value for column.
648      *
649      * @param pId the column id
650      * @return the Date value
651      * @throws OceanusException on error
652      */
653     public OceanusDate getDateValue(final MetisDataFieldId pId) throws OceanusException {
654         /* Obtain the correct id */
655         final PrometheusColumnDefinition myCol = getColumnForId(pId);
656 
657         /* Reject if this is not a date column */
658         if (!(myCol instanceof PrometheusDateColumn myDateCol)) {
659             throw new PrometheusLogicException(getColumnError(myCol) + " is not Date type");
660         }
661 
662         /* Return the value */
663         return myDateCol.getValue();
664     }
665 
666     /**
667      * Get Boolean value for column.
668      *
669      * @param pId the column id
670      * @return the boolean value
671      * @throws OceanusException on error
672      */
673     public Boolean getBooleanValue(final MetisDataFieldId pId) throws OceanusException {
674         /* Obtain the correct id */
675         final PrometheusColumnDefinition myCol = getColumnForId(pId);
676 
677         /* Reject if this is not a boolean column */
678         if (!(myCol instanceof PrometheusBooleanColumn myBoolCol)) {
679             throw new PrometheusLogicException("Column " + getColumnError(myCol) + " is not Boolean type");
680         }
681 
682         /* Return the value */
683         return myBoolCol.getValue();
684     }
685 
686     /**
687      * Get String value for column.
688      *
689      * @param pId the column id
690      * @return the String value
691      * @throws OceanusException on error
692      */
693     public String getStringValue(final MetisDataFieldId pId) throws OceanusException {
694         /* Obtain the correct id */
695         final PrometheusColumnDefinition myCol = getColumnForId(pId);
696 
697         /* Reject if this is not a string column */
698         if (!(myCol instanceof PrometheusStringColumn myStringCol)) {
699             throw new PrometheusLogicException(getColumnError(myCol) + " is not String type");
700         }
701 
702         /* Return the value */
703         return myStringCol.getValue();
704     }
705 
706     /**
707      * Get Money value for column.
708      *
709      * @param pId        the column id
710      * @param pFormatter the data formatter
711      * @return the Money value
712      * @throws OceanusException on error
713      */
714     public OceanusMoney getMoneyValue(final MetisDataFieldId pId,
715                                       final OceanusDataFormatter pFormatter) throws OceanusException {
716         /* Obtain the correct id */
717         final PrometheusColumnDefinition myCol = getColumnForId(pId);
718 
719         /* Reject if this is not a money column */
720         if (!(myCol instanceof PrometheusMoneyColumn myMoneyCol)) {
721             throw new PrometheusLogicException(getColumnError(myCol) + " is not money type");
722         }
723 
724         /* Access the value */
725         return myMoneyCol.getValue(pFormatter);
726     }
727 
728     /**
729      * Get Price value for column.
730      *
731      * @param pId        the column id
732      * @param pFormatter the data formatter
733      * @return the price value
734      * @throws OceanusException on error
735      */
736     public OceanusPrice getPriceValue(final MetisDataFieldId pId,
737                                       final OceanusDataFormatter pFormatter) throws OceanusException {
738         /* Obtain the correct id */
739         final PrometheusColumnDefinition myCol = getColumnForId(pId);
740 
741         /* Reject if this is not a price column */
742         if (!(myCol instanceof PrometheusPriceColumn myPriceCol)) {
743             throw new PrometheusLogicException(getColumnError(myCol) + " is not Price type");
744         }
745 
746         /* Access the value */
747         return myPriceCol.getValue(pFormatter);
748     }
749 
750     /**
751      * Get Rate value for column.
752      *
753      * @param pId        the column id
754      * @param pFormatter the data formatter
755      * @return the rate value
756      * @throws OceanusException on error
757      */
758     public OceanusRate getRateValue(final MetisDataFieldId pId,
759                                     final OceanusDataFormatter pFormatter) throws OceanusException {
760         /* Obtain the correct id */
761         final PrometheusColumnDefinition myCol = getColumnForId(pId);
762 
763         /* Reject if this is not a rate column */
764         if (!(myCol instanceof PrometheusRateColumn myRateCol)) {
765             throw new PrometheusLogicException(getColumnError(myCol) + " is not Rate type");
766         }
767 
768         /* Access the value */
769         return myRateCol.getValue(pFormatter);
770     }
771 
772     /**
773      * Get Units value for column.
774      *
775      * @param pId        the column id
776      * @param pFormatter the data formatter
777      * @return the Units value
778      * @throws OceanusException on error
779      */
780     public OceanusUnits getUnitsValue(final MetisDataFieldId pId,
781                                       final OceanusDataFormatter pFormatter) throws OceanusException {
782         /* Obtain the correct id */
783         final PrometheusColumnDefinition myCol = getColumnForId(pId);
784 
785         /* Reject if this is not a units column */
786         if (!(myCol instanceof PrometheusUnitsColumn myUnitsCol)) {
787             throw new PrometheusLogicException(getColumnError(myCol) + " is not Units type");
788         }
789 
790         /* Access the value */
791         return myUnitsCol.getValue(pFormatter);
792     }
793 
794     /**
795      * Get Ratio value for column.
796      *
797      * @param pId        the column id
798      * @param pFormatter the data formatter
799      * @return the Ratio value
800      * @throws OceanusException on error
801      */
802     public OceanusRatio getRatioValue(final MetisDataFieldId pId,
803                                       final OceanusDataFormatter pFormatter) throws OceanusException {
804         /* Obtain the correct id */
805         final PrometheusColumnDefinition myCol = getColumnForId(pId);
806 
807         /* Reject if this is not a ratio column */
808         if (!(myCol instanceof PrometheusRatioColumn myRatioCol)) {
809             throw new PrometheusLogicException(getColumnError(myCol) + " is not Ratio type");
810         }
811 
812         /* Access the value */
813         return myRatioCol.getValue(pFormatter);
814     }
815 
816     /**
817      * Get Binary value for column.
818      *
819      * @param pId the column id
820      * @return the binary value
821      * @throws OceanusException on error
822      */
823     public byte[] getBinaryValue(final MetisDataFieldId pId) throws OceanusException {
824         /* Obtain the correct id */
825         final PrometheusColumnDefinition myCol = getColumnForId(pId);
826 
827         /* Reject if this is not a string column */
828         if (!(myCol instanceof PrometheusBinaryColumn myBinaryCol)) {
829             throw new PrometheusLogicException(getColumnError(myCol) + " is not Binary type");
830         }
831 
832         /* Return the value */
833         return myBinaryCol.getValue();
834     }
835 
836     /**
837      * Set Integer value for column.
838      *
839      * @param pId    the column id
840      * @param pValue the value
841      * @throws OceanusException on error
842      */
843     public void setIntegerValue(final MetisDataFieldId pId,
844                                 final Integer pValue) throws OceanusException {
845         /* Obtain the correct id */
846         final PrometheusColumnDefinition myCol = getColumnForId(pId);
847 
848         /* Reject if this is not an integer column */
849         if (!(myCol instanceof PrometheusIntegerColumn myIntCol)) {
850             throw new PrometheusLogicException(getColumnError(myCol) + " is not Integer type");
851         }
852 
853         /* Set the value */
854         myIntCol.setValue(pValue);
855     }
856 
857     /**
858      * Set Long value for column.
859      *
860      * @param pId    the column id
861      * @param pValue the value
862      * @throws OceanusException on error
863      */
864     public void setLongValue(final MetisDataFieldId pId,
865                              final Long pValue) throws OceanusException {
866         /* Obtain the correct id */
867         final PrometheusColumnDefinition myCol = getColumnForId(pId);
868 
869         /* Reject if this is not a long column */
870         if (!(myCol instanceof PrometheusLongColumn myLongCol)) {
871             throw new PrometheusLogicException(getColumnError(myCol) + " is not Long type");
872         }
873 
874         /* Set the value */
875         myLongCol.setValue(pValue);
876     }
877 
878     /**
879      * Set Boolean value for column.
880      *
881      * @param pId    the column id
882      * @param pValue the value
883      * @throws OceanusException on error
884      */
885     public void setBooleanValue(final MetisDataFieldId pId,
886                                 final Boolean pValue) throws OceanusException {
887         /* Obtain the correct id */
888         final PrometheusColumnDefinition myCol = getColumnForId(pId);
889 
890         /* Reject if this is not a boolean column */
891         if (!(myCol instanceof PrometheusBooleanColumn myBoolCol)) {
892             throw new PrometheusLogicException(getColumnError(myCol) + " is not Boolean type");
893         }
894 
895         /* Set the value */
896         myBoolCol.setValue(pValue);
897     }
898 
899     /**
900      * Set Date value for column.
901      *
902      * @param pId    the column id
903      * @param pValue the value
904      * @throws OceanusException on error
905      */
906     public void setDateValue(final MetisDataFieldId pId,
907                              final OceanusDate pValue) throws OceanusException {
908         /* Obtain the correct id */
909         final PrometheusColumnDefinition myCol = getColumnForId(pId);
910 
911         /* Reject if this is not a Date column */
912         if (!(myCol instanceof PrometheusDateColumn myDateCol)) {
913             throw new PrometheusLogicException(getColumnError(myCol) + " is not Date type");
914         }
915 
916         /* Set the value */
917         myDateCol.setValue(pValue);
918     }
919 
920     /**
921      * Set String value for column.
922      *
923      * @param pId    the column id
924      * @param pValue the value
925      * @throws OceanusException on error
926      */
927     public void setStringValue(final MetisDataFieldId pId,
928                                final String pValue) throws OceanusException {
929         /* Obtain the correct id */
930         final PrometheusColumnDefinition myCol = getColumnForId(pId);
931 
932         /* Reject if this is not a string column */
933         if (!(myCol instanceof PrometheusStringColumn myStringCol)) {
934             throw new PrometheusLogicException(getColumnError(myCol) + " is not String type");
935         }
936 
937         /* Set the value */
938         myStringCol.setValue(pValue);
939     }
940 
941     /**
942      * Set Binary value for column.
943      *
944      * @param pId    the column id
945      * @param pValue the value
946      * @throws OceanusException on error
947      */
948     public void setBinaryValue(final MetisDataFieldId pId,
949                                final byte[] pValue) throws OceanusException {
950         /* Obtain the correct id */
951         final PrometheusColumnDefinition myCol = getColumnForId(pId);
952 
953         /* Reject if this is not a binary column */
954         if (!(myCol instanceof PrometheusBinaryColumn myBinaryCol)) {
955             throw new PrometheusLogicException(getColumnError(myCol) + " is not Binary type");
956         }
957 
958         /* Set the value */
959         myBinaryCol.setValue(pValue);
960     }
961 
962     /**
963      * Set Money value for column.
964      *
965      * @param pId    the column id
966      * @param pValue the value
967      * @throws OceanusException on error
968      */
969     public void setMoneyValue(final MetisDataFieldId pId,
970                               final OceanusMoney pValue) throws OceanusException {
971         /* Obtain the correct id */
972         final PrometheusColumnDefinition myCol = getColumnForId(pId);
973 
974         /* Reject if this is not a money column */
975         if (!(myCol instanceof PrometheusMoneyColumn myMoneyCol)) {
976             throw new PrometheusLogicException(getColumnError(myCol) + " is not Money type");
977         }
978 
979         /* Set the value */
980         myMoneyCol.setValue(pValue);
981     }
982 
983     /**
984      * Set Rate value for column.
985      *
986      * @param pId    the column id
987      * @param pValue the value
988      * @throws OceanusException on error
989      */
990     public void setRateValue(final MetisDataFieldId pId,
991                              final OceanusRate pValue) throws OceanusException {
992         /* Obtain the correct id */
993         final PrometheusColumnDefinition myCol = getColumnForId(pId);
994 
995         /* Reject if this is not a rate column */
996         if (!(myCol instanceof PrometheusRateColumn myRateCol)) {
997             throw new PrometheusLogicException(getColumnError(myCol) + " is not Rate type");
998         }
999 
1000         /* Set the value */
1001         myRateCol.setValue(pValue);
1002     }
1003 
1004     /**
1005      * Set Ratio value for column.
1006      *
1007      * @param pId    the column id
1008      * @param pValue the value
1009      * @throws OceanusException on error
1010      */
1011     public void setRatioValue(final MetisDataFieldId pId,
1012                               final OceanusRatio pValue) throws OceanusException {
1013         /* Obtain the correct id */
1014         final PrometheusColumnDefinition myCol = getColumnForId(pId);
1015 
1016         /* Reject if this is not a ratio column */
1017         if (!(myCol instanceof PrometheusRatioColumn myRatioCol)) {
1018             throw new PrometheusLogicException(getColumnError(myCol) + " is not Ratio type");
1019         }
1020 
1021         /* Set the value */
1022         myRatioCol.setValue(pValue);
1023     }
1024 
1025     /**
1026      * Locate column for id.
1027      *
1028      * @param pId the id of the column
1029      * @return the column
1030      * @throws OceanusException on error
1031      */
1032     private PrometheusColumnDefinition getColumnForId(final MetisDataFieldId pId) throws OceanusException {
1033         /* Access the definition */
1034         final PrometheusColumnDefinition myDef = (PrometheusColumnDefinition) theMap.get(pId);
1035 
1036         /* Check that the id is in range and present */
1037         if (myDef == null) {
1038             throw new PrometheusLogicException("Invalid Column Id: " + pId + " for " + theTableName);
1039         }
1040 
1041         /* Return the column definition */
1042         return myDef;
1043     }
1044 
1045     /**
1046      * Build the create table string for the table.
1047      *
1048      * @return the SQL string
1049      */
1050     protected String getCreateTableString() {
1051         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1052 
1053         /* Build the initial create */
1054         myBuilder.append("create table ");
1055         addQuoteIfAllowed(myBuilder);
1056         myBuilder.append(theTableName);
1057         addQuoteIfAllowed(myBuilder);
1058         myBuilder.append(" (");
1059 
1060         /* Create the iterator */
1061         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
1062         boolean myFirst = true;
1063 
1064         /* Loop through the columns */
1065         while (myIterator.hasNext()) {
1066             final PrometheusColumnDefinition myDef = myIterator.next();
1067             if (!myFirst) {
1068                 myBuilder.append(", ");
1069             }
1070             myDef.buildCreateString(myBuilder);
1071             myFirst = false;
1072         }
1073 
1074         /* Close the statement and return it */
1075         myBuilder.append(')');
1076         return myBuilder.toString();
1077     }
1078 
1079     /**
1080      * Build the create index string for the table.
1081      *
1082      * @return the SQL string
1083      */
1084     protected String getCreateIndexString() {
1085         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1086 
1087         /* Return null if we are not indexed */
1088         if (!isIndexed()) {
1089             return null;
1090         }
1091 
1092         /* Build the initial create */
1093         myBuilder.append("create index ");
1094         addQuoteIfAllowed(myBuilder);
1095         myBuilder.append(PREFIX_INDEX);
1096         myBuilder.append(theTableName);
1097         addQuoteIfAllowed(myBuilder);
1098         myBuilder.append(" on ");
1099         addQuoteIfAllowed(myBuilder);
1100         myBuilder.append(theTableName);
1101         addQuoteIfAllowed(myBuilder);
1102         myBuilder.append(" (");
1103 
1104         /* Create the iterator */
1105         final Iterator<PrometheusColumnControl> myIterator = theSortList.iterator();
1106         boolean myFirst = true;
1107 
1108         /* Loop through the columns */
1109         while (myIterator.hasNext()) {
1110             final PrometheusColumnDefinition myDef = (PrometheusColumnDefinition) myIterator.next();
1111             if (!myFirst) {
1112                 myBuilder.append(", ");
1113             }
1114             addQuoteIfAllowed(myBuilder);
1115             myBuilder.append(myDef.getColumnName());
1116             addQuoteIfAllowed(myBuilder);
1117             if (myDef.getSortOrder() == PrometheusSortOrder.DESCENDING) {
1118                 myBuilder.append(STR_DESC);
1119             }
1120             myFirst = false;
1121         }
1122 
1123         /* Close the statement and return it */
1124         myBuilder.append(')');
1125         return myBuilder.toString();
1126     }
1127 
1128     /**
1129      * Build the drop table string for the table.
1130      *
1131      * @return the SQL string
1132      */
1133     protected String getDropTableString() {
1134         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1135         myBuilder.append("drop table if exists ");
1136         addQuoteIfAllowed(myBuilder);
1137         myBuilder.append(theTableName);
1138         addQuoteIfAllowed(myBuilder);
1139 
1140         /* Return the command */
1141         return myBuilder.toString();
1142     }
1143 
1144     /**
1145      * Build the drop index string for the table.
1146      *
1147      * @return the SQL string
1148      */
1149     protected String getDropIndexString() {
1150         /* Return null if we are not indexed */
1151         if (!isIndexed() || !theDriver.explicitDropIndex()) {
1152             return null;
1153         }
1154 
1155         /* Build the drop command */
1156         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1157         myBuilder.append("drop index if exists ");
1158         addQuoteIfAllowed(myBuilder);
1159         myBuilder.append(PREFIX_INDEX);
1160         myBuilder.append(theTableName);
1161         addQuoteIfAllowed(myBuilder);
1162         if (!PrometheusJDBCDriver.POSTGRESQL.equals(theDriver)) {
1163             myBuilder.append(" on ");
1164             addQuoteIfAllowed(myBuilder);
1165             myBuilder.append(theTableName);
1166             addQuoteIfAllowed(myBuilder);
1167         }
1168 
1169         /* Return the command */
1170         return myBuilder.toString();
1171     }
1172 
1173     /**
1174      * Build the load string for a list of columns.
1175      *
1176      * @return the SQL string
1177      */
1178     protected String getLoadString() {
1179         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1180 
1181         /* Build the initial select */
1182         myBuilder.append("select ");
1183 
1184         /* Create the iterator */
1185         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
1186         boolean myFirst = true;
1187 
1188         /* Loop through the columns */
1189         while (myIterator.hasNext()) {
1190             final PrometheusColumnDefinition myDef = myIterator.next();
1191             if (!myFirst) {
1192                 myBuilder.append(", ");
1193             }
1194             if (sortOnReference) {
1195                 myBuilder.append("a.");
1196             }
1197             addQuoteIfAllowed(myBuilder);
1198             myBuilder.append(myDef.getColumnName());
1199             addQuoteIfAllowed(myBuilder);
1200             myFirst = false;
1201         }
1202 
1203         /* Close the statement */
1204         myBuilder.append(" from ");
1205         addQuoteIfAllowed(myBuilder);
1206         myBuilder.append(theTableName);
1207         addQuoteIfAllowed(myBuilder);
1208         if (sortOnReference) {
1209             myBuilder.append(" a");
1210         }
1211 
1212         /* If we are indexed */
1213         if (isIndexed()) {
1214             /* Add Joins */
1215             if (sortOnReference) {
1216                 myBuilder.append(getJoinString('a', 1));
1217             }
1218 
1219             /* Add the order clause */
1220             myBuilder.append(" order by ");
1221 
1222             /* Build the order string */
1223             myBuilder.append(getOrderString('a', 0));
1224         }
1225 
1226         return myBuilder.toString();
1227     }
1228 
1229     @Override
1230     public String getJoinString(final char pChar,
1231                                 final Integer pOffset) {
1232         /* Loop through the columns */
1233         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1234         for (PrometheusColumnControl myDef : theSortList) {
1235             /* Access next column and skip if not reference column */
1236             if (!(myDef instanceof PrometheusReferenceColumn myCol)) {
1237                 continue;
1238             }
1239 
1240             /* Add the join */
1241             myCol.buildJoinString(myBuilder, pChar, pOffset);
1242         }
1243 
1244         return myBuilder.toString();
1245     }
1246 
1247     @Override
1248     public String getOrderString(final char pChar,
1249                                  final Integer pOffset) {
1250         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1251 
1252         /* Create the iterator */
1253         final Iterator<PrometheusColumnControl> myIterator = theSortList.iterator();
1254         boolean myFirst = true;
1255 
1256         /* Loop through the columns */
1257         while (myIterator.hasNext()) {
1258             final PrometheusColumnDefinition myDef = (PrometheusColumnDefinition) myIterator.next();
1259             /* Handle secondary columns */
1260             if (!myFirst) {
1261                 myBuilder.append(", ");
1262             }
1263 
1264             /* If we are using prefixes */
1265             if (sortOnReference || pChar > 'a') {
1266                 /* If this is a reference column */
1267                 if (myDef instanceof PrometheusReferenceColumn myCol) {
1268                     /* Handle Reference column */
1269                     myCol.buildOrderString(myBuilder, pOffset + 1);
1270                 } else {
1271                     /* Handle standard column with prefix */
1272                     myBuilder.append(pChar);
1273                     myBuilder.append(".");
1274                     addQuoteIfAllowed(myBuilder);
1275                     myBuilder.append(myDef.getColumnName());
1276                     addQuoteIfAllowed(myBuilder);
1277                     if (myDef.getSortOrder() == PrometheusSortOrder.DESCENDING) {
1278                         myBuilder.append(STR_DESC);
1279                     }
1280                 }
1281             } else {
1282                 /* Handle standard column */
1283                 addQuoteIfAllowed(myBuilder);
1284                 myBuilder.append(myDef.getColumnName());
1285                 addQuoteIfAllowed(myBuilder);
1286                 if (myDef.getSortOrder() == PrometheusSortOrder.DESCENDING) {
1287                     myBuilder.append(STR_DESC);
1288                 }
1289             }
1290 
1291             /* Note secondary columns */
1292             myFirst = false;
1293         }
1294 
1295         return myBuilder.toString();
1296     }
1297 
1298     /**
1299      * Build the insert string for a list of columns.
1300      *
1301      * @return the SQL string
1302      */
1303     protected String getInsertString() {
1304         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1305         final StringBuilder myValues = new StringBuilder(BUFFER_LEN);
1306 
1307         /* Build the initial insert */
1308         myBuilder.append("insert into ");
1309         addQuoteIfAllowed(myBuilder);
1310         myBuilder.append(theTableName);
1311         addQuoteIfAllowed(myBuilder);
1312         myBuilder.append(" (");
1313 
1314         /* Create the iterator */
1315         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
1316         boolean myFirst = true;
1317 
1318         /* Loop through the columns */
1319         while (myIterator.hasNext()) {
1320             final PrometheusColumnDefinition myDef = myIterator.next();
1321             if (!myFirst) {
1322                 myBuilder.append(", ");
1323                 myValues.append(", ");
1324             }
1325             addQuoteIfAllowed(myBuilder);
1326             myBuilder.append(myDef.getColumnName());
1327             addQuoteIfAllowed(myBuilder);
1328             myValues.append('?');
1329             myFirst = false;
1330         }
1331 
1332         /* Close the statement and return it */
1333         myBuilder.append(") values(");
1334         myBuilder.append(myValues);
1335         myBuilder.append(')');
1336         return myBuilder.toString();
1337     }
1338 
1339     /**
1340      * Build the update string for a list of columns.
1341      *
1342      * @return the SQL string
1343      * @throws OceanusException on error
1344      */
1345     protected String getUpdateString() throws OceanusException {
1346         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1347 
1348         /* Build the initial update */
1349         myBuilder.append("update ");
1350         addQuoteIfAllowed(myBuilder);
1351         myBuilder.append(theTableName);
1352         addQuoteIfAllowed(myBuilder);
1353         myBuilder.append(" set ");
1354 
1355         /* Create the iterator */
1356         final Iterator<PrometheusColumnDefinition> myIterator = theList.iterator();
1357         PrometheusColumnDefinition myId = null;
1358         boolean myFirst = true;
1359 
1360         /* Loop through the columns */
1361         while (myIterator.hasNext()) {
1362             final PrometheusColumnDefinition myDef = myIterator.next();
1363 
1364             /* If this is the Id record */
1365             if (myDef instanceof PrometheusIdColumn) {
1366                 /* Reject if the value is not set */
1367                 if (!myDef.isValueSet()) {
1368                     throw new PrometheusLogicException(getColumnError(myDef) + " has no value for update");
1369                 }
1370 
1371                 /* Remember the column */
1372                 myId = myDef;
1373 
1374                 /* If this column is to be updated */
1375             } else if (myDef.isValueSet()) {
1376                 /* Add the update of this column */
1377                 if (!myFirst) {
1378                     myBuilder.append(", ");
1379                 }
1380                 addQuoteIfAllowed(myBuilder);
1381                 myBuilder.append(myDef.getColumnName());
1382                 addQuoteIfAllowed(myBuilder);
1383                 myBuilder.append("=?");
1384                 myFirst = false;
1385             }
1386         }
1387 
1388         /* If we have no values then just return null */
1389         if (myFirst || myId == null) {
1390             return null;
1391         }
1392 
1393         /* Close the statement and return it */
1394         myBuilder.append(" where ");
1395         addQuoteIfAllowed(myBuilder);
1396         myBuilder.append(myId.getColumnName());
1397         addQuoteIfAllowed(myBuilder);
1398         myBuilder.append("=?");
1399         return myBuilder.toString();
1400     }
1401 
1402     /**
1403      * Build the delete string for a table.
1404      *
1405      * @return the SQL string
1406      */
1407     protected String getDeleteString() {
1408         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1409 
1410         /* Build the initial delete */
1411         myBuilder.append("delete from ");
1412         addQuoteIfAllowed(myBuilder);
1413         myBuilder.append(theTableName);
1414         addQuoteIfAllowed(myBuilder);
1415         myBuilder.append(" where ");
1416 
1417         /* Access the id definition */
1418         final PrometheusColumnDefinition myId = theList.getFirst();
1419 
1420         /* Build the rest of the command */
1421         addQuoteIfAllowed(myBuilder);
1422         myBuilder.append(myId.getColumnName());
1423         addQuoteIfAllowed(myBuilder);
1424         myBuilder.append("=?");
1425         return myBuilder.toString();
1426     }
1427 
1428     /**
1429      * Build the purge string for a table.
1430      *
1431      * @return the SQL string
1432      */
1433     protected String getPurgeString() {
1434         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1435 
1436         /* Build the initial delete */
1437         myBuilder.append("delete from ");
1438         addQuoteIfAllowed(myBuilder);
1439         myBuilder.append(theTableName);
1440         addQuoteIfAllowed(myBuilder);
1441         return myBuilder.toString();
1442     }
1443 
1444     /**
1445      * Build the count string for a table.
1446      *
1447      * @return the SQL string
1448      */
1449     protected String getCountString() {
1450         final StringBuilder myBuilder = new StringBuilder(BUFFER_LEN);
1451 
1452         /* Build the initial delete */
1453         myBuilder.append("select count(*) from ");
1454         addQuoteIfAllowed(myBuilder);
1455         myBuilder.append(theTableName);
1456         addQuoteIfAllowed(myBuilder);
1457         return myBuilder.toString();
1458     }
1459 
1460     @Override
1461     public void addQuoteIfAllowed(final StringBuilder pBuilder) {
1462         if (theDriver.useQuotes()) {
1463             pBuilder.append(QUOTE_STRING);
1464         }
1465     }
1466 }