git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1356 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-4.0.0
<artifactId>jackcess</artifactId> | <artifactId>jackcess</artifactId> | ||||
<name>Jackcess</name> | <name>Jackcess</name> | ||||
<description>A pure Java library for reading from and writing to MS Access databases.</description> | <description>A pure Java library for reading from and writing to MS Access databases.</description> | ||||
<version>3.5.2-SNAPSHOT</version> | |||||
<version>4.0.0-SNAPSHOT</version> | |||||
<url>https://jackcess.sourceforge.io</url> | <url>https://jackcess.sourceforge.io</url> | ||||
<inceptionYear>2005</inceptionYear> | <inceptionYear>2005</inceptionYear> | ||||
<developers> | <developers> | ||||
<extensions>true</extensions> | <extensions>true</extensions> | ||||
<configuration> | <configuration> | ||||
<instructions> | <instructions> | ||||
<Automatic-Module-Name>com.healthmarketscience.jackcess</Automatic-Module-Name> | |||||
</instructions> | </instructions> | ||||
</configuration> | </configuration> | ||||
<executions> | <executions> |
<author email="javajedi@users.sf.net">Tim McCune</author> | <author email="javajedi@users.sf.net">Tim McCune</author> | ||||
</properties> | </properties> | ||||
<body> | <body> | ||||
<release version="4.0.0" date="TBD"> | |||||
<action dev="jahlborn" type="update"> | |||||
Add Automatic-Module-Name in order to make Jackcess safe to use in the | |||||
module path in Java 9+ environments. | |||||
</action> | |||||
<action dev="jahlborn" type="update"> | |||||
Make a few classes and constants public in order to facilitate use in | |||||
different packages. | |||||
</action> | |||||
<action dev="jahlborn" type="update"> | |||||
Alter access to java.sql classes so that usage is dependent on whether | |||||
or not the module is loaded in Java 9+ environments. Tweak a few | |||||
methods to no longer throw SQLException. | |||||
</action> | |||||
</release> | |||||
<release version="3.5.1" date="2020-09-10"> | <release version="3.5.1" date="2020-09-10"> | ||||
<action dev="jahlborn" type="update"> | <action dev="jahlborn" type="update"> | ||||
Add more static utility methods to DatabaseBuilder to enable more | Add more static utility methods to DatabaseBuilder to enable more |
package com.healthmarketscience.jackcess; | package com.healthmarketscience.jackcess; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.sql.SQLException; | |||||
import java.util.Map; | import java.util.Map; | ||||
import com.healthmarketscience.jackcess.complex.ComplexColumnInfo; | import com.healthmarketscience.jackcess.complex.ComplexColumnInfo; | ||||
* @author James Ahlborn | * @author James Ahlborn | ||||
* @usage _general_class_ | * @usage _general_class_ | ||||
*/ | */ | ||||
public interface Column | |||||
public interface Column | |||||
{ | { | ||||
/** | /** | ||||
* Meaningless placeholder object for inserting values in an autonumber | * Meaningless placeholder object for inserting values in an autonumber | ||||
* @usage _general_field_ | * @usage _general_field_ | ||||
*/ | */ | ||||
public static final Object AUTO_NUMBER = "<AUTO_NUMBER>"; | public static final Object AUTO_NUMBER = "<AUTO_NUMBER>"; | ||||
/** | /** | ||||
* Meaningless placeholder object for updating rows which indicates that a | * Meaningless placeholder object for updating rows which indicates that a | ||||
* given column should keep its existing value. | * given column should keep its existing value. | ||||
* @usage _general_field_ | * @usage _general_field_ | ||||
*/ | */ | ||||
public static final Object KEEP_VALUE = "<KEEP_VALUE>"; | public static final Object KEEP_VALUE = "<KEEP_VALUE>"; | ||||
/** | /** | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
/** | /** | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
public int getSQLType() throws SQLException; | |||||
public int getSQLType() throws IOException; | |||||
/** | /** | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
public PropertyMap getProperties() throws IOException; | public PropertyMap getProperties() throws IOException; | ||||
/** | /** | ||||
* Returns the column which tracks the version history for an "append only" | * Returns the column which tracks the version history for an "append only" | ||||
* column. | * column. | ||||
* @usage _intermediate_method_ | * @usage _intermediate_method_ | ||||
*/ | */ | ||||
public ColumnValidator getColumnValidator(); | public ColumnValidator getColumnValidator(); | ||||
/** | /** | ||||
* Sets a new ColumnValidator. If {@code null}, resets to the value | * Sets a new ColumnValidator. If {@code null}, resets to the value | ||||
* returned from the Database's ColumnValidatorFactory (if the factory | * returned from the Database's ColumnValidatorFactory (if the factory | ||||
* @usage _intermediate_method_ | * @usage _intermediate_method_ | ||||
*/ | */ | ||||
public void setColumnValidator(ColumnValidator newValidator); | public void setColumnValidator(ColumnValidator newValidator); | ||||
public Object setRowValue(Object[] rowArray, Object value); | public Object setRowValue(Object[] rowArray, Object value); | ||||
public Object setRowValue(Map<String,Object> rowMap, Object value); | public Object setRowValue(Map<String,Object> rowMap, Object value); | ||||
public Object getRowValue(Object[] rowArray); | public Object getRowValue(Object[] rowArray); | ||||
public Object getRowValue(Map<String,?> rowMap); | public Object getRowValue(Map<String,?> rowMap); | ||||
} | } |
package com.healthmarketscience.jackcess; | package com.healthmarketscience.jackcess; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.sql.SQLException; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
/** | /** | ||||
* Sets the type for the new column based on the given SQL type. | * Sets the type for the new column based on the given SQL type. | ||||
*/ | */ | ||||
public ColumnBuilder setSQLType(int type) throws SQLException { | |||||
public ColumnBuilder setSQLType(int type) throws IOException { | |||||
return setSQLType(type, 0, null); | return setSQLType(type, 0, null); | ||||
} | } | ||||
* data length (in type specific units). | * data length (in type specific units). | ||||
*/ | */ | ||||
public ColumnBuilder setSQLType(int type, int lengthInUnits) | public ColumnBuilder setSQLType(int type, int lengthInUnits) | ||||
throws SQLException | |||||
throws IOException | |||||
{ | { | ||||
return setSQLType(type, lengthInUnits, null); | return setSQLType(type, lengthInUnits, null); | ||||
} | } | ||||
*/ | */ | ||||
public ColumnBuilder setSQLType(int type, int lengthInUnits, | public ColumnBuilder setSQLType(int type, int lengthInUnits, | ||||
Database.FileFormat fileFormat) | Database.FileFormat fileFormat) | ||||
throws SQLException | |||||
throws IOException | |||||
{ | { | ||||
return setType(DataType.fromSQLType(type, lengthInUnits, fileFormat)); | return setType(DataType.fromSQLType(type, lengthInUnits, fileFormat)); | ||||
} | } |
import java.io.IOException; | import java.io.IOException; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.math.BigInteger; | import java.math.BigInteger; | ||||
import java.sql.SQLException; | |||||
import java.sql.Types; | import java.sql.Types; | ||||
import java.util.Date; | import java.util.Date; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | import com.healthmarketscience.jackcess.impl.DatabaseImpl; | ||||
import com.healthmarketscience.jackcess.impl.JetFormat; | import com.healthmarketscience.jackcess.impl.JetFormat; | ||||
import com.healthmarketscience.jackcess.impl.SqlHelper; | |||||
/** | /** | ||||
* Supported access data types. | * Supported access data types. | ||||
return _maxSize; | return _maxSize; | ||||
} | } | ||||
public int getSQLType() throws SQLException { | |||||
public int getSQLType() throws IOException { | |||||
if (_sqlType != null) { | if (_sqlType != null) { | ||||
return _sqlType; | return _sqlType; | ||||
} | } | ||||
throw new SQLException("Unsupported data type: " + toString()); | |||||
throw new JackcessException("Unsupported data type: " + toString()); | |||||
} | } | ||||
public int getMinScale() { | public int getMinScale() { | ||||
} | } | ||||
public static DataType fromSQLType(int sqlType) | public static DataType fromSQLType(int sqlType) | ||||
throws SQLException | |||||
throws IOException | |||||
{ | { | ||||
return fromSQLType(sqlType, 0, null); | return fromSQLType(sqlType, 0, null); | ||||
} | } | ||||
public static DataType fromSQLType(int sqlType, int lengthInUnits) | public static DataType fromSQLType(int sqlType, int lengthInUnits) | ||||
throws SQLException | |||||
throws IOException | |||||
{ | { | ||||
return fromSQLType(sqlType, lengthInUnits, null); | return fromSQLType(sqlType, lengthInUnits, null); | ||||
} | } | ||||
public static DataType fromSQLType(int sqlType, int lengthInUnits, | public static DataType fromSQLType(int sqlType, int lengthInUnits, | ||||
Database.FileFormat fileFormat) | Database.FileFormat fileFormat) | ||||
throws SQLException | |||||
throws IOException | |||||
{ | { | ||||
DataType[] rtnArr = SQL_TYPES.get(sqlType); | DataType[] rtnArr = SQL_TYPES.get(sqlType); | ||||
if(rtnArr == null) { | if(rtnArr == null) { | ||||
throw new SQLException("Unsupported SQL type: " + sqlType); | |||||
throw new JackcessException("Unsupported SQL type: " + sqlType); | |||||
} | } | ||||
JetFormat format = | JetFormat format = | ||||
((fileFormat != null) ? | ((fileFormat != null) ? | ||||
DataType altType) | DataType altType) | ||||
{ | { | ||||
try { | try { | ||||
java.lang.reflect.Field sqlTypeField = Types.class.getField(typeName); | |||||
Integer value = (Integer)sqlTypeField.get(null); | |||||
Integer value = SqlHelper.INSTANCE.getNewSqlType(typeName); | |||||
SQL_TYPES.put(value, new DataType[]{type}); | SQL_TYPES.put(value, new DataType[]{type}); | ||||
if(altType != null) { | if(altType != null) { | ||||
ALT_SQL_TYPES.put(value, altType); | ALT_SQL_TYPES.put(value, altType); |
buffer.put(intValBytes); | buffer.put(intValBytes); | ||||
} catch(ArithmeticException e) { | } catch(ArithmeticException e) { | ||||
throw (IOException) | |||||
new IOException(withErrorContext( | |||||
"Numeric value '" + inValue + "' out of range")) | |||||
.initCause(e); | |||||
throw new IOException( | |||||
withErrorContext("Numeric value '" + inValue + "' out of range"), e); | |||||
} | } | ||||
} | } | ||||
import java.nio.ByteOrder; | import java.nio.ByteOrder; | ||||
import java.nio.CharBuffer; | import java.nio.CharBuffer; | ||||
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import java.sql.Blob; | |||||
import java.sql.Clob; | |||||
import java.sql.SQLException; | |||||
import java.time.DateTimeException; | import java.time.DateTimeException; | ||||
import java.time.Duration; | import java.time.Duration; | ||||
import java.time.Instant; | import java.time.Instant; | ||||
} | } | ||||
@Override | @Override | ||||
public int getSQLType() throws SQLException { | |||||
public int getSQLType() throws IOException { | |||||
return _type.getSQLType(); | return _type.getSQLType(); | ||||
} | } | ||||
// too big) | // too big) | ||||
buffer.putLong(decVal.movePointRight(4).longValueExact()); | buffer.putLong(decVal.movePointRight(4).longValueExact()); | ||||
} catch(ArithmeticException e) { | } catch(ArithmeticException e) { | ||||
throw (IOException) | |||||
new IOException(withErrorContext( | |||||
"Currency value '" + inValue + "' out of range")) | |||||
.initCause(e); | |||||
throw new IOException( | |||||
withErrorContext("Currency value '" + inValue + "' out of range"), e); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
buffer.put(intValBytes); | buffer.put(intValBytes); | ||||
} catch(ArithmeticException e) { | } catch(ArithmeticException e) { | ||||
throw (IOException) | |||||
new IOException(withErrorContext( | |||||
"Numeric value '" + inValue + "' out of range")) | |||||
.initCause(e); | |||||
throw new IOException( | |||||
withErrorContext("Numeric value '" + inValue + "' out of range"), e); | |||||
} | } | ||||
} | } | ||||
return null; | return null; | ||||
} else if(value instanceof CharSequence) { | } else if(value instanceof CharSequence) { | ||||
return (CharSequence)value; | return (CharSequence)value; | ||||
} else if(value instanceof Clob) { | |||||
try { | |||||
Clob c = (Clob)value; | |||||
// note, start pos is 1-based | |||||
return c.getSubString(1L, (int)c.length()); | |||||
} catch(SQLException e) { | |||||
throw (IOException)(new IOException(e.getMessage())).initCause(e); | |||||
} | |||||
} else if(SqlHelper.INSTANCE.isClob(value)) { | |||||
return SqlHelper.INSTANCE.getClobString(value); | |||||
} else if(value instanceof Reader) { | } else if(value instanceof Reader) { | ||||
char[] buf = new char[8 * 1024]; | char[] buf = new char[8 * 1024]; | ||||
StringBuilder sout = new StringBuilder(); | StringBuilder sout = new StringBuilder(); | ||||
return null; | return null; | ||||
} else if(value instanceof byte[]) { | } else if(value instanceof byte[]) { | ||||
return (byte[])value; | return (byte[])value; | ||||
} else if(value instanceof OleUtil.OleBlobImpl) { | |||||
return ((OleUtil.OleBlobImpl)value).getBytes(); | |||||
} else if(value instanceof Blob) { | |||||
try { | |||||
Blob b = (Blob)value; | |||||
// note, start pos is 1-based | |||||
return b.getBytes(1L, (int)b.length()); | |||||
} catch(SQLException e) { | |||||
throw (IOException)(new IOException(e.getMessage())).initCause(e); | |||||
} | |||||
} else if(value instanceof RawData) { | |||||
return ((RawData)value).getBytes(); | |||||
} else if(value instanceof InMemoryBlob) { | |||||
return ((InMemoryBlob)value).getBytes(); | |||||
} else if(SqlHelper.INSTANCE.isBlob(value)) { | |||||
return SqlHelper.INSTANCE.getBlobBytes(value); | |||||
} | } | ||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(); | ByteArrayOutputStream bout = new ByteArrayOutputStream(); | ||||
/** | /** | ||||
* Wrapper for raw column data which can be re-written. | * Wrapper for raw column data which can be re-written. | ||||
*/ | */ | ||||
private static class RawData implements Serializable | |||||
private static final class RawData implements Serializable, InMemoryBlob | |||||
{ | { | ||||
private static final long serialVersionUID = 0L; | private static final long serialVersionUID = 0L; | ||||
_bytes = bytes; | _bytes = bytes; | ||||
} | } | ||||
private byte[] getBytes() { | |||||
@Override | |||||
public byte[] getBytes() { | |||||
return _bytes; | return _bytes; | ||||
} | } | ||||
return LocalDateTime.ofInstant(inst, db.getZoneId()); | return LocalDateTime.ofInstant(inst, db.getZoneId()); | ||||
} | } | ||||
} | } | ||||
/** internal interface for types which hold bytes in memory */ | |||||
static interface InMemoryBlob { | |||||
public byte[] getBytes() throws IOException; | |||||
} | |||||
} | } |
/** | /** | ||||
* Returns the password mask retrieved from the given header page and | * Returns the password mask retrieved from the given header page and | ||||
* format, or {@code null} if this format does not use a password mask. | * format, or {@code null} if this format does not use a password mask. | ||||
* @usage _advanced_method_ | |||||
*/ | */ | ||||
static byte[] getPasswordMask(ByteBuffer buffer, JetFormat format) | |||||
public static byte[] getPasswordMask(ByteBuffer buffer, JetFormat format) | |||||
{ | { | ||||
// get extra password mask if necessary (the extra password mask is | // get extra password mask if necessary (the extra password mask is | ||||
// generated from the database creation date stored in the header) | // generated from the database creation date stored in the header) |
private static final byte CODE_VERSION_16 = 0x5; | private static final byte CODE_VERSION_16 = 0x5; | ||||
/** location of the engine name in the header */ | /** location of the engine name in the header */ | ||||
static final int OFFSET_ENGINE_NAME = 0x4; | |||||
public static final int OFFSET_ENGINE_NAME = 0x4; | |||||
/** length of the engine name in the header */ | /** length of the engine name in the header */ | ||||
static final int LENGTH_ENGINE_NAME = 0xF; | |||||
public static final int LENGTH_ENGINE_NAME = 0xF; | |||||
/** amount of initial data to be read to determine database type */ | /** amount of initial data to be read to determine database type */ | ||||
private static final int HEADER_LENGTH = 21; | private static final int HEADER_LENGTH = 21; | ||||
} | } | ||||
static final class OleBlobImpl implements OleBlob | |||||
static final class OleBlobImpl implements OleBlob, ColumnImpl.InMemoryBlob | |||||
{ | { | ||||
private byte[] _bytes; | private byte[] _bytes; | ||||
private ContentImpl _content; | private ContentImpl _content; | ||||
return _bytes.length; | return _bytes.length; | ||||
} | } | ||||
@Override | |||||
public byte[] getBytes() throws IOException { | public byte[] getBytes() throws IOException { | ||||
if(_bytes == null) { | if(_bytes == null) { | ||||
throw new IOException("blob is closed"); | throw new IOException("blob is closed"); |
static final int INVALID_PAGE_NUMBER = -1; | static final int INVALID_PAGE_NUMBER = -1; | ||||
static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN; | |||||
/** default byte order of access mdb files */ | |||||
public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN; | |||||
/** invalid page header, used when deallocating old pages. data pages | /** invalid page header, used when deallocating old pages. data pages | ||||
generally have 4 interesting bytes at the beginning which we want to | generally have 4 interesting bytes at the beginning which we want to |
/* | |||||
Copyright (c) 2021 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.io.IOException; | |||||
/** | |||||
* Helper class to isolate the java.sql module interactions from the core of | |||||
* jackcess (in java 9+ environments). If the module is enabled (indicating | |||||
* that the application is already using sql constructs), then jackcess will | |||||
* seamlessly interact with sql types. If the module is not enabled | |||||
* (indicating that the application is not using any sql constructs), then | |||||
* jackcess will not require the module in order to function otherwise | |||||
* normally. | |||||
* | |||||
* This base class is the "fallback" class if the java.sql module is not | |||||
* available. | |||||
* | |||||
* @author James Ahlborn | |||||
*/ | |||||
public class SqlHelper { | |||||
public static final SqlHelper INSTANCE = loadInstance(); | |||||
public SqlHelper() {} | |||||
public boolean isBlob(Object value) { | |||||
return false; | |||||
} | |||||
public byte[] getBlobBytes(Object value) throws IOException { | |||||
throw new UnsupportedOperationException(); | |||||
} | |||||
public boolean isClob(Object value) { | |||||
return false; | |||||
} | |||||
public CharSequence getClobString(Object value) throws IOException { | |||||
throw new UnsupportedOperationException(); | |||||
} | |||||
public Integer getNewSqlType(String typeName) throws Exception { | |||||
throw new UnsupportedOperationException(); | |||||
} | |||||
private static final SqlHelper loadInstance() { | |||||
// attempt to load the implementation of this class which works with | |||||
// java.sql classes. if that fails, use this fallback instance instead. | |||||
try { | |||||
return (SqlHelper) | |||||
Class.forName("com.healthmarketscience.jackcess.impl.SqlHelperImpl") | |||||
.newInstance(); | |||||
} catch(Throwable ignored) {} | |||||
return new SqlHelper(); | |||||
} | |||||
} |
/* | |||||
Copyright (c) 2021 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.io.IOException; | |||||
import java.sql.Blob; | |||||
import java.sql.Clob; | |||||
import java.sql.SQLException; | |||||
import java.sql.Types; | |||||
/** | |||||
* Implementation of SqlHelperImpl which works with the java.sql modules | |||||
* classes. This class is used if the java.sql module is enabled in the | |||||
* application. | |||||
* | |||||
* @author James Ahlborn | |||||
*/ | |||||
public class SqlHelperImpl extends SqlHelper { | |||||
public SqlHelperImpl() {} | |||||
@Override | |||||
public boolean isBlob(Object value) { | |||||
return (value instanceof Blob); | |||||
} | |||||
@Override | |||||
public byte[] getBlobBytes(Object value) throws IOException { | |||||
try { | |||||
Blob b = (Blob)value; | |||||
// note, start pos is 1-based | |||||
return b.getBytes(1L, (int)b.length()); | |||||
} catch(SQLException e) { | |||||
throw new IOException(e.getMessage(), e); | |||||
} | |||||
} | |||||
@Override | |||||
public boolean isClob(Object value) { | |||||
return (value instanceof Clob); | |||||
} | |||||
@Override | |||||
public CharSequence getClobString(Object value) throws IOException { | |||||
try { | |||||
Clob c = (Clob)value; | |||||
// note, start pos is 1-based | |||||
return c.getSubString(1L, (int)c.length()); | |||||
} catch(SQLException e) { | |||||
throw new IOException(e.getMessage(), e); | |||||
} | |||||
} | |||||
@Override | |||||
public Integer getNewSqlType(String typeName) throws Exception { | |||||
java.lang.reflect.Field sqlTypeField = Types.class.getField(typeName); | |||||
return (Integer)sqlTypeField.get(null); | |||||
} | |||||
} |
package com.healthmarketscience.jackcess.util; | package com.healthmarketscience.jackcess.util; | ||||
import com.healthmarketscience.jackcess.ColumnBuilder; | |||||
import com.healthmarketscience.jackcess.DataType; | |||||
import com.healthmarketscience.jackcess.Database; | |||||
import com.healthmarketscience.jackcess.Table; | |||||
import com.healthmarketscience.jackcess.TableBuilder; | |||||
import com.healthmarketscience.jackcess.impl.ByteUtil; | |||||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||||
import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
import java.io.EOFException; | import java.io.EOFException; | ||||
import java.io.File; | import java.io.File; | ||||
import java.util.regex.Matcher; | import java.util.regex.Matcher; | ||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import com.healthmarketscience.jackcess.ColumnBuilder; | |||||
import com.healthmarketscience.jackcess.DataType; | |||||
import com.healthmarketscience.jackcess.Database; | |||||
import com.healthmarketscience.jackcess.Table; | |||||
import com.healthmarketscience.jackcess.TableBuilder; | |||||
import com.healthmarketscience.jackcess.impl.ByteUtil; | |||||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||||
/** | /** | ||||
* Utility class for importing tables to an Access database from other | * Utility class for importing tables to an Access database from other | ||||
* sources. See the {@link Builder} for convenient configuration of the | * sources. See the {@link Builder} for convenient configuration of the | ||||
* @return a List of Columns | * @return a List of Columns | ||||
*/ | */ | ||||
public static List<ColumnBuilder> toColumns(ResultSetMetaData md) | public static List<ColumnBuilder> toColumns(ResultSetMetaData md) | ||||
throws SQLException | |||||
throws SQLException, IOException | |||||
{ | { | ||||
List<ColumnBuilder> columns = new ArrayList<ColumnBuilder>(); | List<ColumnBuilder> columns = new ArrayList<ColumnBuilder>(); | ||||
for (int i = 1; i <= md.getColumnCount(); i++) { | for (int i = 1; i <= md.getColumnCount(); i++) { | ||||
return table.getName(); | return table.getName(); | ||||
} catch(SQLException e) { | } catch(SQLException e) { | ||||
throw (IOException)new IOException(e.getMessage()).initCause(e); | |||||
throw new IOException(e.getMessage(), e); | |||||
} | } | ||||
} | } | ||||
<item name="Downloads" href="https://sourceforge.net/project/showfiles.php?group_id=134943"/> | <item name="Downloads" href="https://sourceforge.net/project/showfiles.php?group_id=134943"/> | ||||
<item name="SourceForge Project" href="https://sourceforge.net/projects/jackcess/"/> | <item name="SourceForge Project" href="https://sourceforge.net/projects/jackcess/"/> | ||||
<item name="Cookbook" href="cookbook.html"/> | <item name="Cookbook" href="cookbook.html"/> | ||||
<item name="Upgrading to Jackcess 3.5.x" href="jackcess-3_5.html"/> | |||||
<item name="Upgrading to Jackcess 3.x" href="jackcess-3.html"/> | |||||
<item name="Upgrading to Jackcess 2.x" href="jackcess-2.html"/> | |||||
<item name="Upgrading to Jackcess 4.x" href="jackcess-4.html"/> | |||||
<item name="Upgrading to Older Versions" href="jackcess_upgrades.html" collapse="true"> | |||||
<item name="Jackcess 3.5.x" href="jackcess-3_5.html"/> | |||||
<item name="Jackcess 3.x" href="jackcess-3.html"/> | |||||
<item name="Jackcess 2.x" href="jackcess-2.html"/> | |||||
</item> | |||||
<item name="FAQ" href="faq.html"/> | <item name="FAQ" href="faq.html"/> | ||||
<item name="Support" href="https://sourceforge.net/projects/jackcess/support"/> | <item name="Support" href="https://sourceforge.net/projects/jackcess/support"/> | ||||
<item name="Jackcess Encrypt" href="https://jackcessencrypt.sourceforge.io/"/> | <item name="Jackcess Encrypt" href="https://jackcessencrypt.sourceforge.io/"/> |
Questions</a> for more info. | Questions</a> for more info. | ||||
</p> | </p> | ||||
<subsection name="Java 9+ Compatibility (2021-TBD)"> | |||||
<p> | |||||
While Jackcess <i>still only requires Java 8+</i>, as of the 4.0.0 | |||||
release it now includes an Automatic-Module-Name of | |||||
<code>com.healthmarketscience.jackcess</code> in its manifest. This | |||||
allows it to be used safely in the module path for Java 9+ projects. | |||||
</p> | |||||
<p> | |||||
This release is binary compatible with the 3.x release series. | |||||
</p> | |||||
</subsection> | |||||
<subsection name="Java 8+ Support (2019-02-08)"> | <subsection name="Java 8+ Support (2019-02-08)"> | ||||
<p> | <p> | ||||
Jackcess now requires Java 8+ as of the 3.0.0 release. All third | Jackcess now requires Java 8+ as of the 3.0.0 release. All third | ||||
<li>Creating a new table and writing data into it: | <li>Creating a new table and writing data into it: | ||||
<source>Database db = DatabaseBuilder.create(Database.FileFormat.V2000, new File("new.mdb")); | <source>Database db = DatabaseBuilder.create(Database.FileFormat.V2000, new File("new.mdb")); | ||||
Table newTable = new TableBuilder("NewTable") | Table newTable = new TableBuilder("NewTable") | ||||
.addColumn(new ColumnBuilder("a") | |||||
.setSQLType(Types.INTEGER)) | |||||
.addColumn(new ColumnBuilder("b") | |||||
.setSQLType(Types.VARCHAR)) | |||||
.addColumn(new ColumnBuilder("a", DataType.LONG)) | |||||
.addColumn(new ColumnBuilder("b", DataType.TEXT)) | |||||
.toTable(db); | .toTable(db); | ||||
newTable.addRow(1, "foo"); | newTable.addRow(1, "foo"); | ||||
</source> | </source> |
behavior of Jackcess out of the box. The old behavior is still | behavior of Jackcess out of the box. The old behavior is still | ||||
available, but you must now configure it explicitly. | available, but you must now configure it explicitly. | ||||
<ul> | <ul> | ||||
<li><b>DateTypeType default has been changed to | |||||
<li><b>DateTimeType default has been changed to | |||||
LOCAL_DATE_TIME.</b></li> | LOCAL_DATE_TIME.</b></li> | ||||
<ul> | <ul> | ||||
<li>The Jackcess API was originally built using the Date type | <li>The Jackcess API was originally built using the Date type |
<?xml version="1.0"?> | |||||
<document> | |||||
<properties> | |||||
<author email="jahlborn@users.sf.net">James Ahlborn</author> | |||||
<title>Upgrading from Jackcess 3.x (or 3.5.x) to 4.x</title> | |||||
</properties> | |||||
<body> | |||||
<section name="Jackcess 4.0"> | |||||
<subsection name="Just give it to me straight"> | |||||
<p> | |||||
Jackcess 4.x is largely binary compatible with the previous 3.x (and | |||||
3.5.x) versions. In order to be more "module friendly", the | |||||
exceptions thrown from a few infrequently used methods have changed | |||||
from <code>SQLException</code> to <code>IOException</code>. Other | |||||
than that, code compiled against the 3.x series should work fine | |||||
with the 4.x series. | |||||
</p> | |||||
<p> | |||||
Even though Jackcess is now Java 9+ module compatible, <i>it still | |||||
only requires Java 8+</i>. However, Jackcess can now be safely used | |||||
in the module path of a Java 9+ project (with the module name of | |||||
<code>com.healthmarketscience.jackcess</code>). There are a few | |||||
features which require additional core Java modules to be enabled | |||||
(but these modules are not required if the corresponding features | |||||
are not utilized). | |||||
<ul> | |||||
<li><b>java.sql</b></li> | |||||
<ul> | |||||
<li>This module is necessary if Jackcess will need to interact | |||||
with any of the classes from the <code>java.sql</code> package | |||||
(e.g. handling <code>Clob</code>/<code>Blob</code> or using | |||||
<code>ImportUtil</code>).</li> | |||||
</ul> | |||||
<li><b>java.scripting</b></li> | |||||
<ul> | |||||
<li>This module is necessary if expression evaluation is | |||||
enabled (the default) and the database includes expressions | |||||
(e.g. default values, calculated columns, validation | |||||
rules, etc.).</li> | |||||
</ul> | |||||
</ul> | |||||
</p> | |||||
<h4>Working with Jackcess Encrypt</h4> | |||||
<p> | |||||
If you are using the <a href="https://jackcessencrypt.sourceforge.io/">Jackcess Encrypt</a> | |||||
project, then you will need to use a version compatible with the | |||||
relevant Jackess API. Fortunately, the major versions match, so | |||||
it's pretty simple: | |||||
</p> | |||||
<ul> | |||||
<li>Jackcess 4.x -> Jackcess Encrypt 4.y</li> | |||||
<li>Jackcess 3.x -> Jackcess Encrypt 3.y</li> | |||||
<li>Jackcess 2.x -> Jackcess Encrypt 2.y</li> | |||||
<li>Jackcess 1.x -> Jackcess Encrypt 1.y</li> | |||||
</ul> | |||||
</subsection> | |||||
</section> | |||||
</body> | |||||
</document> |
<?xml version="1.0"?> | |||||
<document> | |||||
<properties> | |||||
<author email="jahlborn@users.sf.net">James Ahlborn</author> | |||||
<title>Upgrading to Older Versions</title> | |||||
</properties> | |||||
<body> | |||||
<section name="Upgrading to Older Versions"> | |||||
<subsection name="Overview"> | |||||
<table> | |||||
<tr><th>Document</th></tr> | |||||
<tr><td><a href="jackcess-3_5.html">Upgrading to Jackcess 3.5.x</a></td></tr> | |||||
<tr><td><a href="jackcess-3.html">Upgrading to Jackcess 3.x</a></td></tr> | |||||
<tr><td><a href="jackcess-2.html">Upgrading to Jackcess 2.x</a></td></tr> | |||||
</table> | |||||
</subsection> | |||||
</section> | |||||
</body> | |||||
</document> |
Assert.assertEquals(expectedLdt, found); | Assert.assertEquals(expectedLdt, found); | ||||
} | } | ||||
static void copyFile(File srcFile, File dstFile) | |||||
public static void copyFile(File srcFile, File dstFile) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
// FIXME should really be using commons io FileUtils here, but don't want | // FIXME should really be using commons io FileUtils here, but don't want | ||||
} | } | ||||
} | } | ||||
static File createTempFile(boolean keep) throws Exception { | |||||
public static File createTempFile(boolean keep) throws Exception { | |||||
File tmp = File.createTempFile("databaseTest", ".mdb"); | File tmp = File.createTempFile("databaseTest", ".mdb"); | ||||
if(keep) { | if(keep) { | ||||
System.out.println("Created " + tmp); | System.out.println("Created " + tmp); |
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import java.nio.channels.FileChannel; | import java.nio.channels.FileChannel; | ||||
import java.nio.channels.NonWritableChannelException; | import java.nio.channels.NonWritableChannelException; | ||||
import java.sql.SQLException; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.EnumSet; | import java.util.EnumSet; | ||||
import java.util.List; | import java.util.List; | ||||
import com.healthmarketscience.jackcess.DataType; | import com.healthmarketscience.jackcess.DataType; | ||||
import com.healthmarketscience.jackcess.Database; | import com.healthmarketscience.jackcess.Database; | ||||
import com.healthmarketscience.jackcess.JackcessException; | |||||
import static com.healthmarketscience.jackcess.Database.*; | import static com.healthmarketscience.jackcess.Database.*; | ||||
import com.healthmarketscience.jackcess.DatabaseBuilder; | import com.healthmarketscience.jackcess.DatabaseBuilder; | ||||
import com.healthmarketscience.jackcess.PropertyMap; | import com.healthmarketscience.jackcess.PropertyMap; | ||||
Integer sqlType = null; | Integer sqlType = null; | ||||
try { | try { | ||||
sqlType = dt.getSQLType(); | sqlType = dt.getSQLType(); | ||||
} catch(SQLException ignored) {} | |||||
} catch(JackcessException ignored) {} | |||||
if(sqlType != null) { | if(sqlType != null) { | ||||
assertEquals(dt, DataType.fromSQLType(sqlType)); | assertEquals(dt, DataType.fromSQLType(sqlType)); |