diff options
Diffstat (limited to 'src/test')
7 files changed, 1405 insertions, 40 deletions
diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java index 7ca0521..056ca68 100644 --- a/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java @@ -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()); @@ -103,7 +105,7 @@ public class PropertiesTest extends TestCase 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, @@ -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()); @@ -403,9 +406,119 @@ public class PropertiesTest extends TestCase } } + 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(); + } + } + public void testEnumValues() throws Exception { - PropertyMaps maps = new PropertyMaps(10, null, null); + PropertyMaps maps = new PropertyMaps(10, null, null, null); PropertyMapImpl colMap = maps.get("testcol"); diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java new file mode 100644 index 0000000..5d3fb44 --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java @@ -0,0 +1,283 @@ +/* +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; + +import java.util.List; + +import junit.framework.TestCase; + +import static com.healthmarketscience.jackcess.Database.*; +import static com.healthmarketscience.jackcess.TestUtil.*; +import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; + +/** + * + * @author James Ahlborn + */ +public class PropertyExpressionTest extends TestCase +{ + + public PropertyExpressionTest(String name) { + super(name); + } + + public void testDefaultValue() throws Exception + { + for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + Database db = create(fileFormat); + db.setEvaluateExpressions(true); + + Table t = new TableBuilder("test") + .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true)) + .addColumn(new ColumnBuilder("data1", DataType.TEXT) + .putProperty(PropertyMap.DEFAULT_VALUE_PROP, + "=\"FOO \" & \"BAR\"")) + .addColumn(new ColumnBuilder("data2", DataType.LONG) + .putProperty(PropertyMap.DEFAULT_VALUE_PROP, + "37")) + .toTable(db); + + t.addRow(Column.AUTO_NUMBER, null, 13); + t.addRow(Column.AUTO_NUMBER, "blah", null); + + setProp(t, "data1", PropertyMap.DEFAULT_VALUE_PROP, null); + setProp(t, "data2", PropertyMap.DEFAULT_VALUE_PROP, "42"); + + t.addRow(Column.AUTO_NUMBER, null, null); + + List<Row> expectedRows = + createExpectedTable( + createExpectedRow( + "id", 1, + "data1", "FOO BAR", + "data2", 13), + createExpectedRow( + "id", 2, + "data1", "blah", + "data2", 37), + createExpectedRow( + "id", 3, + "data1", null, + "data2", 42)); + + assertTable(expectedRows, t); + + db.close(); + } + } + + public void testCalculatedValue() throws Exception + { + Database db = create(FileFormat.V2016); + db.setEvaluateExpressions(true); + + Table t = new TableBuilder("test") + .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true)) + .addColumn(new ColumnBuilder("c1", DataType.LONG) + .setCalculatedInfo("[c2]+[c3]")) + .addColumn(new ColumnBuilder("c2", DataType.LONG) + .setCalculatedInfo("[c3]*5")) + .addColumn(new ColumnBuilder("c3", DataType.LONG) + .setCalculatedInfo("[c4]-6")) + .addColumn(new ColumnBuilder("c4", DataType.LONG)) + .toTable(db); + + t.addRow(Column.AUTO_NUMBER, null, null, null, 16); + + setProp(t, "c1", PropertyMap.EXPRESSION_PROP, "[c4]+2"); + setProp(t, "c2", PropertyMap.EXPRESSION_PROP, "[c1]+[c3]"); + setProp(t, "c3", PropertyMap.EXPRESSION_PROP, "[c1]*7"); + + t.addRow(Column.AUTO_NUMBER, null, null, null, 7); + + List<Row> expectedRows = + createExpectedTable( + createExpectedRow( + "id", 1, + "c1", 60, + "c2", 50, + "c3", 10, + "c4", 16), + createExpectedRow( + "id", 2, + "c1", 9, + "c2", 72, + "c3", 63, + "c4", 7)); + + assertTable(expectedRows, t); + + db.close(); + } + + public void testColumnValidator() throws Exception + { + for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + Database db = create(fileFormat); + db.setEvaluateExpressions(true); + + Table t = new TableBuilder("test") + .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true)) + .addColumn(new ColumnBuilder("data1", DataType.LONG) + .putProperty(PropertyMap.VALIDATION_RULE_PROP, + ">37")) + .addColumn(new ColumnBuilder("data2", DataType.LONG) + .putProperty(PropertyMap.VALIDATION_RULE_PROP, + "between 7 and 10") + .putProperty(PropertyMap.VALIDATION_TEXT_PROP, + "You failed")) + .toTable(db); + + t.addRow(Column.AUTO_NUMBER, 42, 8); + + try { + t.addRow(Column.AUTO_NUMBER, 42, 20); + fail("InvalidValueException should have been thrown"); + } catch(InvalidValueException ive) { + // success + assertTrue(ive.getMessage().contains("You failed")); + } + + try { + t.addRow(Column.AUTO_NUMBER, 3, 8); + fail("InvalidValueException should have been thrown"); + } catch(InvalidValueException ive) { + // success + assertFalse(ive.getMessage().contains("You failed")); + } + + t.addRow(Column.AUTO_NUMBER, 54, 9); + + setProp(t, "data1", PropertyMap.VALIDATION_RULE_PROP, null); + setProp(t, "data2", PropertyMap.VALIDATION_RULE_PROP, "<100"); + setProp(t, "data2", PropertyMap.VALIDATION_TEXT_PROP, "Too big"); + + try { + t.addRow(Column.AUTO_NUMBER, 42, 200); + fail("InvalidValueException should have been thrown"); + } catch(InvalidValueException ive) { + // success + assertTrue(ive.getMessage().contains("Too big")); + } + + t.addRow(Column.AUTO_NUMBER, 1, 9); + + List<Row> expectedRows = + createExpectedTable( + createExpectedRow( + "id", 1, + "data1", 42, + "data2", 8), + createExpectedRow( + "id", 2, + "data1", 54, + "data2", 9), + createExpectedRow( + "id", 3, + "data1", 1, + "data2", 9)); + + assertTable(expectedRows, t); + + db.close(); + } + } + + public void testRowValidator() throws Exception + { + for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + Database db = create(fileFormat); + db.setEvaluateExpressions(true); + + Table t = new TableBuilder("test") + .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true)) + .addColumn(new ColumnBuilder("data1", DataType.LONG)) + .addColumn(new ColumnBuilder("data2", DataType.LONG)) + .putProperty(PropertyMap.VALIDATION_RULE_PROP, + "([data1] > 10) and ([data2] < 100)") + .putProperty(PropertyMap.VALIDATION_TEXT_PROP, + "You failed") + .toTable(db); + + t.addRow(Column.AUTO_NUMBER, 42, 8); + + try { + t.addRow(Column.AUTO_NUMBER, 1, 20); + fail("InvalidValueException should have been thrown"); + } catch(InvalidValueException ive) { + // success + assertTrue(ive.getMessage().contains("You failed")); + } + + t.addRow(Column.AUTO_NUMBER, 54, 9); + + setTableProp(t, PropertyMap.VALIDATION_RULE_PROP, "[data2]<100"); + setTableProp(t, PropertyMap.VALIDATION_TEXT_PROP, "Too big"); + + try { + t.addRow(Column.AUTO_NUMBER, 42, 200); + fail("InvalidValueException should have been thrown"); + } catch(InvalidValueException ive) { + // success + assertTrue(ive.getMessage().contains("Too big")); + } + + t.addRow(Column.AUTO_NUMBER, 1, 9); + + List<Row> expectedRows = + createExpectedTable( + createExpectedRow( + "id", 1, + "data1", 42, + "data2", 8), + createExpectedRow( + "id", 2, + "data1", 54, + "data2", 9), + createExpectedRow( + "id", 3, + "data1", 1, + "data2", 9)); + + assertTable(expectedRows, t); + + db.close(); + } + } + + private static void setProp(Table t, String colName, String propName, + String propVal) throws Exception { + PropertyMap props = t.getColumn(colName).getProperties(); + if(propVal != null) { + props.put(propName, propVal); + } else { + props.remove(propName); + } + props.save(); + } + + private static void setTableProp(Table t, String propName, + String propVal) throws Exception { + PropertyMap props = t.getProperties(); + if(propVal != null) { + props.put(propName, propVal); + } else { + props.remove(propName); + } + props.save(); + } +} diff --git a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java index 3317c7f..7680fb3 100644 --- a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java +++ b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java @@ -53,12 +53,12 @@ import org.junit.Assert; * * @author James Ahlborn */ -public class TestUtil +public class TestUtil { public static final TimeZone TEST_TZ = TimeZone.getTimeZone("America/New_York"); - - private static final ThreadLocal<Boolean> _autoSync = + + private static final ThreadLocal<Boolean> _autoSync = new ThreadLocal<Boolean>(); private TestUtil() {} @@ -76,22 +76,22 @@ public class TestUtil return ((autoSync != null) ? autoSync : Database.DEFAULT_AUTO_SYNC); } - public static Database open(FileFormat fileFormat, File file) - throws Exception + public static Database open(FileFormat fileFormat, File file) + throws Exception { return open(fileFormat, file, false); } - public static Database open(FileFormat fileFormat, File file, boolean inMem) - throws Exception + public static Database open(FileFormat fileFormat, File file, boolean inMem) + throws Exception { FileChannel channel = (inMem ? MemFileChannel.newChannel( - file, DatabaseImpl.RW_CHANNEL_MODE) + file, DatabaseImpl.RW_CHANNEL_MODE) : null); final Database db = new DatabaseBuilder(file).setReadOnly(true) .setAutoSync(getTestAutoSync()).setChannel(channel).open(); - Assert.assertEquals("Wrong JetFormat.", - DatabaseImpl.getFileFormatDetails(fileFormat).getFormat(), + Assert.assertEquals("Wrong JetFormat.", + DatabaseImpl.getFileFormatDetails(fileFormat).getFormat(), ((DatabaseImpl)db).getFormat()); Assert.assertEquals("Wrong FileFormat.", fileFormat, db.getFileFormat()); return db; @@ -109,8 +109,8 @@ public class TestUtil return create(fileFormat, false); } - public static Database create(FileFormat fileFormat, boolean keep) - throws Exception + public static Database create(FileFormat fileFormat, boolean keep) + throws Exception { return create(fileFormat, keep, false); } @@ -119,9 +119,9 @@ public class TestUtil return create(fileFormat, false, true); } - private static Database create(FileFormat fileFormat, boolean keep, - boolean inMem) - throws Exception + private static Database create(FileFormat fileFormat, boolean keep, + boolean inMem) + throws Exception { FileChannel channel = (inMem ? MemFileChannel.newChannel() : null); @@ -147,7 +147,7 @@ public class TestUtil ByteUtil.closeQuietly(outStream); } } - + return new DatabaseBuilder(createTempFile(keep)).setFileFormat(fileFormat) .setAutoSync(getTestAutoSync()).setChannel(channel).create(); } @@ -176,7 +176,7 @@ public class TestUtil File tmp = createTempFile(keep); copyFile(file, tmp); Database db = new DatabaseBuilder(tmp).setAutoSync(getTestAutoSync()).open(); - Assert.assertEquals("Wrong JetFormat.", + Assert.assertEquals("Wrong JetFormat.", DatabaseImpl.getFileFormatDetails(fileFormat).getFormat(), ((DatabaseImpl)db).getFormat()); Assert.assertEquals("Wrong FileFormat.", fileFormat, db.getFileFormat()); @@ -192,7 +192,7 @@ public class TestUtil public static Object[] createTestRow() { return createTestRow("Tim"); } - + static Map<String,Object> createTestRowMap(String col1Val) { return createExpectedRow("A", col1Val, "B", "R", "C", "McCune", "D", 1234, "E", (byte) 0xad, "F", 555.66d, @@ -220,7 +220,7 @@ public class TestUtil static String createNonAsciiString(int len) { return createString(len, '\u0CC0'); } - + private static String createString(int len, char firstChar) { StringBuilder builder = new StringBuilder(len); for(int i = 0; i < len; ++i) { @@ -235,7 +235,7 @@ public class TestUtil Assert.assertEquals(expectedRowCount, countRows(table)); Assert.assertEquals(expectedRowCount, table.getRowCount()); } - + public static int countRows(Table table) throws Exception { int rtn = 0; for(Map<String, Object> row : CursorBuilder.createCursor(table)) { @@ -245,15 +245,15 @@ public class TestUtil } public static void assertTable( - List<? extends Map<String, Object>> expectedTable, + List<? extends Map<String, Object>> expectedTable, Table table) throws IOException { assertCursor(expectedTable, CursorBuilder.createCursor(table)); } - + public static void assertCursor( - List<? extends Map<String, Object>> expectedTable, + List<? extends Map<String, Object>> expectedTable, Cursor cursor) { List<Map<String, Object>> foundTable = @@ -264,9 +264,9 @@ public class TestUtil Assert.assertEquals(expectedTable.size(), foundTable.size()); for(int i = 0; i < expectedTable.size(); ++i) { Assert.assertEquals(expectedTable.get(i), foundTable.get(i)); - } + } } - + public static RowImpl createExpectedRow(Object... rowElements) { RowImpl row = new RowImpl((RowIdImpl)null); for(int i = 0; i < rowElements.length; i += 2) { @@ -274,12 +274,12 @@ public class TestUtil rowElements[i + 1]); } return row; - } + } public static List<Row> createExpectedTable(Row... rows) { return Arrays.<Row>asList(rows); - } - + } + public static void dumpDatabase(Database mdb) throws Exception { dumpDatabase(mdb, false); } @@ -313,7 +313,7 @@ public class TestUtil for(Index index : table.getIndexes()) { ((IndexImpl)index).initialize(); } - + writer.println("TABLE: " + table.getName()); List<String> colNames = new ArrayList<String>(); for(Column col : table.getColumns()) { @@ -377,25 +377,33 @@ public class TestUtil "), found " + foundTime + " (" + found + ")"); } } - + static void copyFile(File srcFile, File dstFile) throws IOException { // FIXME should really be using commons io FileUtils here, but don't want // to add dep for one simple test method - byte[] buf = new byte[1024]; OutputStream ostream = new FileOutputStream(dstFile); InputStream istream = new FileInputStream(srcFile); try { - int numBytes = 0; - while((numBytes = istream.read(buf)) >= 0) { - ostream.write(buf, 0, numBytes); - } + copyStream(istream, ostream); } finally { ostream.close(); } } + static void copyStream(InputStream istream, OutputStream ostream) + throws IOException + { + // FIXME should really be using commons io FileUtils here, but don't want + // to add dep for one simple test method + byte[] buf = new byte[1024]; + int numBytes = 0; + while((numBytes = istream.read(buf)) >= 0) { + ostream.write(buf, 0, numBytes); + } + } + static File createTempFile(boolean keep) throws Exception { File tmp = File.createTempFile("databaseTest", ".mdb"); if(keep) { @@ -416,7 +424,7 @@ public class TestUtil val = f.get(val); ((Map<?,?>)val).clear(); } - + public static byte[] toByteArray(File file) throws IOException { diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/NumberFormatterTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/NumberFormatterTest.java new file mode 100644 index 0000000..f69dca1 --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/impl/NumberFormatterTest.java @@ -0,0 +1,106 @@ +/* +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.math.BigDecimal; + +import junit.framework.TestCase; + + +/** + * + * @author James Ahlborn + */ +public class NumberFormatterTest extends TestCase +{ + + public NumberFormatterTest(String name) { + super(name); + } + + public void testDoubleFormat() throws Exception + { + assertEquals("894984737284944", NumberFormatter.format(894984737284944d)); + assertEquals("-894984737284944", NumberFormatter.format(-894984737284944d)); + assertEquals("8949.84737284944", NumberFormatter.format(8949.84737284944d)); + assertEquals("8949847372844", NumberFormatter.format(8949847372844d)); + assertEquals("8949.847384944", NumberFormatter.format(8949.847384944d)); + assertEquals("8.94985647372849E+16", NumberFormatter.format(89498564737284944d)); + assertEquals("-8.94985647372849E+16", NumberFormatter.format(-89498564737284944d)); + assertEquals("895649.847372849", NumberFormatter.format(895649.84737284944d)); + assertEquals("300", NumberFormatter.format(300d)); + assertEquals("-300", NumberFormatter.format(-300d)); + assertEquals("0.3", NumberFormatter.format(0.3d)); + assertEquals("0.1", NumberFormatter.format(0.1d)); + assertEquals("2.3423421E-12", NumberFormatter.format(0.0000000000023423421d)); + assertEquals("2.3423421E-11", NumberFormatter.format(0.000000000023423421d)); + assertEquals("2.3423421E-10", NumberFormatter.format(0.00000000023423421d)); + assertEquals("-2.3423421E-10", NumberFormatter.format(-0.00000000023423421d)); + assertEquals("2.34234214E-12", NumberFormatter.format(0.00000000000234234214d)); + assertEquals("2.342342156E-12", NumberFormatter.format(0.000000000002342342156d)); + assertEquals("0.000000023423421", NumberFormatter.format(0.000000023423421d)); + assertEquals("2.342342133E-07", NumberFormatter.format(0.0000002342342133d)); + assertEquals("1.#INF", NumberFormatter.format(Double.POSITIVE_INFINITY)); + assertEquals("-1.#INF", NumberFormatter.format(Double.NEGATIVE_INFINITY)); + assertEquals("1.#QNAN", NumberFormatter.format(Double.NaN)); + } + + public void testFloatFormat() throws Exception + { + assertEquals("8949847", NumberFormatter.format(8949847f)); + assertEquals("-8949847", NumberFormatter.format(-8949847f)); + assertEquals("8949.847", NumberFormatter.format(8949.847f)); + assertEquals("894984", NumberFormatter.format(894984f)); + assertEquals("8949.84", NumberFormatter.format(8949.84f)); + assertEquals("8.949856E+16", NumberFormatter.format(89498564737284944f)); + assertEquals("-8.949856E+16", NumberFormatter.format(-89498564737284944f)); + assertEquals("895649.9", NumberFormatter.format(895649.84737284944f)); + assertEquals("300", NumberFormatter.format(300f)); + assertEquals("-300", NumberFormatter.format(-300f)); + assertEquals("0.3", NumberFormatter.format(0.3f)); + assertEquals("0.1", NumberFormatter.format(0.1f)); + assertEquals("2.342342E-12", NumberFormatter.format(0.0000000000023423421f)); + assertEquals("2.342342E-11", NumberFormatter.format(0.000000000023423421f)); + assertEquals("2.342342E-10", NumberFormatter.format(0.00000000023423421f)); + assertEquals("-2.342342E-10", NumberFormatter.format(-0.00000000023423421f)); + assertEquals("2.342342E-12", NumberFormatter.format(0.00000000000234234214f)); + assertEquals("2.342342E-12", NumberFormatter.format(0.000000000002342342156f)); + assertEquals("0.0000234", NumberFormatter.format(0.0000234f)); + assertEquals("2.342E-05", NumberFormatter.format(0.00002342f)); + assertEquals("1.#INF", NumberFormatter.format(Float.POSITIVE_INFINITY)); + assertEquals("-1.#INF", NumberFormatter.format(Float.NEGATIVE_INFINITY)); + assertEquals("1.#QNAN", NumberFormatter.format(Float.NaN)); + } + + public void testDecimalFormat() throws Exception + { + assertEquals("9874539485972.2342342234234", NumberFormatter.format(new BigDecimal("9874539485972.2342342234234"))); + assertEquals("9874539485972.234234223423468", NumberFormatter.format(new BigDecimal("9874539485972.2342342234234678"))); + assertEquals("-9874539485972.234234223423468", NumberFormatter.format(new BigDecimal("-9874539485972.2342342234234678"))); + assertEquals("9.874539485972234234223423468E+31", NumberFormatter.format(new BigDecimal("98745394859722342342234234678000"))); + assertEquals("9.874539485972234234223423468E+31", NumberFormatter.format(new BigDecimal("98745394859722342342234234678000"))); + assertEquals("-9.874539485972234234223423468E+31", NumberFormatter.format(new BigDecimal("-98745394859722342342234234678000"))); + assertEquals("300", NumberFormatter.format(new BigDecimal("300.0"))); + assertEquals("-300", NumberFormatter.format(new BigDecimal("-300.000"))); + assertEquals("0.3", NumberFormatter.format(new BigDecimal("0.3"))); + assertEquals("0.1", NumberFormatter.format(new BigDecimal("0.1000"))); + assertEquals("0.0000000000023423428930458", NumberFormatter.format(new BigDecimal("0.0000000000023423428930458"))); + assertEquals("2.3423428930458389038451E-12", NumberFormatter.format(new BigDecimal("0.0000000000023423428930458389038451"))); + assertEquals("2.342342893045838903845134766E-12", NumberFormatter.format(new BigDecimal("0.0000000000023423428930458389038451347656"))); + } +} diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/TopoSorterTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/TopoSorterTest.java new file mode 100644 index 0000000..61acacb --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/impl/TopoSorterTest.java @@ -0,0 +1,166 @@ +/* +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.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +/** + * + * @author James Ahlborn + */ +public class TopoSorterTest extends TestCase +{ + + public TopoSorterTest(String name) { + super(name); + } + + public void testTopoSort() throws Exception + { + doTopoTest(Arrays.asList("A", "B", "C"), + Arrays.asList("A", "B", "C")); + + doTopoTest(Arrays.asList("B", "A", "C"), + Arrays.asList("A", "B", "C"), + "B", "C", + "A", "B"); + + try { + doTopoTest(Arrays.asList("B", "A", "C"), + Arrays.asList("C", "B", "A"), + "B", "C", + "A", "B", + "C", "A"); + fail("IllegalStateException should have been thrown"); + } catch(IllegalStateException expected) { + // success + assertTrue(expected.getMessage().startsWith("Cycle")); + } + + try { + doTopoTest(Arrays.asList("B", "A", "C"), + Arrays.asList("C", "B", "A"), + "B", "D"); + fail("IllegalStateException should have been thrown"); + } catch(IllegalStateException expected) { + // success + assertTrue(expected.getMessage().startsWith("Unknown descendent")); + } + + doTopoTest(Arrays.asList("B", "D", "A", "C"), + Arrays.asList("D", "A", "B", "C"), + "B", "C", + "A", "B"); + + doTopoTest(Arrays.asList("B", "D", "A", "C"), + Arrays.asList("A", "D", "B", "C"), + "B", "C", + "A", "B", + "A", "D"); + + doTopoTest(Arrays.asList("B", "D", "A", "C"), + Arrays.asList("D", "A", "C", "B"), + "D", "A", + "C", "B"); + + doTopoTest(Arrays.asList("B", "D", "A", "C"), + Arrays.asList("D", "C", "A", "B"), + "D", "A", + "C", "B", + "C", "A"); + + doTopoTest(Arrays.asList("B", "D", "A", "C"), + Arrays.asList("C", "D", "A", "B"), + "D", "A", + "C", "B", + "C", "D"); + + doTopoTest(Arrays.asList("B", "D", "A", "C"), + Arrays.asList("D", "A", "C", "B"), + "D", "A", + "C", "B", + "D", "B"); + } + + private static void doTopoTest(List<String> original, + List<String> expected, + String... descs) { + + List<String> values = new ArrayList<String>(); + values.addAll(original); + + TestTopoSorter tsorter = new TestTopoSorter(values, false); + for(int i = 0; i < descs.length; i+=2) { + tsorter.addDescendents(descs[i], descs[i+1]); + } + + tsorter.sort(); + + assertEquals(expected, values); + + + values = new ArrayList<String>(); + values.addAll(original); + + tsorter = new TestTopoSorter(values, true); + for(int i = 0; i < descs.length; i+=2) { + tsorter.addDescendents(descs[i], descs[i+1]); + } + + tsorter.sort(); + + List<String> expectedReverse = new ArrayList<String>(expected); + Collections.reverse(expectedReverse); + + assertEquals(expectedReverse, values); + } + + private static class TestTopoSorter extends TopoSorter<String> + { + private final Map<String,List<String>> _descMap = + new HashMap<String,List<String>>(); + + protected TestTopoSorter(List<String> values, boolean reverse) { + super(values, reverse); + } + + public void addDescendents(String from, String... tos) { + List<String> descs = _descMap.get(from); + if(descs == null) { + descs = new ArrayList<String>(); + _descMap.put(from, descs); + } + + descs.addAll(Arrays.asList(tos)); + } + + @Override + protected void getDescendents(String from, List<String> descendents) { + List<String> descs = _descMap.get(from); + if(descs != null) { + descendents.addAll(descs); + } + } + } +} diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java new file mode 100644 index 0000000..0b02888 --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java @@ -0,0 +1,221 @@ +/* +Copyright (c) 2016 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.expr; + +import java.math.BigDecimal; + +import com.healthmarketscience.jackcess.expr.EvalException; +import junit.framework.TestCase; +import static com.healthmarketscience.jackcess.impl.expr.ExpressionatorTest.eval; + +/** + * + * @author James Ahlborn + */ +public class DefaultFunctionsTest extends TestCase +{ + + public DefaultFunctionsTest(String name) { + super(name); + } + + public void testFuncs() throws Exception + { + assertEquals("foo", eval("=IIf(10 > 1, \"foo\", \"bar\")")); + assertEquals("bar", eval("=IIf(10 < 1, \"foo\", \"bar\")")); + assertEquals(102, eval("=Asc(\"foo\")")); + assertEquals(9786, eval("=AscW(\"\u263A\")")); + assertEquals("f", eval("=Chr(102)")); + assertEquals("\u263A", eval("=ChrW(9786)")); + assertEquals("263A", eval("=Hex(9786)")); + + assertEquals("blah", eval("=Nz(\"blah\")")); + assertEquals("", eval("=Nz(Null)")); + assertEquals("blah", eval("=Nz(\"blah\",\"FOO\")")); + assertEquals("FOO", eval("=Nz(Null,\"FOO\")")); + + assertEquals("23072", eval("=Oct(9786)")); + assertEquals(" 9786", eval("=Str(9786)")); + assertEquals("-42", eval("=Str(-42)")); + assertEquals("-42", eval("=Str$(-42)")); + assertNull(eval("=Str(Null)")); + + try { + eval("=Str$(Null)"); + fail("EvalException should have been thrown"); + } catch(EvalException expected) { + // success + } + + assertEquals(-1, eval("=CBool(\"1\")")); + assertEquals(13, eval("=CByte(\"13\")")); + assertEquals(14, eval("=CByte(\"13.7\")")); + assertEquals(new BigDecimal("57.1235"), eval("=CCur(\"57.12346\")")); + assertEquals(new Double("57.12345"), eval("=CDbl(\"57.12345\")")); + assertEquals(new BigDecimal("57.123456789"), eval("=CDec(\"57.123456789\")")); + assertEquals(513, eval("=CInt(\"513\")")); + assertEquals(514, eval("=CInt(\"513.7\")")); + assertEquals(345513, eval("=CLng(\"345513\")")); + assertEquals(345514, eval("=CLng(\"345513.7\")")); + assertEquals(new Float("57.12345").doubleValue(), + eval("=CSng(\"57.12345\")")); + assertEquals("9786", eval("=CStr(9786)")); + assertEquals("-42", eval("=CStr(-42)")); + + assertEquals(2, eval("=InStr('AFOOBAR', 'FOO')")); + assertEquals(2, eval("=InStr('AFOOBAR', 'foo')")); + assertEquals(2, eval("=InStr(1, 'AFOOBAR', 'foo')")); + assertEquals(0, eval("=InStr(1, 'AFOOBAR', 'foo', 0)")); + assertEquals(2, eval("=InStr(1, 'AFOOBAR', 'foo', 1)")); + assertEquals(2, eval("=InStr(1, 'AFOOBAR', 'FOO', 0)")); + assertEquals(2, eval("=InStr(2, 'AFOOBAR', 'FOO')")); + assertEquals(0, eval("=InStr(3, 'AFOOBAR', 'FOO')")); + assertEquals(0, eval("=InStr(17, 'AFOOBAR', 'FOO')")); + assertEquals(2, eval("=InStr(1, 'AFOOBARFOOBAR', 'FOO')")); + assertEquals(8, eval("=InStr(3, 'AFOOBARFOOBAR', 'FOO')")); + assertNull(eval("=InStr(3, Null, 'FOO')")); + + assertEquals(2, eval("=InStrRev('AFOOBAR', 'FOO')")); + assertEquals(2, eval("=InStrRev('AFOOBAR', 'foo')")); + assertEquals(2, eval("=InStrRev('AFOOBAR', 'foo', -1)")); + assertEquals(0, eval("=InStrRev('AFOOBAR', 'foo', -1, 0)")); + assertEquals(2, eval("=InStrRev('AFOOBAR', 'foo', -1, 1)")); + assertEquals(2, eval("=InStrRev('AFOOBAR', 'FOO', -1, 0)")); + assertEquals(2, eval("=InStrRev('AFOOBAR', 'FOO', 4)")); + assertEquals(0, eval("=InStrRev('AFOOBAR', 'FOO', 3)")); + assertEquals(2, eval("=InStrRev('AFOOBAR', 'FOO', 17)")); + assertEquals(2, eval("=InStrRev('AFOOBARFOOBAR', 'FOO', 9)")); + assertEquals(8, eval("=InStrRev('AFOOBARFOOBAR', 'FOO', 10)")); + assertNull(eval("=InStrRev(Null, 'FOO', 3)")); + + assertEquals("FOOO", eval("=UCase(\"fOoO\")")); + assertEquals("fooo", eval("=LCase(\"fOoO\")")); + + assertEquals("bl", eval("=Left(\"blah\", 2)")); + assertEquals("", eval("=Left(\"blah\", 0)")); + assertEquals("blah", eval("=Left(\"blah\", 17)")); + + assertEquals("ah", eval("=Right(\"blah\", 2)")); + assertEquals("", eval("=Right(\"blah\", 0)")); + assertEquals("blah", eval("=Right(\"blah\", 17)")); + + } + + + public void testFinancialFuncs() throws Exception + { + assertEquals("-9.57859403981317", + eval("=CStr(NPer(0.12/12,-100,-1000))")); + assertEquals("-9.48809500550583", + eval("=CStr(NPer(0.12/12,-100,-1000,0,1))")); + assertEquals("60.0821228537617", + eval("=CStr(NPer(0.12/12,-100,-1000,10000))")); + assertEquals("59.6738656742946", + eval("=CStr(NPer(0.12/12,-100,-1000,10000,1))")); + assertEquals("69.6607168935748", + eval("=CStr(NPer(0.12/12,-100,0,10000))")); + assertEquals("69.1619606798004", + eval("=CStr(NPer(0.12/12,-100,0,10000,1))")); + + assertEquals("8166.96698564091", + eval("=CStr(FV(0.12/12,60,-100))")); + assertEquals("8248.63665549732", + eval("=CStr(FV(0.12/12,60,-100,0,1))")); + assertEquals("6350.27028707682", + eval("=CStr(FV(0.12/12,60,-100,1000))")); + assertEquals("6431.93995693323", + eval("=CStr(FV(0.12/12,60,-100,1000,1))")); + + assertEquals("4495.5038406224", + eval("=CStr(PV(0.12/12,60,-100))")); + assertEquals("4540.45887902863", + eval("=CStr(PV(0.12/12,60,-100,0,1))")); + assertEquals("-1008.99231875519", + eval("=CStr(PV(0.12/12,60,-100,10000))")); + assertEquals("-964.037280348968", + eval("=CStr(PV(0.12/12,60,-100,10000,1))")); + + assertEquals("22.2444476849018", + eval("=CStr(Pmt(0.12/12,60,-1000))")); + assertEquals("22.0242056286156", + eval("=CStr(Pmt(0.12/12,60,-1000,0,1))")); + assertEquals("-100.200029164116", + eval("=CStr(Pmt(0.12/12,60,-1000,10000))")); + assertEquals("-99.2079496674414", + eval("=CStr(Pmt(0.12/12,60,-1000,10000,1))")); + assertEquals("-122.444476849018", + eval("=CStr(Pmt(0.12/12,60,0,10000))")); + assertEquals("-121.232155296057", + eval("=CStr(Pmt(0.12/12,60,0,10000,1))")); + + // FIXME not working for all param combos + // assertEquals("10.0", + // eval("=CStr(IPmt(0.12/12,1,60,-1000))")); + // assertEquals("5.904184782975672", + // eval("=CStr(IPmt(0.12/12,30,60,-1000))")); + // 0 + // assertEquals("", + // eval("=CStr(IPmt(0.12/12,1,60,-1000,0,1))")); + // 5.84572750... + // assertEquals("5.845727507896704", + // eval("=CStr(IPmt(0.12/12,30,60,-1000,0,1))")); + // 0 + // assertEquals("", + // eval("=CStr(IPmt(0.12/12,1,60,0,10000))")); + // 40.9581521702433 + // assertEquals("40.95815217024329", + // eval("=CStr(IPmt(0.12/12,30,60,0,10000))")); + // 0 + // assertEquals("", + // eval("=CStr(IPmt(0.12/12,1,60,0,10000,1))")); + // 40.552625911132 + // assertEquals("40.55262591113197", + // eval("=CStr(IPmt(0.12/12,30,60,0,10000,1))")); + // assertEquals("10.0", + // eval("=CStr(IPmt(0.12/12,1,60,-1000,10000))")); + // assertEquals("46.862336953218964", + // eval("=CStr(IPmt(0.12/12,30,60,-1000,10000))")); + // 0 + // assertEquals("", + // eval("=CStr(IPmt(0.12/12,1,60,-1000,10000,1))")); + // 46.3983534190287 + // assertEquals("46.39835341902867", + // eval("=CStr(IPmt(0.12/12,30,60,-1000,10000,1))")); + + // FIXME, doesn't work for partial days + // assertEquals("1.3150684931506849", + // eval("=CStr(DDB(2400,300,10*365,1))")); + // assertEquals("40.0", + // eval("=CStr(DDB(2400,300,10*12,1))")); + // assertEquals("480.0", + // eval("=CStr(DDB(2400,300,10,1))")); + // assertEquals("22.122547200000042", + // eval("=CStr(DDB(2400,300,10,10))")); + // assertEquals("245.76", + // eval("=CStr(DDB(2400,300,10,4))")); + // assertEquals("307.20000000000005", + // eval("=CStr(DDB(2400,300,10,3))")); + // assertEquals("480.0", + // eval("=CStr(DDB(2400,300,10,0.1))")); + // 274.768033075174 + // assertEquals("", + // eval("=CStr(DDB(2400,300,10,3.5))")); + + + } + +} diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java new file mode 100644 index 0000000..2f6e738 --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -0,0 +1,468 @@ +/* +Copyright (c) 2016 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.expr; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.script.Bindings; +import javax.script.SimpleBindings; + +import com.healthmarketscience.jackcess.DatabaseBuilder; +import com.healthmarketscience.jackcess.TestUtil; +import com.healthmarketscience.jackcess.expr.EvalContext; +import com.healthmarketscience.jackcess.expr.Expression; +import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.Identifier; +import com.healthmarketscience.jackcess.expr.TemporalConfig; +import com.healthmarketscience.jackcess.expr.Value; +import com.healthmarketscience.jackcess.impl.NumberFormatter; +import junit.framework.TestCase; + +/** + * + * @author James Ahlborn + */ +public class ExpressionatorTest extends TestCase +{ + private static final double[] DBLS = { + -10.3d,-9.0d,-8.234d,-7.11111d,-6.99999d,-5.5d,-4.0d,-3.4159265d,-2.84d, + -1.0000002d,-1.0d,-0.0002013d,0.0d, 0.9234d,1.0d,1.954d,2.200032d,3.001d, + 4.9321d,5.0d,6.66666d,7.396d,8.1d,9.20456200d,10.325d}; + + public ExpressionatorTest(String name) { + super(name); + } + + + public void testParseSimpleExprs() throws Exception + { + validateExpr("\"A\"", "<ELiteralValue>{\"A\"}"); + + validateExpr("13", "<ELiteralValue>{13}"); + + validateExpr("-42", "<EUnaryOp>{- <ELiteralValue>{42}}"); + + validateExpr("(+37)", "<EParen>{(<EUnaryOp>{+ <ELiteralValue>{37}})}"); + + doTestSimpleBinOp("EBinaryOp", "+", "-", "*", "/", "\\", "^", "&", "Mod"); + doTestSimpleBinOp("ECompOp", "<", "<=", ">", ">=", "=", "<>"); + doTestSimpleBinOp("ELogicalOp", "And", "Or", "Eqv", "Xor", "Imp"); + + for(String constStr : new String[]{"True", "False", "Null"}) { + validateExpr(constStr, "<EConstValue>{" + constStr + "}"); + } + + validateExpr("[Field1]", "<EObjValue>{[Field1]}"); + + validateExpr("[Table2].[Field3]", "<EObjValue>{[Table2].[Field3]}"); + + validateExpr("Not \"A\"", "<EUnaryOp>{Not <ELiteralValue>{\"A\"}}"); + + validateExpr("-[Field1]", "<EUnaryOp>{- <EObjValue>{[Field1]}}"); + + validateExpr("\"A\" Is Null", "<ENullOp>{<ELiteralValue>{\"A\"} Is Null}"); + + validateExpr("\"A\" In (1,2,3)", "<EInOp>{<ELiteralValue>{\"A\"} In (<ELiteralValue>{1},<ELiteralValue>{2},<ELiteralValue>{3})}"); + + validateExpr("\"A\" Not Between 3 And 7", "<EBetweenOp>{<ELiteralValue>{\"A\"} Not Between <ELiteralValue>{3} And <ELiteralValue>{7}}"); + + validateExpr("(\"A\" Or \"B\")", "<EParen>{(<ELogicalOp>{<ELiteralValue>{\"A\"} Or <ELiteralValue>{\"B\"}})}"); + + validateExpr("IIf(\"A\",42,False)", "<EFunc>{IIf(<ELiteralValue>{\"A\"},<ELiteralValue>{42},<EConstValue>{False})}"); + + validateExpr("\"A\" Like \"a*b\"", "<ELikeOp>{<ELiteralValue>{\"A\"} Like \"a*b\"(a.*b)}"); + + validateExpr("' \"A\" '", "<ELiteralValue>{\" \"\"A\"\" \"}", + "\" \"\"A\"\" \""); + } + + private static void doTestSimpleBinOp(String opName, String... ops) throws Exception + { + for(String op : ops) { + validateExpr("\"A\" " + op + " \"B\"", + "<" + opName + ">{<ELiteralValue>{\"A\"} " + op + + " <ELiteralValue>{\"B\"}}"); + } + } + + public void testOrderOfOperations() throws Exception + { + validateExpr("\"A\" Eqv \"B\"", + "<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELiteralValue>{\"B\"}}"); + + validateExpr("\"A\" Eqv \"B\" Xor \"C\"", + "<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELogicalOp>{<ELiteralValue>{\"B\"} Xor <ELiteralValue>{\"C\"}}}"); + + validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\"", + "<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELogicalOp>{<ELiteralValue>{\"B\"} Xor <ELogicalOp>{<ELiteralValue>{\"C\"} Or <ELiteralValue>{\"D\"}}}}"); + + validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\" And \"E\"", + "<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELogicalOp>{<ELiteralValue>{\"B\"} Xor <ELogicalOp>{<ELiteralValue>{\"C\"} Or <ELogicalOp>{<ELiteralValue>{\"D\"} And <ELiteralValue>{\"E\"}}}}}"); + + validateExpr("\"A\" Or \"B\" Or \"C\"", + "<ELogicalOp>{<ELogicalOp>{<ELiteralValue>{\"A\"} Or <ELiteralValue>{\"B\"}} Or <ELiteralValue>{\"C\"}}"); + + validateExpr("\"A\" & \"B\" Is Null", + "<ENullOp>{<EBinaryOp>{<ELiteralValue>{\"A\"} & <ELiteralValue>{\"B\"}} Is Null}"); + + validateExpr("\"A\" Or \"B\" Is Null", + "<ELogicalOp>{<ELiteralValue>{\"A\"} Or <ENullOp>{<ELiteralValue>{\"B\"} Is Null}}"); + + validateExpr("Not \"A\" & \"B\"", + "<EUnaryOp>{Not <EBinaryOp>{<ELiteralValue>{\"A\"} & <ELiteralValue>{\"B\"}}}"); + + validateExpr("Not \"A\" Or \"B\"", + "<ELogicalOp>{<EUnaryOp>{Not <ELiteralValue>{\"A\"}} Or <ELiteralValue>{\"B\"}}"); + + validateExpr("\"A\" + \"B\" Not Between 37 - 15 And 52 / 4", + "<EBetweenOp>{<EBinaryOp>{<ELiteralValue>{\"A\"} + <ELiteralValue>{\"B\"}} Not Between <EBinaryOp>{<ELiteralValue>{37} - <ELiteralValue>{15}} And <EBinaryOp>{<ELiteralValue>{52} / <ELiteralValue>{4}}}"); + + validateExpr("\"A\" + (\"B\" Not Between 37 - 15 And 52) / 4", + "<EBinaryOp>{<ELiteralValue>{\"A\"} + <EBinaryOp>{<EParen>{(<EBetweenOp>{<ELiteralValue>{\"B\"} Not Between <EBinaryOp>{<ELiteralValue>{37} - <ELiteralValue>{15}} And <ELiteralValue>{52}})} / <ELiteralValue>{4}}}"); + + + } + + public void testSimpleMathExpressions() throws Exception + { + for(int i = -10; i <= 10; ++i) { + assertEquals(-i, eval("=-(" + i + ")")); + } + + for(int i = -10; i <= 10; ++i) { + assertEquals(i, eval("=+(" + i + ")")); + } + + for(double i : DBLS) { + assertEquals(toBD(-i), eval("=-(" + i + ")")); + } + + for(double i : DBLS) { + assertEquals(toBD(i), eval("=+(" + i + ")")); + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + assertEquals((i + j), eval("=" + i + " + " + j)); + } + } + + for(double i : DBLS) { + for(double j : DBLS) { + assertEquals(toBD(toBD(i).add(toBD(j))), eval("=" + i + " + " + j)); + } + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + assertEquals((i - j), eval("=" + i + " - " + j)); + } + } + + for(double i : DBLS) { + for(double j : DBLS) { + assertEquals(toBD(toBD(i).subtract(toBD(j))), eval("=" + i + " - " + j)); + } + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + assertEquals((i * j), eval("=" + i + " * " + j)); + } + } + + for(double i : DBLS) { + for(double j : DBLS) { + assertEquals(toBD(toBD(i).multiply(toBD(j))), eval("=" + i + " * " + j)); + } + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + if(j == 0L) { + evalFail("=" + i + " \\ " + j, ArithmeticException.class); + } else { + assertEquals((i / j), eval("=" + i + " \\ " + j)); + } + } + } + + for(double i : DBLS) { + for(double j : DBLS) { + if(roundToLongInt(j) == 0) { + evalFail("=" + i + " \\ " + j, ArithmeticException.class); + } else { + assertEquals((roundToLongInt(i) / roundToLongInt(j)), + eval("=" + i + " \\ " + j)); + } + } + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + if(j == 0) { + evalFail("=" + i + " Mod " + j, ArithmeticException.class); + } else { + assertEquals((i % j), eval("=" + i + " Mod " + j)); + } + } + } + + for(double i : DBLS) { + for(double j : DBLS) { + if(roundToLongInt(j) == 0) { + evalFail("=" + i + " Mod " + j, ArithmeticException.class); + } else { + assertEquals((roundToLongInt(i) % roundToLongInt(j)), + eval("=" + i + " Mod " + j)); + } + } + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + if(j == 0) { + evalFail("=" + i + " / " + j, ArithmeticException.class); + } else { + double result = (double)i / (double)j; + if((int)result == result) { + assertEquals((int)result, eval("=" + i + " / " + j)); + } else { + assertEquals(result, eval("=" + i + " / " + j)); + } + } + } + } + + for(double i : DBLS) { + for(double j : DBLS) { + if(j == 0.0d) { + evalFail("=" + i + " / " + j, ArithmeticException.class); + } else { + assertEquals(toBD(BuiltinOperators.divide(toBD(i), toBD(j))), + eval("=" + i + " / " + j)); + } + } + } + + for(int i = -10; i <= 10; ++i) { + for(int j = -10; j <= 10; ++j) { + double result = Math.pow(i, j); + if((int)result == result) { + assertEquals((int)result, eval("=" + i + " ^ " + j)); + } else { + assertEquals(result, eval("=" + i + " ^ " + j)); + } + } + } + } + + public void testTrickyMathExpressions() throws Exception + { + assertEquals(37, eval("=30+7")); + assertEquals(23, eval("=30+-7")); + assertEquals(23, eval("=30-+7")); + assertEquals(37, eval("=30--7")); + assertEquals(23, eval("=30-7")); + + assertEquals(100, eval("=-10^2")); + assertEquals(-100, eval("=-(10)^2")); + assertEquals(-100d, eval("=-\"10\"^2")); + assertEquals(toBD(-98.9d), eval("=1.1+(-\"10\"^2)")); + + assertEquals(toBD(99d), eval("=-10E-1+10e+1")); + assertEquals(toBD(-101d), eval("=-10E-1-10e+1")); + } + + public void testTypeCoercion() throws Exception + { + assertEquals("foobar", eval("=\"foo\" + \"bar\"")); + + assertEquals("12foo", eval("=12 + \"foo\"")); + assertEquals("foo12", eval("=\"foo\" + 12")); + + assertEquals(37d, eval("=\"25\" + 12")); + assertEquals(37d, eval("=12 + \"25\"")); + + evalFail(("=12 - \"foo\""), RuntimeException.class); + evalFail(("=\"foo\" - 12"), RuntimeException.class); + + assertEquals("foo1225", eval("=\"foo\" + 12 + 25")); + assertEquals("37foo", eval("=12 + 25 + \"foo\"")); + assertEquals("foo37", eval("=\"foo\" + (12 + 25)")); + assertEquals("25foo12", eval("=\"25foo\" + 12")); + + assertEquals(new Date(1485579600000L), eval("=#1/1/2017# + 27")); + assertEquals(128208, eval("=#1/1/2017# * 3")); + } + + public void testLikeExpression() throws Exception + { + validateExpr("Like \"[abc]*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc]*\"([abc].*)}", + "<THIS_COL> Like \"[abc]*\""); + assertTrue(evalCondition("Like \"[abc]*\"", "afcd")); + assertFalse(evalCondition("Like \"[abc]*\"", "fcd")); + + validateExpr("Like \"[abc*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc*\"((?!))}", + "<THIS_COL> Like \"[abc*\""); + assertFalse(evalCondition("Like \"[abc*\"", "afcd")); + assertFalse(evalCondition("Like \"[abc*\"", "fcd")); + assertFalse(evalCondition("Like \"[abc*\"", "")); + } + + public void testLiteralDefaultValue() throws Exception + { + assertEquals("-28 blah ", eval("=CDbl(9)-37 & \" blah \"", + Value.Type.STRING)); + assertEquals("CDbl(9)-37 & \" blah \"", + eval("CDbl(9)-37 & \" blah \"", Value.Type.STRING)); + + assertEquals(-28d, eval("=CDbl(9)-37", Value.Type.DOUBLE)); + assertEquals(-28d, eval("CDbl(9)-37", Value.Type.DOUBLE)); + } + + private static void validateExpr(String exprStr, String debugStr) { + validateExpr(exprStr, debugStr, exprStr); + } + + private static void validateExpr(String exprStr, String debugStr, + String cleanStr) { + Expression expr = Expressionator.parse( + Expressionator.Type.FIELD_VALIDATOR, exprStr, null, null); + String foundDebugStr = expr.toDebugString(); + if(foundDebugStr.startsWith("<EImplicitCompOp>")) { + assertEquals("<EImplicitCompOp>{<EThisValue>{<THIS_COL>} = " + + debugStr + "}", foundDebugStr); + } else { + assertEquals(debugStr, foundDebugStr); + } + assertEquals(cleanStr, expr.toString()); + } + + static Object eval(String exprStr) { + return eval(exprStr, null); + } + + static Object eval(String exprStr, Value.Type resultType) { + Expression expr = Expressionator.parse( + Expressionator.Type.DEFAULT_VALUE, exprStr, resultType, + new TestParseContext()); + return expr.eval(new TestEvalContext(null)); + } + + private static void evalFail( + String exprStr, Class<? extends Exception> failure) + { + Expression expr = Expressionator.parse( + Expressionator.Type.DEFAULT_VALUE, exprStr, null, + new TestParseContext()); + try { + expr.eval(new TestEvalContext(null)); + fail(failure + " should have been thrown"); + } catch(Exception e) { + assertTrue(failure.isInstance(e)); + } + } + + private static Boolean evalCondition(String exprStr, String thisVal) { + Expression expr = Expressionator.parse( + Expressionator.Type.FIELD_VALIDATOR, exprStr, null, new TestParseContext()); + return (Boolean)expr.eval(new TestEvalContext(BuiltinOperators.toValue(thisVal))); + } + + static int roundToLongInt(double d) { + return new BigDecimal(d).setScale(0, NumberFormatter.ROUND_MODE) + .intValueExact(); + } + + static BigDecimal toBD(double d) { + return toBD(BigDecimal.valueOf(d)); + } + + static BigDecimal toBD(BigDecimal bd) { + return BuiltinOperators.normalize(bd); + } + + private static final class TestParseContext implements Expressionator.ParseContext + { + public TemporalConfig getTemporalConfig() { + return TemporalConfig.US_TEMPORAL_CONFIG; + } + public SimpleDateFormat createDateFormat(String formatStr) { + SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr); + sdf.setTimeZone(TestUtil.TEST_TZ); + return sdf; + } + + public Function getExpressionFunction(String name) { + return DefaultFunctions.getFunction(name); + } + } + + private static final class TestEvalContext implements EvalContext + { + private final Value _thisVal; + private final RandomContext _rndCtx = new RandomContext(); + private final Bindings _bindings = new SimpleBindings(); + + private TestEvalContext(Value thisVal) { + _thisVal = thisVal; + } + + public Value.Type getResultType() { + return null; + } + + public TemporalConfig getTemporalConfig() { + return TemporalConfig.US_TEMPORAL_CONFIG; + } + + public SimpleDateFormat createDateFormat(String formatStr) { + SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr); + sdf.setTimeZone(TestUtil.TEST_TZ); + return sdf; + } + + public Value getThisColumnValue() { + if(_thisVal == null) { + throw new UnsupportedOperationException(); + } + return _thisVal; + } + + public Value getIdentifierValue(Identifier identifier) { + throw new UnsupportedOperationException(); + } + + public float getRandom(Integer seed) { + return _rndCtx.getRandom(seed); + } + + public Bindings getBindings() { + return _bindings; + } + + public Object get(String key) { + return _bindings.get(key); + } + + public void put(String key, Object value) { + _bindings.put(key, value); + } + } +} |