git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1147 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.2.0
@@ -0,0 +1,32 @@ | |||
/* | |||
Copyright (c) 2018 James Ahlborn | |||
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 com.healthmarketscience.jackcess; | |||
/** | |||
* JackcessException which indicates that an invalid column value was provided | |||
* in a database update. | |||
* | |||
* @author James Ahlborn | |||
*/ | |||
public class InvalidValueException extends JackcessException | |||
{ | |||
private static final long serialVersionUID = 20180428L; | |||
public InvalidValueException(String msg) { | |||
super(msg); | |||
} | |||
} |
@@ -21,6 +21,8 @@ import java.math.BigDecimal; | |||
import java.nio.ByteBuffer; | |||
import java.nio.ByteOrder; | |||
import com.healthmarketscience.jackcess.InvalidValueException; | |||
/** | |||
* Utility code for dealing with calculated columns. | |||
@@ -30,7 +32,7 @@ import java.nio.ByteOrder; | |||
* | |||
* @author James Ahlborn | |||
*/ | |||
class CalculatedColumnUtil | |||
class CalculatedColumnUtil | |||
{ | |||
// offset to the int which specifies the length of the actual data | |||
private static final int CALC_DATA_LEN_OFFSET = 16; | |||
@@ -51,12 +53,12 @@ class CalculatedColumnUtil | |||
/** | |||
* Creates the appropriate ColumnImpl class for a calculated column and | |||
* reads a column definition in from a buffer | |||
* | |||
* | |||
* @param args column construction info | |||
* @usage _advanced_method_ | |||
*/ | |||
static ColumnImpl create(ColumnImpl.InitArgs args) throws IOException | |||
{ | |||
{ | |||
switch(args.type) { | |||
case BOOLEAN: | |||
return new CalcBooleanColImpl(args); | |||
@@ -71,7 +73,7 @@ class CalculatedColumnUtil | |||
if(args.type.getHasScalePrecision()) { | |||
return new CalcNumericColImpl(args); | |||
} | |||
return new CalcColImpl(args); | |||
} | |||
@@ -82,7 +84,7 @@ class CalculatedColumnUtil | |||
if(data.length < CALC_DATA_OFFSET) { | |||
return data; | |||
} | |||
ByteBuffer buffer = PageChannel.wrap(data); | |||
buffer.position(CALC_DATA_LEN_OFFSET); | |||
int dataLen = buffer.getInt(); | |||
@@ -109,7 +111,7 @@ class CalculatedColumnUtil | |||
*/ | |||
private static byte[] wrapCalculatedValue(byte[] data) { | |||
int dataLen = data.length; | |||
data = ByteUtil.copyOf(data, 0, dataLen + CALC_EXTRA_DATA_LEN, | |||
data = ByteUtil.copyOf(data, 0, dataLen + CALC_EXTRA_DATA_LEN, | |||
CALC_DATA_OFFSET); | |||
PageChannel.wrap(data).putInt(CALC_DATA_LEN_OFFSET, dataLen); | |||
return data; | |||
@@ -126,7 +128,7 @@ class CalculatedColumnUtil | |||
buffer.position(CALC_DATA_OFFSET); | |||
return buffer; | |||
} | |||
/** | |||
* General calculated column implementation. | |||
@@ -148,7 +150,7 @@ class CalculatedColumnUtil | |||
} | |||
@Override | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
ByteOrder order) | |||
throws IOException | |||
{ | |||
@@ -185,7 +187,7 @@ class CalculatedColumnUtil | |||
} | |||
@Override | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
ByteOrder order) | |||
throws IOException | |||
{ | |||
@@ -216,7 +218,7 @@ class CalculatedColumnUtil | |||
} | |||
@Override | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
ByteOrder order) | |||
throws IOException | |||
{ | |||
@@ -249,12 +251,12 @@ class CalculatedColumnUtil | |||
} | |||
@Override | |||
protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength) | |||
protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength) | |||
throws IOException | |||
{ | |||
return super.writeLongValue( | |||
wrapCalculatedValue(value), remainingRowLength); | |||
} | |||
} | |||
} | |||
/** | |||
@@ -282,7 +284,7 @@ class CalculatedColumnUtil | |||
} | |||
@Override | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
protected ByteBuffer writeRealData(Object obj, int remainingRowLength, | |||
ByteOrder order) | |||
throws IOException | |||
{ | |||
@@ -337,14 +339,14 @@ class CalculatedColumnUtil | |||
decVal = decVal.setScale(maxScale); | |||
} | |||
int scale = decVal.scale(); | |||
// check precision | |||
if(decVal.precision() > getType().getMaxPrecision()) { | |||
throw new IOException(withErrorContext( | |||
throw new InvalidValueException(withErrorContext( | |||
"Numeric value is too big for specified precision " | |||
+ getType().getMaxPrecision() + ": " + decVal)); | |||
} | |||
// convert to unscaled BigInteger, big-endian bytes | |||
byte[] intValBytes = toUnscaledByteArray(decVal, dataLen - 4); | |||
@@ -0,0 +1,62 @@ | |||
/* | |||
Copyright (c) 2018 James Ahlborn | |||
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 com.healthmarketscience.jackcess.impl; | |||
import java.io.IOException; | |||
import com.healthmarketscience.jackcess.Column; | |||
import com.healthmarketscience.jackcess.util.ColumnValidator; | |||
/** | |||
* Base class for ColumnValidator instances handling "internal" validation | |||
* functionality, which are wrappers around any "external" behavior. | |||
* | |||
* @author James Ahlborn | |||
*/ | |||
abstract class InternalColumnValidator implements ColumnValidator | |||
{ | |||
private ColumnValidator _delegate; | |||
protected InternalColumnValidator(ColumnValidator delegate) | |||
{ | |||
_delegate = delegate; | |||
} | |||
ColumnValidator getExternal() { | |||
ColumnValidator extValidator = _delegate; | |||
while(extValidator instanceof InternalColumnValidator) { | |||
extValidator = ((InternalColumnValidator)extValidator)._delegate; | |||
} | |||
return extValidator; | |||
} | |||
void setExternal(ColumnValidator extValidator) { | |||
InternalColumnValidator intValidator = this; | |||
while(intValidator._delegate instanceof InternalColumnValidator) { | |||
intValidator = (InternalColumnValidator)intValidator._delegate; | |||
} | |||
intValidator._delegate = extValidator; | |||
} | |||
public final Object validate(Column col, Object val) throws IOException { | |||
val = _delegate.validate(col, val); | |||
return internalValidate(col, val); | |||
} | |||
protected abstract Object internalValidate(Column col, Object val) | |||
throws IOException; | |||
} |
@@ -17,19 +17,19 @@ limitations under the License. | |||
package com.healthmarketscience.jackcess.impl; | |||
import java.io.IOException; | |||
import java.lang.reflect.Type; | |||
import java.nio.ByteBuffer; | |||
import java.nio.ByteOrder; | |||
import java.util.Collection; | |||
import com.healthmarketscience.jackcess.InvalidValueException; | |||
/** | |||
* ColumnImpl subclass which is used for long value data types. | |||
* | |||
* | |||
* @author James Ahlborn | |||
* @usage _advanced_class_ | |||
*/ | |||
class LongValueColumnImpl extends ColumnImpl | |||
class LongValueColumnImpl extends ColumnImpl | |||
{ | |||
/** | |||
* Long value (LVAL) type that indicates that the value is stored on the | |||
@@ -60,12 +60,12 @@ class LongValueColumnImpl extends ColumnImpl | |||
{ | |||
super(args); | |||
} | |||
@Override | |||
public int getOwnedPageCount() { | |||
return ((_lvalBufferH == null) ? 0 : _lvalBufferH.getOwnedPageCount()); | |||
} | |||
@Override | |||
void setUsageMaps(UsageMap ownedPages, UsageMap freeSpacePages) { | |||
_lvalBufferH = new UmapLongValueBufferHolder(ownedPages, freeSpacePages); | |||
@@ -75,7 +75,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
void collectUsageMapPages(Collection<Integer> pages) { | |||
_lvalBufferH.collectUsageMapPages(pages); | |||
} | |||
@Override | |||
void postTableLoadInit() throws IOException { | |||
if(_lvalBufferH == null) { | |||
@@ -104,7 +104,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
default: | |||
throw new RuntimeException(withErrorContext( | |||
"unexpected var length, long value type: " + getType())); | |||
} | |||
} | |||
} | |||
@Override | |||
@@ -122,12 +122,12 @@ class LongValueColumnImpl extends ColumnImpl | |||
default: | |||
throw new RuntimeException(withErrorContext( | |||
"unexpected var length, long value type: " + getType())); | |||
} | |||
} | |||
// create long value buffer | |||
return writeLongValue(toByteArray(obj), remainingRowLength); | |||
} | |||
} | |||
/** | |||
* @param lvalDefinition Column value that points to an LVAL record | |||
* @return The LVAL data | |||
@@ -152,7 +152,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
if(rowLen < length) { | |||
// warn the caller, but return whatever we can | |||
LOG.warn(withErrorContext( | |||
"Value may be truncated: expected length " + | |||
"Value may be truncated: expected length " + | |||
length + " found " + rowLen)); | |||
rtn = new byte[rowLen]; | |||
} | |||
@@ -172,7 +172,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
int rowNum = ByteUtil.getUnsignedByte(def); | |||
int pageNum = ByteUtil.get3ByteInt(def, def.position()); | |||
ByteBuffer lvalPage = getPageChannel().createPageBuffer(); | |||
switch (type) { | |||
case LONG_VALUE_TYPE_OTHER_PAGE: | |||
{ | |||
@@ -185,16 +185,16 @@ class LongValueColumnImpl extends ColumnImpl | |||
if(rowLen < length) { | |||
// warn the caller, but return whatever we can | |||
LOG.warn(withErrorContext( | |||
"Value may be truncated: expected length " + | |||
"Value may be truncated: expected length " + | |||
length + " found " + rowLen)); | |||
rtn = new byte[rowLen]; | |||
} | |||
lvalPage.position(rowStart); | |||
lvalPage.get(rtn); | |||
} | |||
break; | |||
case LONG_VALUE_TYPE_OTHER_PAGES: | |||
ByteBuffer rtnBuf = ByteBuffer.wrap(rtn); | |||
@@ -205,7 +205,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat()); | |||
short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat()); | |||
// read next page information | |||
lvalPage.position(rowStart); | |||
rowNum = ByteUtil.getUnsignedByte(lvalPage); | |||
@@ -218,22 +218,22 @@ class LongValueColumnImpl extends ColumnImpl | |||
chunkLength = remainingLen; | |||
} | |||
remainingLen -= chunkLength; | |||
lvalPage.limit(rowEnd); | |||
rtnBuf.put(lvalPage); | |||
} | |||
break; | |||
default: | |||
throw new IOException(withErrorContext( | |||
"Unrecognized long value type: " + type)); | |||
} | |||
} | |||
return rtn; | |||
} | |||
/** | |||
* @param lvalDefinition Column value that points to an LVAL record | |||
* @return The LVAL data | |||
@@ -259,11 +259,11 @@ class LongValueColumnImpl extends ColumnImpl | |||
* value (unless written to other pages) | |||
* @usage _advanced_method_ | |||
*/ | |||
protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength) | |||
protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength) | |||
throws IOException | |||
{ | |||
if(value.length > getType().getMaxSize()) { | |||
throw new IOException(withErrorContext( | |||
throw new InvalidValueException(withErrorContext( | |||
"value too big for column, max " + | |||
getType().getMaxSize() + ", got " + value.length)); | |||
} | |||
@@ -292,11 +292,11 @@ class LongValueColumnImpl extends ColumnImpl | |||
def.putInt(0); //Unknown | |||
def.put(value); | |||
} else { | |||
ByteBuffer lvalPage = null; | |||
int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER; | |||
byte firstLvalRow = 0; | |||
// write other page(s) | |||
switch(type) { | |||
case LONG_VALUE_TYPE_OTHER_PAGE: | |||
@@ -335,7 +335,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
nextLvalPage = _lvalBufferH.getLongValuePage( | |||
(remainingLen - chunkLength) + 4); | |||
nextLvalPageNum = _lvalBufferH.getPageNumber(); | |||
nextLvalRowNum = TableImpl.getRowsOnDataPage(nextLvalPage, | |||
nextLvalRowNum = TableImpl.getRowsOnDataPage(nextLvalPage, | |||
getFormat()); | |||
} else { | |||
nextLvalPage = null; | |||
@@ -345,7 +345,7 @@ class LongValueColumnImpl extends ColumnImpl | |||
// add row to this page | |||
TableImpl.addDataPageRow(lvalPage, chunkLength + 4, getFormat(), 0); | |||
// write next page info | |||
lvalPage.put((byte)nextLvalRowNum); // row number | |||
ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number | |||
@@ -373,9 +373,9 @@ class LongValueColumnImpl extends ColumnImpl | |||
def.put(firstLvalRow); | |||
ByteUtil.put3ByteInt(def, firstLvalPageNum); | |||
def.putInt(0); //Unknown | |||
} | |||
def.flip(); | |||
return def; | |||
} | |||
@@ -499,10 +499,10 @@ class LongValueColumnImpl extends ColumnImpl | |||
@Override | |||
protected ByteBuffer findNewPage(int dataLength) throws IOException { | |||
// grab last owned page and check for free space. | |||
ByteBuffer newPage = TableImpl.findFreeRowSpace( | |||
// grab last owned page and check for free space. | |||
ByteBuffer newPage = TableImpl.findFreeRowSpace( | |||
_ownedPages, _freeSpacePages, _longValueBufferH); | |||
if(newPage != null) { | |||
if(TableImpl.rowFitsOnDataPage(dataLength, newPage, getFormat())) { | |||
return newPage; |
@@ -46,16 +46,19 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
/** maps the PropertyMap name (case-insensitive) to the PropertyMap | |||
instance */ | |||
private final Map<String,PropertyMapImpl> _maps = | |||
private final Map<String,PropertyMapImpl> _maps = | |||
new LinkedHashMap<String,PropertyMapImpl>(); | |||
private final int _objectId; | |||
private final RowIdImpl _rowId; | |||
private final Handler _handler; | |||
private final Owner _owner; | |||
public PropertyMaps(int objectId, RowIdImpl rowId, Handler handler) { | |||
public PropertyMaps(int objectId, RowIdImpl rowId, Handler handler, | |||
Owner owner) { | |||
_objectId = objectId; | |||
_rowId = rowId; | |||
_handler = handler; | |||
_owner = owner; | |||
} | |||
public int getObjectId() { | |||
@@ -110,6 +113,9 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
public void save() throws IOException { | |||
_handler.save(this); | |||
if(_owner != null) { | |||
_owner.propertiesUpdated(); | |||
} | |||
} | |||
@Override | |||
@@ -129,7 +135,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
/** the system table "property" column */ | |||
private final ColumnImpl _propCol; | |||
/** cache of PropColumns used to read/write property values */ | |||
private final Map<DataType,PropColumn> _columns = | |||
private final Map<DataType,PropColumn> _columns = | |||
new HashMap<DataType,PropColumn>(); | |||
Handler(DatabaseImpl database) { | |||
@@ -142,11 +148,11 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
* @return a PropertyMaps instance decoded from the given bytes (always | |||
* returns non-{@code null} result). | |||
*/ | |||
public PropertyMaps read(byte[] propBytes, int objectId, | |||
RowIdImpl rowId) | |||
throws IOException | |||
public PropertyMaps read(byte[] propBytes, int objectId, | |||
RowIdImpl rowId, Owner owner) | |||
throws IOException | |||
{ | |||
PropertyMaps maps = new PropertyMaps(objectId, rowId, this); | |||
PropertyMaps maps = new PropertyMaps(objectId, rowId, this, owner); | |||
if((propBytes == null) || (propBytes.length == 0)) { | |||
return maps; | |||
} | |||
@@ -176,7 +182,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
short type = bb.getShort(); | |||
int endPos = bb.position() + len - 6; | |||
ByteBuffer bbBlock = PageChannel.narrowBuffer(bb, bb.position(), | |||
ByteBuffer bbBlock = PageChannel.narrowBuffer(bb, bb.position(), | |||
endPos); | |||
if(type == PROPERTY_NAME_LIST) { | |||
@@ -226,7 +232,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
writeBlock(propMap, propNames, propMap.getType(), bab); | |||
} | |||
} | |||
return bab.toArray(); | |||
} | |||
@@ -260,12 +266,12 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
writePropertyNames(propNames, bab); | |||
} else { | |||
writePropertyValues(propMap, propNames, bab); | |||
} | |||
} | |||
int len = bab.position() - blockStartPos; | |||
bab.putInt(blockStartPos, len); | |||
} | |||
/** | |||
* @return the property names parsed from the given data chunk | |||
*/ | |||
@@ -281,7 +287,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
ByteArrayBuilder bab) { | |||
for(String propName : propNames) { | |||
writePropName(propName, bab); | |||
} | |||
} | |||
} | |||
/** | |||
@@ -290,7 +296,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
*/ | |||
private PropertyMapImpl readPropertyValues( | |||
ByteBuffer bbBlock, List<String> propNames, short blockType, | |||
PropertyMaps maps) | |||
PropertyMaps maps) | |||
throws IOException | |||
{ | |||
String mapName = DEFAULT_NAME; | |||
@@ -305,13 +311,13 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
} | |||
bbBlock.position(endPos); | |||
} | |||
PropertyMapImpl map = maps.get(mapName, blockType); | |||
// read the values | |||
while(bbBlock.hasRemaining()) { | |||
int valLen = bbBlock.getShort(); | |||
int valLen = bbBlock.getShort(); | |||
int endPos = bbBlock.position() + valLen - 2; | |||
boolean isDdl = (bbBlock.get() != 0); | |||
DataType dataType = DataType.fromByte(bbBlock.get()); | |||
@@ -333,9 +339,9 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
} | |||
private void writePropertyValues( | |||
PropertyMapImpl propMap, Set<String> propNames, ByteArrayBuilder bab) | |||
PropertyMapImpl propMap, Set<String> propNames, ByteArrayBuilder bab) | |||
throws IOException | |||
{ | |||
{ | |||
// write the map name, if any | |||
String mapName = propMap.getName(); | |||
int blockStartPos = bab.position(); | |||
@@ -384,7 +390,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
/** | |||
* Reads a property name from the given data block | |||
*/ | |||
private String readPropName(ByteBuffer buffer) { | |||
private String readPropName(ByteBuffer buffer) { | |||
int nameLength = buffer.getShort(); | |||
byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength); | |||
return ColumnImpl.decodeUncompressedText(nameBytes, _database.getCharset()); | |||
@@ -404,8 +410,8 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
* Gets a PropColumn capable of reading/writing a property of the given | |||
* DataType | |||
*/ | |||
private PropColumn getColumn(DataType dataType, String propName, | |||
int dataSize, Object value) | |||
private PropColumn getColumn(DataType dataType, String propName, | |||
int dataSize, Object value) | |||
throws IOException | |||
{ | |||
@@ -426,7 +432,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
} | |||
// create column with ability to read/write the given data type | |||
col = ((colType == DataType.BOOLEAN) ? | |||
col = ((colType == DataType.BOOLEAN) ? | |||
new BooleanPropColumn() : new PropColumn(colType)); | |||
_columns.put(dataType, col); | |||
@@ -436,11 +442,11 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
} | |||
private static boolean isPseudoGuidColumn( | |||
DataType dataType, String propName, int dataSize, Object value) | |||
DataType dataType, String propName, int dataSize, Object value) | |||
throws IOException | |||
{ | |||
// guids seem to be marked as "binary" fields | |||
return((dataType == DataType.BINARY) && | |||
return((dataType == DataType.BINARY) && | |||
((dataSize == DataType.GUID.getFixedSize()) || | |||
((dataSize == -1) && ColumnImpl.isGUIDValue(value))) && | |||
PropertyMap.GUID_PROP.equalsIgnoreCase(propName)); | |||
@@ -454,7 +460,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
private PropColumn(DataType type) { | |||
super(null, null, type, 0, 0, 0); | |||
} | |||
@Override | |||
public DatabaseImpl getDatabase() { | |||
return _database; | |||
@@ -487,4 +493,15 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
} | |||
} | |||
} | |||
/** | |||
* Utility interface for the object which owns the PropertyMaps | |||
*/ | |||
static interface Owner { | |||
/** | |||
* Invoked when new properties are saved. | |||
*/ | |||
public void propertiesUpdated() throws IOException; | |||
} | |||
} |
@@ -21,9 +21,11 @@ import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import static com.healthmarketscience.jackcess.Database.*; | |||
import com.healthmarketscience.jackcess.InvalidValueException; | |||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; | |||
import com.healthmarketscience.jackcess.impl.PropertyMapImpl; | |||
@@ -44,7 +46,7 @@ public class PropertiesTest extends TestCase | |||
public void testPropertyMaps() throws Exception | |||
{ | |||
PropertyMaps maps = new PropertyMaps(10, null, null); | |||
PropertyMaps maps = new PropertyMaps(10, null, null, null); | |||
assertTrue(maps.isEmpty()); | |||
assertEquals(0, maps.getSize()); | |||
assertFalse(maps.iterator().hasNext()); | |||
@@ -59,10 +61,10 @@ public class PropertiesTest extends TestCase | |||
assertTrue(colMap.isEmpty()); | |||
assertEquals(0, colMap.getSize()); | |||
assertFalse(colMap.iterator().hasNext()); | |||
assertFalse(maps.isEmpty()); | |||
assertEquals(2, maps.getSize()); | |||
assertSame(defMap, maps.get(PropertyMaps.DEFAULT_NAME)); | |||
assertEquals(PropertyMaps.DEFAULT_NAME, defMap.getName()); | |||
assertSame(colMap, maps.get("TESTCOL")); | |||
@@ -97,25 +99,25 @@ public class PropertiesTest extends TestCase | |||
} | |||
} | |||
assertEquals(Arrays.asList(defMap.get("foo"), defMap.get("baz"), | |||
assertEquals(Arrays.asList(defMap.get("foo"), defMap.get("baz"), | |||
colMap.get("buzz")), props); | |||
} | |||
public void testInferTypes() throws Exception | |||
{ | |||
PropertyMaps maps = new PropertyMaps(10, null, null); | |||
PropertyMaps maps = new PropertyMaps(10, null, null, null); | |||
PropertyMap defMap = maps.getDefault(); | |||
assertEquals(DataType.TEXT, | |||
assertEquals(DataType.TEXT, | |||
defMap.put(PropertyMap.FORMAT_PROP, null).getType()); | |||
assertEquals(DataType.BOOLEAN, | |||
assertEquals(DataType.BOOLEAN, | |||
defMap.put(PropertyMap.REQUIRED_PROP, null).getType()); | |||
assertEquals(DataType.TEXT, | |||
assertEquals(DataType.TEXT, | |||
defMap.put("strprop", "this is a string").getType()); | |||
assertEquals(DataType.BOOLEAN, | |||
assertEquals(DataType.BOOLEAN, | |||
defMap.put("boolprop", true).getType()); | |||
assertEquals(DataType.LONG, | |||
assertEquals(DataType.LONG, | |||
defMap.put("intprop", 37).getType()); | |||
} | |||
@@ -127,22 +129,22 @@ public class PropertiesTest extends TestCase | |||
Database db = open(testDb); | |||
TableImpl t = (TableImpl)db.getTable("Table1"); | |||
assertEquals(t.getTableDefPageNumber(), | |||
assertEquals(t.getTableDefPageNumber(), | |||
t.getPropertyMaps().getObjectId()); | |||
PropertyMap tProps = t.getProperties(); | |||
assertEquals(PropertyMaps.DEFAULT_NAME, tProps.getName()); | |||
int expectedNumProps = 3; | |||
if(db.getFileFormat() != Database.FileFormat.V1997) { | |||
assertEquals("{5A29A676-1145-4D1A-AE47-9F5415CDF2F1}", | |||
assertEquals("{5A29A676-1145-4D1A-AE47-9F5415CDF2F1}", | |||
tProps.getValue(PropertyMap.GUID_PROP)); | |||
if(nameMapBytes == null) { | |||
nameMapBytes = (byte[])tProps.getValue("NameMap"); | |||
} else { | |||
assertTrue(Arrays.equals(nameMapBytes, | |||
assertTrue(Arrays.equals(nameMapBytes, | |||
(byte[])tProps.getValue("NameMap"))); | |||
} | |||
expectedNumProps += 2; | |||
} | |||
} | |||
assertEquals(expectedNumProps, tProps.getSize()); | |||
assertEquals((byte)0, tProps.getValue("Orientation")); | |||
assertEquals(Boolean.FALSE, tProps.getValue("OrderByOn")); | |||
@@ -152,7 +154,7 @@ public class PropertiesTest extends TestCase | |||
assertEquals("A", colProps.getName()); | |||
expectedNumProps = 9; | |||
if(db.getFileFormat() != Database.FileFormat.V1997) { | |||
assertEquals("{E9EDD90C-CE55-4151-ABE1-A1ACE1007515}", | |||
assertEquals("{E9EDD90C-CE55-4151-ABE1-A1ACE1007515}", | |||
colProps.getValue(PropertyMap.GUID_PROP)); | |||
++expectedNumProps; | |||
} | |||
@@ -160,9 +162,9 @@ public class PropertiesTest extends TestCase | |||
assertEquals((short)-1, colProps.getValue("ColumnWidth")); | |||
assertEquals((short)0, colProps.getValue("ColumnOrder")); | |||
assertEquals(Boolean.FALSE, colProps.getValue("ColumnHidden")); | |||
assertEquals(Boolean.FALSE, | |||
assertEquals(Boolean.FALSE, | |||
colProps.getValue(PropertyMap.REQUIRED_PROP)); | |||
assertEquals(Boolean.FALSE, | |||
assertEquals(Boolean.FALSE, | |||
colProps.getValue(PropertyMap.ALLOW_ZERO_LEN_PROP)); | |||
assertEquals((short)109, colProps.getValue("DisplayControl")); | |||
assertEquals(Boolean.TRUE, colProps.getValue("UnicodeCompression")); | |||
@@ -210,7 +212,8 @@ public class PropertiesTest extends TestCase | |||
for(Row row : ((DatabaseImpl)db).getSystemCatalog()) { | |||
int id = row.getInt("Id"); | |||
byte[] propBytes = row.getBytes("LvProp"); | |||
PropertyMaps propMaps = ((DatabaseImpl)db).getPropertiesForObject(id); | |||
PropertyMaps propMaps = ((DatabaseImpl)db).getPropertiesForObject( | |||
id, null); | |||
int byteLen = ((propBytes != null) ? propBytes.length : 0); | |||
if(byteLen == 0) { | |||
assertTrue(propMaps.isEmpty()); | |||
@@ -251,12 +254,12 @@ public class PropertiesTest extends TestCase | |||
checkProperties(propMap, propMap2); | |||
} | |||
assertFalse(iter.hasNext()); | |||
assertFalse(iter2.hasNext()); | |||
db.close(); | |||
} | |||
} | |||
} | |||
public void testModifyProperties() throws Exception | |||
@@ -333,7 +336,7 @@ public class PropertiesTest extends TestCase | |||
assertTrue((Boolean)cProps.getValue(PropertyMap.REQUIRED_PROP)); | |||
assertEquals("42", fProps.getValue(PropertyMap.DEFAULT_VALUE_PROP)); | |||
assertNull(dProps.getValue("DisplayControl")); | |||
assertNull(dProps.getValue("DisplayControl")); | |||
cProps.put(PropertyMap.REQUIRED_PROP, DataType.BOOLEAN, false); | |||
fProps.get(PropertyMap.DEFAULT_VALUE_PROP).setValue("0"); | |||
@@ -355,7 +358,7 @@ public class PropertiesTest extends TestCase | |||
// weirdo format, no properties | |||
continue; | |||
} | |||
File file = TestUtil.createTempFile(false); | |||
Database db = new DatabaseBuilder(file) | |||
.setFileFormat(ff) | |||
@@ -380,16 +383,16 @@ public class PropertiesTest extends TestCase | |||
db.close(); | |||
db = new DatabaseBuilder(file).open(); | |||
assertEquals("123", db.getUserDefinedProperties().getValue("testing")); | |||
t = db.getTable("Test"); | |||
assertEquals(Boolean.TRUE, | |||
assertEquals(Boolean.TRUE, | |||
t.getProperties().getValue("awesome_table")); | |||
Column c = t.getColumn("id"); | |||
assertEquals(Boolean.TRUE, | |||
assertEquals(Boolean.TRUE, | |||
c.getProperties().getValue(PropertyMap.REQUIRED_PROP)); | |||
assertEquals("{" + u1.toString().toUpperCase() + "}", | |||
c.getProperties().getValue(PropertyMap.GUID_PROP)); | |||
@@ -397,13 +400,123 @@ public class PropertiesTest extends TestCase | |||
c = t.getColumn("data"); | |||
assertEquals(Boolean.FALSE, | |||
c.getProperties().getValue(PropertyMap.ALLOW_ZERO_LEN_PROP)); | |||
assertEquals("{" + u2.toString().toUpperCase() + "}", | |||
assertEquals("{" + u2.toString().toUpperCase() + "}", | |||
c.getProperties().getValue(PropertyMap.GUID_PROP)); | |||
} | |||
} | |||
private static void checkProperties(PropertyMap propMap1, | |||
public void testEnforceProperties() throws Exception | |||
{ | |||
for(final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { | |||
Database db = create(fileFormat); | |||
Table t = new TableBuilder("testReq") | |||
.addColumn(new ColumnBuilder("id", DataType.LONG) | |||
.setAutoNumber(true) | |||
.putProperty(PropertyMap.REQUIRED_PROP, true)) | |||
.addColumn(new ColumnBuilder("value", DataType.TEXT) | |||
.putProperty(PropertyMap.REQUIRED_PROP, true)) | |||
.toTable(db); | |||
t.addRow(Column.AUTO_NUMBER, "v1"); | |||
try { | |||
t.addRow(Column.AUTO_NUMBER, null); | |||
fail("InvalidValueException should have been thrown"); | |||
} catch(InvalidValueException expected) { | |||
// success | |||
} | |||
t.addRow(Column.AUTO_NUMBER, ""); | |||
List<? extends Map<String, Object>> expectedRows = | |||
createExpectedTable( | |||
createExpectedRow( | |||
"id", 1, | |||
"value", "v1"), | |||
createExpectedRow( | |||
"id", 2, | |||
"value", "")); | |||
assertTable(expectedRows, t); | |||
t = new TableBuilder("testNz") | |||
.addColumn(new ColumnBuilder("id", DataType.LONG) | |||
.setAutoNumber(true) | |||
.putProperty(PropertyMap.REQUIRED_PROP, true)) | |||
.addColumn(new ColumnBuilder("value", DataType.TEXT) | |||
.putProperty(PropertyMap.ALLOW_ZERO_LEN_PROP, false)) | |||
.toTable(db); | |||
t.addRow(Column.AUTO_NUMBER, "v1"); | |||
try { | |||
t.addRow(Column.AUTO_NUMBER, ""); | |||
fail("InvalidValueException should have been thrown"); | |||
} catch(InvalidValueException expected) { | |||
// success | |||
} | |||
t.addRow(Column.AUTO_NUMBER, null); | |||
expectedRows = | |||
createExpectedTable( | |||
createExpectedRow( | |||
"id", 1, | |||
"value", "v1"), | |||
createExpectedRow( | |||
"id", 2, | |||
"value", null)); | |||
assertTable(expectedRows, t); | |||
t = new TableBuilder("testReqNz") | |||
.addColumn(new ColumnBuilder("id", DataType.LONG) | |||
.setAutoNumber(true) | |||
.putProperty(PropertyMap.REQUIRED_PROP, true)) | |||
.addColumn(new ColumnBuilder("value", DataType.TEXT)) | |||
.toTable(db); | |||
Column col = t.getColumn("value"); | |||
PropertyMap props = col.getProperties(); | |||
props.put(PropertyMap.REQUIRED_PROP, true); | |||
props.put(PropertyMap.ALLOW_ZERO_LEN_PROP, false); | |||
props.save(); | |||
t.addRow(Column.AUTO_NUMBER, "v1"); | |||
try { | |||
t.addRow(Column.AUTO_NUMBER, ""); | |||
fail("InvalidValueException should have been thrown"); | |||
} catch(InvalidValueException expected) { | |||
// success | |||
} | |||
try { | |||
t.addRow(Column.AUTO_NUMBER, null); | |||
fail("InvalidValueException should have been thrown"); | |||
} catch(InvalidValueException expected) { | |||
// success | |||
} | |||
t.addRow(Column.AUTO_NUMBER, "v2"); | |||
expectedRows = | |||
createExpectedTable( | |||
createExpectedRow( | |||
"id", 1, | |||
"value", "v1"), | |||
createExpectedRow( | |||
"id", 2, | |||
"value", "v2")); | |||
assertTable(expectedRows, t); | |||
db.close(); | |||
} | |||
} | |||
private static void checkProperties(PropertyMap propMap1, | |||
PropertyMap propMap2) | |||
{ | |||
assertEquals(propMap1.getSize(), propMap2.getSize()); |