diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2018-05-08 04:36:42 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2018-05-08 04:36:42 +0000 |
commit | 1a8771e55502dbfd84e192017cc23f6433f2a8d2 (patch) | |
tree | fc34a2467f4d312f92fa2f38a44b4a0726a3310b /src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java | |
parent | 5a39a80966669d8280490e0e3b138c03d481a823 (diff) | |
download | jackcess-1a8771e55502dbfd84e192017cc23f6433f2a8d2.tar.gz jackcess-1a8771e55502dbfd84e192017cc23f6433f2a8d2.zip |
plug expr evaluation into columns/tables; create Identifier for tracking expression ids; support single quoting in expressions; tweak string to number coercion; implement topo sorter for calc col eval
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1148 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java')
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java | 157 |
1 files changed, 153 insertions, 4 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 138b2c8..7e996e1 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -49,8 +49,10 @@ import com.healthmarketscience.jackcess.PropertyMap; import com.healthmarketscience.jackcess.Row; import com.healthmarketscience.jackcess.RowId; import com.healthmarketscience.jackcess.Table; +import com.healthmarketscience.jackcess.expr.Identifier; import com.healthmarketscience.jackcess.util.ErrorHandler; import com.healthmarketscience.jackcess.util.ExportUtil; +import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -134,6 +136,8 @@ public class TableImpl implements Table, PropertyMaps.Owner private final List<ColumnImpl> _varColumns = new ArrayList<ColumnImpl>(); /** List of autonumber columns in this table, ordered by column number */ private final List<ColumnImpl> _autoNumColumns = new ArrayList<ColumnImpl>(1); + /** handler for calculated columns */ + private final CalcColEvaluator _calcColEval = new CalcColEvaluator(); /** List of indexes on this table (multiple logical indexes may be backed by the same index data) */ private final List<IndexImpl> _indexes = new ArrayList<IndexImpl>(); @@ -179,6 +183,8 @@ public class TableImpl implements Table, PropertyMaps.Owner private Boolean _allowAutoNumInsert; /** foreign-key enforcer for this table */ private final FKEnforcer _fkEnforcer; + /** table validator if any (and enabled) */ + private RowValidatorEvalContext _rowValidator; /** default cursor for iterating through the table, kept here for basic table traversal */ @@ -281,11 +287,36 @@ public class TableImpl implements Table, PropertyMaps.Owner _fkEnforcer = new FKEnforcer(this); if(!isSystem()) { - // after fully constructed, allow column validator to be configured (but - // only for user tables) + // after fully constructed, allow column/row validators to be configured + // (but only for user tables) for(ColumnImpl col : _columns) { col.initColumnValidator(); } + + reloadRowValidator(); + } + } + + private void reloadRowValidator() throws IOException { + + // reset table row validator before proceeding + _rowValidator = null; + + if(!getDatabase().isEvaluateExpressions()) { + return; + } + + PropertyMap props = getProperties(); + + String exprStr = PropertyMaps.getTrimmedStringProperty( + props, PropertyMap.VALIDATION_RULE_PROP); + + if(exprStr != null) { + String helpStr = PropertyMaps.getTrimmedStringProperty( + props, PropertyMap.VALIDATION_TEXT_PROP); + + _rowValidator = new RowValidatorEvalContext(this) + .setExpr(exprStr, helpStr); } } @@ -444,9 +475,16 @@ public class TableImpl implements Table, PropertyMaps.Owner } public void propertiesUpdated() throws IOException { + // propagate update to columns for(ColumnImpl col : _columns) { col.propertiesUpdated(); } + + reloadRowValidator(); + + // calculated columns will need to be re-sorted (their expressions may + // have changed when their properties were updated) + _calcColEval.reSort(); } public List<IndexImpl> getIndexes() { @@ -1290,6 +1328,9 @@ public class TableImpl implements Table, PropertyMaps.Owner if(newCol.isAutoNumber()) { _autoNumColumns.add(newCol); } + if(newCol.isCalculated()) { + _calcColEval.add(newCol); + } if(umapPos >= 0) { // read column usage map @@ -1925,6 +1966,7 @@ public class TableImpl implements Table, PropertyMaps.Owner Collections.sort(_columns); initAutoNumberColumns(); + initCalculatedColumns(); // setup the data index for the columns int colIdx = 0; @@ -2187,8 +2229,12 @@ public class TableImpl implements Table, PropertyMaps.Owner // handle various value massaging activities for(ColumnImpl column : _columns) { if(!column.isAutoNumber()) { + Object val = column.getRowValue(row); + if(val == null) { + val = column.generateDefaultValue(); + } // pass input value through column validator - column.setRowValue(row, column.validate(column.getRowValue(row))); + column.setRowValue(row, column.validate(val)); } } @@ -2196,6 +2242,15 @@ public class TableImpl implements Table, PropertyMaps.Owner handleAutoNumbersForAdd(row, writeRowState); ++autoNumAssignCount; + // need to assign calculated values after all the other fields are + // filled in but before final validation + _calcColEval.calculate(row); + + // run row validation if enabled + if(_rowValidator != null) { + _rowValidator.validate(row); + } + // write the row of data to a temporary buffer ByteBuffer rowData = createRow( row, _writeRowBufferH.getPageBuffer(getPageChannel())); @@ -2440,6 +2495,15 @@ public class TableImpl implements Table, PropertyMaps.Owner // fill in autonumbers handleAutoNumbersForUpdate(row, rowBuffer, rowState); + // need to assign calculated values after all the other fields are + // filled in but before final validation + _calcColEval.calculate(row); + + // run row validation if enabled + if(_rowValidator != null) { + _rowValidator.validate(row); + } + // generate new row bytes ByteBuffer newRowData = createRow( row, _writeRowBufferH.getPageBuffer(getPageChannel()), oldRowSize, @@ -2661,6 +2725,7 @@ public class TableImpl implements Table, PropertyMaps.Owner return dataPage; } + // exposed for unit tests protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer) throws IOException { @@ -2984,6 +3049,7 @@ public class TableImpl implements Table, PropertyMaps.Owner .append("columnCount", _columns.size()) .append("indexCount(data)", _indexCount) .append("logicalIndexCount", _logicalIndexCount) + .append("validator", CustomToStringStyle.ignoreNull(_rowValidator)) .append("columns", _columns) .append("indexes", _indexes) .append("ownedPages", _ownedPages) @@ -3164,6 +3230,20 @@ public class TableImpl implements Table, PropertyMaps.Owner } } + private void initCalculatedColumns() { + for(ColumnImpl c : _columns) { + if(c.isCalculated()) { + _calcColEval.add(c); + } + } + } + + boolean isThisTable(Identifier identifier) { + String collectionName = identifier.getCollectionName(); + return ((collectionName == null) || + collectionName.equalsIgnoreCase(getName())); + } + /** * Returns {@code true} if a row of the given size will fit on the given * data page, {@code false} otherwise. @@ -3190,7 +3270,7 @@ public class TableImpl implements Table, PropertyMaps.Owner return copy; } - private String withErrorContext(String msg) { + String withErrorContext(String msg) { return withErrorContext(msg, getDatabase(), getName()); } @@ -3493,4 +3573,73 @@ public class TableImpl implements Table, PropertyMaps.Owner } } + /** + * Utility for managing calculated columns. Calculated columns need to be + * evaluated in dependency order. + */ + private class CalcColEvaluator + { + /** List of calculated columns in this table, ordered by calculation + dependency */ + private final List<ColumnImpl> _calcColumns = new ArrayList<ColumnImpl>(1); + private boolean _sorted; + + public void add(ColumnImpl col) { + if(!getDatabase().isEvaluateExpressions()) { + return; + } + _calcColumns.add(col); + // whenever we add new columns, we need to re-sort + _sorted = false; + } + + public void reSort() { + // mark columns for re-sort on next use + _sorted = false; + } + + public void calculate(Object[] row) throws IOException { + if(!_sorted) { + sortColumnsByDeps(); + _sorted = true; + } + + for(ColumnImpl col : _calcColumns) { + Object rowValue = col.getCalculationContext().eval(row); + col.setRowValue(row, rowValue); + } + } + + private void sortColumnsByDeps() { + + // a topological sort sorts nodes where A -> B such that A ends up in + // the list before B (assuming that we are working with a DAG). In our + // case, we return "descendent" info as Field1 -> Field2 (where Field1 + // uses Field2 in its calculation). This means that in order to + // correctly calculate Field1, we need to calculate Field2 first, and + // hence essentially need the reverse topo sort (a list where Field2 + // comes before Field1). + (new TopoSorter<ColumnImpl>(_calcColumns, TopoSorter.REVERSE) { + @Override + protected void getDescendents(ColumnImpl from, + List<ColumnImpl> descendents) { + + Set<Identifier> identifiers = new LinkedHashSet<Identifier>(); + from.getCalculationContext().collectIdentifiers(identifiers); + + for(Identifier identifier : identifiers) { + if(isThisTable(identifier)) { + String colName = identifier.getObjectName(); + for(ColumnImpl calcCol : _calcColumns) { + // we only care if the identifier is another calc field + if(calcCol.getName().equalsIgnoreCase(colName)) { + descendents.add(calcCol); + } + } + } + } + } + }).sort(); + } + } } |