Browse Source

Add support for extended date/time type in access 2019+ dbs

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1365 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-4.0.1
James Ahlborn 2 years ago
parent
commit
9706c1f1e5

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

@@ -11,6 +11,9 @@
<action dev="jahlborn" type="update">
Add basic support for access 2019+ dbs.
</action>
<action dev="jahlborn" type="update">
Add support for extended date/time type in access 2019+ dbs.
</action>
</release>
<release version="4.0.0" date="2021-01-20">
<action dev="jahlborn" type="update">

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

@@ -161,6 +161,14 @@ public enum DataType {
* {@link Types#BIGINT}.
*/
BIG_INT((byte) 0x13, Types.BIGINT, 8),
/**
* Corresponds to a java {@link LocalDateTime} (with 7 digits of nanosecond
* precision). Accepts a Date, LocalDateTime (or related types), any
* {@link Number} (using {@link Number#longValue}), or {@code null}.
* Equivalent to SQL {@link Types#TIMESTAMP}, {@link Types#DATE},
* {@link Types#TIME}.
*/
EXT_DATE_TIME((byte) 0x14, null, 42),
/**
* Dummy type for a fixed length type which is not currently supported.
* Handled like a fixed length {@link #BINARY}.

+ 103
- 7
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -37,6 +37,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Calendar;
@@ -109,6 +110,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
public static final LocalTime BASE_LT = LocalTime.of(0, 0);
public static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT);

private static final LocalDate BASE_EXT_LD = LocalDate.of(1, 1, 1);
private static final LocalTime BASE_EXT_LT = LocalTime.of(0, 0);
private static final LocalDateTime BASE_EXT_LDT =
LocalDateTime.of(BASE_EXT_LD, BASE_EXT_LT);
private static final byte[] EXT_LDT_TRAILER = {':', '7', 0x00};

private static final DateTimeFactory DEF_DATE_TIME_FACTORY =
new DefaultDateTimeFactory();

@@ -808,6 +815,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
return readNumericValue(buffer);
case GUID:
return readGUIDValue(buffer, order);
case EXT_DATE_TIME:
return readExtendedDateValue(buffer);
case UNKNOWN_0D:
case UNKNOWN_11:
// treat like "binary" data
@@ -966,6 +975,37 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
return getDateTimeFactory().fromDateBits(this, dateBits);
}

/**
* Decodes an "extended" date/time value.
*/
private static Object readExtendedDateValue(ByteBuffer buffer) {
// format: <19digits>:<19digits>:7 0x00
long numDays = readExtDateLong(buffer, 19);
buffer.get();
long seconds = readExtDateLong(buffer, 12);
// there are 7 fractional digits
long nanos = readExtDateLong(buffer, 7) * 100L;
ByteUtil.forward(buffer, EXT_LDT_TRAILER.length);

return BASE_EXT_LDT
.plusDays(numDays)
.plusSeconds(seconds)
.plusNanos(nanos);
}

/**
* Reads the given number of ascii encoded characters as a long value.
*/
private static long readExtDateLong(ByteBuffer buffer, int numChars) {
long val = 0L;
for(int i = 0; i < numChars; ++i) {
char digit = (char)buffer.get();
long inc = digit - '0';
val = (val * 10L) + inc;
}
return val;
}

/**
* Returns a java long time value converted from an access date double.
* @usage _advanced_method_
@@ -1034,6 +1074,51 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
}
}

/**
* Writes an "extended" date/time value.
*/
private void writeExtendedDateValue(ByteBuffer buffer, Object value)
throws InvalidValueException
{
LocalDateTime ldt = BASE_EXT_LDT;
if(value != null) {
ldt = toLocalDateTime(value, this);
}

LocalDate ld = ldt.toLocalDate();
LocalTime lt = ldt.toLocalTime();

long numDays = BASE_EXT_LD.until(ld, ChronoUnit.DAYS);
long numSeconds = BASE_EXT_LT.until(lt, ChronoUnit.SECONDS);
long nanos = lt.getNano();

// format: <19digits>:<19digits>:7 0x00
writeExtDateLong(buffer, numDays, 19);
buffer.put((byte)':');
writeExtDateLong(buffer, numSeconds, 12);
// there are 7 fractional digits
writeExtDateLong(buffer, (nanos / 100L), 7);

buffer.put(EXT_LDT_TRAILER);
}

/**
* Writes the given long value as the given number of ascii encoded
* characters.
*/
private static void writeExtDateLong(
ByteBuffer buffer, long val, int numChars) {
// we write the desired number of digits in reverse order
int end = buffer.position();
int start = end + numChars - 1;
for(int i = start; i >= end; --i) {
char digit = (char)('0' + (char)(val % 10L));
buffer.put(i, (byte)digit);
val /= 10L;
}
ByteUtil.forward(buffer, numChars);
}

/**
* Returns an access date double converted from a java Date/Calendar/Number
* time value.
@@ -1059,6 +1144,15 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
}

private static LocalDateTime toLocalDateTime(
Object value, DateTimeContext dtc) {
if(value instanceof TemporalAccessor) {
return temporalToLocalDateTime((TemporalAccessor)value, dtc);
}
Instant inst = Instant.ofEpochMilli(toDateLong(value));
return LocalDateTime.ofInstant(inst, dtc.getZoneId());
}

private static LocalDateTime temporalToLocalDateTime(
TemporalAccessor value, DateTimeContext dtc) {

// handle some common Temporal types
@@ -1117,7 +1211,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
if(value instanceof Instant) {
return (Instant)value;
}
return toLocalDateTime(value, dtc).atZone(dtc.getZoneId()).toInstant();
return temporalToLocalDateTime(value, dtc).atZone(dtc.getZoneId())
.toInstant();
}

static double toLocalDateDouble(long time) {
@@ -1464,6 +1559,9 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
case BIG_INT:
buffer.putLong(toNumber(obj).longValue());
break;
case EXT_DATE_TIME:
writeExtendedDateValue(buffer, obj);
break;
case UNSUPPORTED_FIXEDLEN:
byte[] bytes = toByteArray(obj);
if(bytes.length != getLength()) {
@@ -2188,6 +2286,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
case BIG_INT:
return ((value instanceof Long) ? value :
toNumber(value, db).longValue());
case EXT_DATE_TIME:
return toLocalDateTime(value, db);
default:
// some variation of binary data
return toByteArray(value);
@@ -2756,16 +2856,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeConte
value = Instant.ofEpochMilli(toDateLong(value));
}
return ColumnImpl.toDateDouble(
toLocalDateTime((TemporalAccessor)value, dtc));
temporalToLocalDateTime((TemporalAccessor)value, dtc));
}

@Override
public Object toInternalValue(DatabaseImpl db, Object value) {
if(value instanceof TemporalAccessor) {
return toLocalDateTime((TemporalAccessor)value, db);
}
Instant inst = Instant.ofEpochMilli(toDateLong(value));
return LocalDateTime.ofInstant(inst, db.getZoneId());
return toLocalDateTime(value, db);
}
}


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

@@ -73,6 +73,11 @@ public class IndexData {

protected static final byte[] EMPTY_PREFIX = new byte[0];

private static final byte[] ASC_EXT_DATE_TRAILER =
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
private static final byte[] DESC_EXT_DATE_TRAILER =
flipBytes(ByteUtil.copyOf(ASC_EXT_DATE_TRAILER, ASC_EXT_DATE_TRAILER.length));

static final short COLUMN_UNUSED = -1;

public static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
@@ -1550,6 +1555,8 @@ public class IndexData {
return new GuidColumnDescriptor(col, flags);
case BINARY:
return new BinaryColumnDescriptor(col, flags);
case EXT_DATE_TIME:
return new ExtDateColumnDescriptor(col, flags);

default:
// we can't modify this index at this point in time
@@ -1980,6 +1987,46 @@ public class IndexData {
}
}

/**
* ColumnDescriptor for extended date/time based columns.
*/
private static final class ExtDateColumnDescriptor extends ColumnDescriptor
{
private ExtDateColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
}

@Override
protected void writeNonNullValue(Object value, ByteStream bout)
throws IOException
{
byte[] valueBytes = encodeNumberColumnValue(value, getColumn());

// entry (which is 42 bytes of data) is encoded in blocks of 8 bytes
// separated by '\t' char

// note that for desc, all bytes are flipped _except_ separator char
byte[] trailer = ASC_EXT_DATE_TRAILER;
if(!isAscending()) {
flipBytes(valueBytes);
trailer = DESC_EXT_DATE_TRAILER;
}

// first 5 blocks are all value data
int valIdx = 0;
for(int i = 0; i < 5; ++i) {
bout.write(valueBytes, valIdx, 8);
bout.write((byte)0x09);
valIdx += 8;
}

// last two data bytes and then the trailer
bout.write(valueBytes, valIdx, 2);
bout.write(trailer);
}
}

/**
* ColumnDescriptor for columns which we cannot currently write.

+ 16
- 6
src/main/java/com/healthmarketscience/jackcess/impl/JetFormat.java View File

@@ -155,6 +155,18 @@ public abstract class JetFormat {
V16_CALC_TYPES.addAll(V14_CALC_TYPES);
}

private static final Set<DataType> V16_UNSUPP_TYPES =
EnumSet.of(DataType.EXT_DATE_TIME);
private static final Set<DataType> V12_UNSUPP_TYPES =
EnumSet.of(DataType.BIG_INT);
private static final Set<DataType> V3_UNSUPP_TYPES =
EnumSet.of(DataType.COMPLEX_TYPE);

static {
V12_UNSUPP_TYPES.addAll(V16_UNSUPP_TYPES);
V3_UNSUPP_TYPES.addAll(V12_UNSUPP_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" */
@@ -769,8 +781,7 @@ public abstract class JetFormat {

@Override
public boolean isSupportedDataType(DataType type) {
return ((type != DataType.COMPLEX_TYPE) &&
(type != DataType.BIG_INT));
return !V3_UNSUPP_TYPES.contains(type);
}

@Override
@@ -1006,8 +1017,7 @@ public abstract class JetFormat {

@Override
public boolean isSupportedDataType(DataType type) {
return ((type != DataType.COMPLEX_TYPE) &&
(type != DataType.BIG_INT));
return !V3_UNSUPP_TYPES.contains(type);
}

@Override
@@ -1063,7 +1073,7 @@ public abstract class JetFormat {

@Override
public boolean isSupportedDataType(DataType type) {
return (type != DataType.BIG_INT);
return !V12_UNSUPP_TYPES.contains(type);
}

@Override
@@ -1114,7 +1124,7 @@ public abstract class JetFormat {

@Override
public boolean isSupportedDataType(DataType type) {
return true;
return !V16_UNSUPP_TYPES.contains(type);
}

@Override

Loading…
Cancel
Save