diff options
16 files changed, 192 insertions, 19 deletions
@@ -1,7 +1,7 @@ Jackcess Jackcess is a pure Java library for reading from and writing to MS Access -databases (currently supporting versions 2000-2013). It is not an +databases (currently supporting versions 2000-2016). It is not an application. There is no GUI. It's a library, intended for other developers to use to build Java applications. Jackcess is licensed under the Apache License (as of version 2.1.0). diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1825256..0ff4525 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -4,6 +4,13 @@ <author email="javajedi@users.sf.net">Tim McCune</author> </properties> <body> + <release version="2.1.10" date="TBD"> + <action dev="jahlborn" type="update" system="SourceForge2Features" + issue="37"> + Add full support for access 2016 databases including the new "Large + Number" (aka Bigint) data type. + </action> + </release> <release version="2.1.9" date="2017-10-09"> <action dev="jahlborn" type="fix" system="SourceForge2" issue="141"> Handle more advanced query join constructs. diff --git a/src/main/java/com/healthmarketscience/jackcess/DataType.java b/src/main/java/com/healthmarketscience/jackcess/DataType.java index 5a5cebe..9baaba6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/DataType.java +++ b/src/main/java/com/healthmarketscience/jackcess/DataType.java @@ -152,6 +152,13 @@ public enum DataType { */ COMPLEX_TYPE((byte) 0x12, null, 4), /** + * Corresponds to a java {@link Long}. Accepts any {@link Number} (using + * {@link Number#longValue}), Boolean as 1 or 0, any Object converted to a + * String and parsed as Double, or {@code null}. Equivalent to SQL + * {@link Types#BIGINT}. + */ + BIG_INT((byte) 0x13, null, 8), + /** * Dummy type for a fixed length type which is not currently supported. * Handled like a fixed length {@link #BINARY}. */ diff --git a/src/main/java/com/healthmarketscience/jackcess/Database.java b/src/main/java/com/healthmarketscience/jackcess/Database.java index 42eb216..5644d09 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Database.java +++ b/src/main/java/com/healthmarketscience/jackcess/Database.java @@ -143,6 +143,8 @@ public interface Database extends Iterable<Table>, Closeable, Flushable V2007(".accdb"), /** A database which was created by MS Access 2010+ */ V2010(".accdb"), + /** A database which was created by MS Access 2016+ */ + V2016(".accdb"), /** A database which was created by MS Money */ MSISAM(".mny"); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java index f3fdc21..e77963f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java @@ -26,7 +26,7 @@ import java.nio.ByteOrder; * Utility code for dealing with calculated columns. * <p/> * These are the currently possible calculated types: FLOAT, DOUBLE, INT, - * LONG, GUID, SHORT_DATE_TIME, MONEY, BOOLEAN, NUMERIC, TEXT, MEMO. + * LONG, BIG_INT, GUID, SHORT_DATE_TIME, MONEY, BOOLEAN, NUMERIC, TEXT, MEMO. * * @author James Ahlborn */ diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index c5d0449..b5a0d0e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -632,6 +632,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return data; case COMPLEX_TYPE: return new ComplexValueForeignKeyImpl(this, buffer.getInt()); + case BIG_INT: + return Long.valueOf(buffer.getLong()); default: throw new IOException(withErrorContext("Unrecognized data type: " + _type)); } @@ -1128,6 +1130,9 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { case COMPLEX_TYPE: buffer.putInt(toNumber(obj).intValue()); break; + case BIG_INT: + buffer.putLong(toNumber(obj).longValue()); + break; case UNSUPPORTED_FIXEDLEN: byte[] bytes = toByteArray(obj); if(bytes.length != getLength()) { @@ -1800,6 +1805,9 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { case COMPLEX_TYPE: // leave alone for now? return value; + case BIG_INT: + return ((value instanceof Long) ? value : + toNumber(value).longValue()); default: // some variation of binary data return toByteArray(value); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ComplexColumnSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/ComplexColumnSupport.java index 954791b..93dbf03 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ComplexColumnSupport.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ComplexColumnSupport.java @@ -53,7 +53,8 @@ public class ComplexColumnSupport private static final Set<DataType> MULTI_VALUE_TYPES = EnumSet.of( DataType.BYTE, DataType.INT, DataType.LONG, DataType.FLOAT, - DataType.DOUBLE, DataType.GUID, DataType.NUMERIC, DataType.TEXT); + DataType.DOUBLE, DataType.GUID, DataType.NUMERIC, DataType.TEXT, + DataType.BIG_INT); /** diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index 15c79e0..cc76ccb 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -116,6 +116,7 @@ public class DatabaseImpl implements Database addFileFormatDetails(FileFormat.V2003, "empty2003", JetFormat.VERSION_4); addFileFormatDetails(FileFormat.V2007, "empty2007", JetFormat.VERSION_12); addFileFormatDetails(FileFormat.V2010, "empty2010", JetFormat.VERSION_14); + addFileFormatDetails(FileFormat.V2016, "empty2016", JetFormat.VERSION_16); addFileFormatDetails(FileFormat.MSISAM, null, JetFormat.VERSION_MSISAM); } @@ -182,7 +183,7 @@ public class DatabaseImpl implements Database private static final int DB_PARENT_ID = 0xF000000; /** the maximum size of any of the included "empty db" resources */ - private static final long MAX_EMPTYDB_SIZE = 350000L; + private static final long MAX_EMPTYDB_SIZE = 370000L; /** this object is a "system" object */ static final int SYSTEM_OBJECT_FLAG = 0x80000000; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index e7ae7d5..e2b2dd5 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -1528,6 +1528,7 @@ public class IndexData { case LONG: case MONEY: case COMPLEX_TYPE: + case BIG_INT: return new IntegerColumnDescriptor(col, flags); case FLOAT: case DOUBLE: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/JetFormat.java b/src/main/java/com/healthmarketscience/jackcess/impl/JetFormat.java index ba848e4..a230edf 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/JetFormat.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/JetFormat.java @@ -54,10 +54,12 @@ public abstract class JetFormat { private static final byte CODE_VERSION_3 = 0x0; /** Version code for Jet version 4 */ private static final byte CODE_VERSION_4 = 0x1; - /** Version code for Jet version 12 */ + /** Version code for Jet version 12.0 */ private static final byte CODE_VERSION_12 = 0x2; - /** Version code for Jet version 14 */ + /** Version code for Jet version 14.0 */ private static final byte CODE_VERSION_14 = 0x3; + /** Version code for Jet version 16.7 */ + private static final byte CODE_VERSION_16 = 0x5; /** location of the engine name in the header */ static final int OFFSET_ENGINE_NAME = 0x4; @@ -121,6 +123,9 @@ public abstract class JetFormat { private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_14 = Collections.singletonMap((String)null, Database.FileFormat.V2010); + private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_16 = + Collections.singletonMap((String)null, Database.FileFormat.V2016); + private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_MSISAM = Collections.singletonMap((String)null, Database.FileFormat.MSISAM); @@ -138,16 +143,24 @@ public abstract class JetFormat { DataType.SHORT_DATE_TIME, DataType.MONEY, DataType.NUMERIC, DataType.TEXT, DataType.MEMO); + /** calculated types supported in version 16 */ + private static final Set<DataType> V16_CALC_TYPES = EnumSet.of(DataType.BIG_INT); + static { + V16_CALC_TYPES.addAll(V14_CALC_TYPES); + } + /** the JetFormat constants for the Jet database version "3" */ public static final JetFormat VERSION_3 = new Jet3Format(); /** the JetFormat constants for the Jet database version "4" */ public static final JetFormat VERSION_4 = new Jet4Format(); /** the JetFormat constants for the MSISAM database */ public static final JetFormat VERSION_MSISAM = new MSISAMFormat(); - /** the JetFormat constants for the Jet database version "12" */ + /** the JetFormat constants for the Jet database version "12.0" */ public static final JetFormat VERSION_12 = new Jet12Format(); - /** the JetFormat constants for the Jet database version "14" */ + /** the JetFormat constants for the Jet database version "14.0" */ public static final JetFormat VERSION_14 = new Jet14Format(); + /** the JetFormat constants for the Jet database version "16.7" */ + public static final JetFormat VERSION_16 = new Jet16Format(); //These constants are populated by this class's constructor. They can't be //populated by the subclass's constructor because they are final, and Java @@ -289,6 +302,8 @@ public abstract class JetFormat { return VERSION_12; } else if (version == CODE_VERSION_14) { return VERSION_14; + } else if (version == CODE_VERSION_16) { + return VERSION_16; } throw new IOException("Unsupported " + ((version < CODE_VERSION_3) ? "older" : "newer") + @@ -738,7 +753,8 @@ public abstract class JetFormat { @Override public boolean isSupportedDataType(DataType type) { - return (type != DataType.COMPLEX_TYPE); + return ((type != DataType.COMPLEX_TYPE) && + (type != DataType.BIG_INT)); } @Override @@ -971,7 +987,8 @@ public abstract class JetFormat { @Override public boolean isSupportedDataType(DataType type) { - return (type != DataType.COMPLEX_TYPE); + return ((type != DataType.COMPLEX_TYPE) && + (type != DataType.BIG_INT)); } @Override @@ -1002,7 +1019,6 @@ public abstract class JetFormat { super("VERSION_12"); } - private Jet12Format(String name) { super(name); } @@ -1028,7 +1044,7 @@ public abstract class JetFormat { @Override public boolean isSupportedDataType(DataType type) { - return true; + return (type != DataType.BIG_INT); } @Override @@ -1037,10 +1053,14 @@ public abstract class JetFormat { } } - private static final class Jet14Format extends Jet12Format { - private Jet14Format() { - super("VERSION_14"); - } + private static class Jet14Format extends Jet12Format { + private Jet14Format() { + super("VERSION_14"); + } + + private Jet14Format(String name) { + super(name); + } @Override protected ColumnImpl.SortOrder defineDefaultSortOrder() { @@ -1063,4 +1083,26 @@ public abstract class JetFormat { } } + private static final class Jet16Format extends Jet14Format { + + private Jet16Format() { + super("VERSION_16"); + } + + @Override + public boolean isSupportedDataType(DataType type) { + return true; + } + + @Override + protected Map<String,Database.FileFormat> getPossibleFileFormats() { + return PossibleFileFormats.POSSIBLE_VERSION_16; + } + + @Override + public boolean isSupportedCalculatedDataType(DataType type) { + return V16_CALC_TYPES.contains(type); + } + } + } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java index 619da07..9ec5fca 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java @@ -204,6 +204,8 @@ public class PropertyMapImpl implements PropertyMap type = DataType.SHORT_DATE_TIME; } else if(value instanceof byte[]) { type = DataType.OLE; + } else if(value instanceof Long) { + type = DataType.BIG_INT; } else { throw new IllegalArgumentException( "Could not determine type for property " + name + diff --git a/src/main/resources/com/healthmarketscience/jackcess/empty2016.accdb b/src/main/resources/com/healthmarketscience/jackcess/empty2016.accdb Binary files differnew file mode 100644 index 0000000..94f4e08 --- /dev/null +++ b/src/main/resources/com/healthmarketscience/jackcess/empty2016.accdb diff --git a/src/site/fml/faq.fml b/src/site/fml/faq.fml index 769936b..18bd093 100644 --- a/src/site/fml/faq.fml +++ b/src/site/fml/faq.fml @@ -16,7 +16,7 @@ <faq id="formats"> <question>What Access formats does it support?</question> <answer> - <p>Jackcess supports Access database versions 2000-2013 read/write and + <p>Jackcess supports Access database versions 2000-2016 read/write and Access 97 read-only.</p> </answer> </faq> diff --git a/src/site/xdoc/cookbook.xml b/src/site/xdoc/cookbook.xml index fca2519..5e083af 100644 --- a/src/site/xdoc/cookbook.xml +++ b/src/site/xdoc/cookbook.xml @@ -195,7 +195,7 @@ As of version 2.1.5, Jackcess supports: </p> <ul> - <li>Creating databases for Access all versions 2000-2013</li> + <li>Creating databases for Access all versions 2000-2016</li> <li>Creating columns for all simple data types</li> <li>Creating tables with single-table Indexes</li> <li>Creating tables with (index backed) foreign-key constraints diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index bb8b877..7f23a71 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -10,7 +10,7 @@ <section name="Jackcess"> <p> Jackcess is a pure Java library for reading from and writing to MS - Access databases (currently supporting versions 2000-2013). It is part of the <a href="http://openhms.sourceforge.net/">OpenHMS</a> project from <a href="http://www.healthmarketscience.com/">Health Market Science, Inc.</a>. It is not an application. There is no GUI. It's a + Access databases (currently supporting versions 2000-2016). It is part of the <a href="http://openhms.sourceforge.net/">OpenHMS</a> project from <a href="http://www.healthmarketscience.com/">Health Market Science, Inc.</a>. It is not an application. There is no GUI. It's a library, intended for other developers to use to build Java applications. Jackcess is licensed under the <a href="http://www.apache.org/licenses/LICENSE-2.0.txt">Apache License</a> (as of version 2.1.0). diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/BigIntTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/BigIntTest.java new file mode 100644 index 0000000..143ac9b --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/impl/BigIntTest.java @@ -0,0 +1,102 @@ +/* +Copyright (c) 2017 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.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import com.healthmarketscience.jackcess.Column; +import com.healthmarketscience.jackcess.ColumnBuilder; +import com.healthmarketscience.jackcess.Cursor; +import com.healthmarketscience.jackcess.CursorBuilder; +import com.healthmarketscience.jackcess.DataType; +import com.healthmarketscience.jackcess.Database; +import com.healthmarketscience.jackcess.IndexBuilder; +import com.healthmarketscience.jackcess.Table; +import com.healthmarketscience.jackcess.TableBuilder; +import junit.framework.TestCase; +import static com.healthmarketscience.jackcess.TestUtil.*; +import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; + +/** + * + * @author James Ahlborn + */ +public class BigIntTest extends TestCase +{ + + public BigIntTest(String name) throws Exception { + super(name); + } + + public void testBigInt() throws Exception { + + for (final Database.FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + JetFormat format = DatabaseImpl.getFileFormatDetails(fileFormat) + .getFormat(); + + if(!format.isSupportedDataType(DataType.BIG_INT)) { + continue; + } + + Database db = create(fileFormat); + + Table t = new TableBuilder("Test") + .addColumn(new ColumnBuilder("id", DataType.LONG) + .setAutoNumber(true)) + .addColumn(new ColumnBuilder("data1", DataType.TEXT)) + .addColumn(new ColumnBuilder("num1", DataType.BIG_INT)) + .addIndex(new IndexBuilder("idx").addColumns("num1")) + .toTable(db); + + long[] vals = new long[] { + 0L, -10L, 3844L, -45309590834L, 50392084913L, 65000L, -6489273L}; + + List<Map<String, Object>> expectedTable = + new ArrayList<Map<String, Object>>(); + + int idx = 1; + for(long lng : vals) { + t.addRow(Column.AUTO_NUMBER, "" + lng, lng); + + expectedTable.add(createExpectedRow( + "id", idx++, + "data1", "" + lng, + "num1", lng)); + } + + Collections.sort(expectedTable, new Comparator<Map<String, Object>>() { + public int compare( + Map<String, Object> r1, + Map<String, Object> r2) { + Long l1 = (Long)r1.get("num1"); + Long l2 = (Long)r2.get("num1"); + return l1.compareTo(l2); + } + }); + + Cursor c = new CursorBuilder(t).setIndexByName("idx").toIndexCursor(); + + assertCursor(expectedTable, c); + + db.close(); + } + } +} |