Browse Source

Add full support for access 2016 databases including the new 'Large Number' (aka Bigint) data type. Fixes feature #37

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1130 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.1.10
James Ahlborn 6 years ago
parent
commit
4fb8409266

+ 1
- 1
README.txt View File

@@ -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).

+ 7
- 0
src/changes/changes.xml View File

@@ -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.

+ 7
- 0
src/main/java/com/healthmarketscience/jackcess/DataType.java View File

@@ -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}.

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/Database.java View File

@@ -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");


+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java View File

@@ -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
*/

+ 8
- 0
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -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);

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/ComplexColumnSupport.java View File

@@ -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);


/**

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java View File

@@ -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;

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java View File

@@ -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
- 12
src/main/java/com/healthmarketscience/jackcess/impl/JetFormat.java View File

@@ -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);
}
}

}

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java View File

@@ -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 +

BIN
src/main/resources/com/healthmarketscience/jackcess/empty2016.accdb View File


+ 1
- 1
src/site/fml/faq.fml View File

@@ -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>

+ 1
- 1
src/site/xdoc/cookbook.xml View File

@@ -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

+ 1
- 1
src/site/xdoc/index.xml View File

@@ -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).

+ 102
- 0
src/test/java/com/healthmarketscience/jackcess/impl/BigIntTest.java View File

@@ -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();
}
}
}

Loading…
Cancel
Save