git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1130 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.1.10
@@ -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). |
@@ -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. |
@@ -151,6 +151,13 @@ public enum DataType { | |||
* which is the key for a secondary table which holds the "real" data. | |||
*/ | |||
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}. |
@@ -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"); | |||
@@ -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 | |||
*/ |
@@ -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); |
@@ -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); | |||
/** |
@@ -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; |
@@ -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: |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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 + |
@@ -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> |
@@ -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 |
@@ -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). |
@@ -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(); | |||
} | |||
} | |||
} |