You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Database.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. /*
  2. Copyright (c) 2005 Health Market Science, Inc.
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. You can contact Health Market Science at info@healthmarketscience.com
  16. or at the following address:
  17. Health Market Science
  18. 2700 Horizon Drive
  19. Suite 200
  20. King of Prussia, PA 19406
  21. */
  22. package com.healthmarketscience.jackcess;
  23. import java.io.BufferedReader;
  24. import java.io.File;
  25. import java.io.FileNotFoundException;
  26. import java.io.FileReader;
  27. import java.io.IOException;
  28. import java.io.RandomAccessFile;
  29. import java.nio.ByteBuffer;
  30. import java.nio.channels.Channels;
  31. import java.nio.channels.FileChannel;
  32. import java.sql.ResultSet;
  33. import java.sql.ResultSetMetaData;
  34. import java.sql.SQLException;
  35. import java.sql.Types;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.Date;
  39. import java.util.HashMap;
  40. import java.util.HashSet;
  41. import java.util.Iterator;
  42. import java.util.LinkedList;
  43. import java.util.List;
  44. import java.util.Map;
  45. import java.util.Set;
  46. import org.apache.commons.lang.builder.ToStringBuilder;
  47. import org.apache.commons.logging.Log;
  48. import org.apache.commons.logging.LogFactory;
  49. /**
  50. * An Access database.
  51. *
  52. * @author Tim McCune
  53. */
  54. public class Database {
  55. private static final Log LOG = LogFactory.getLog(Database.class);
  56. private static final byte[] SID = new byte[2];
  57. static {
  58. SID[0] = (byte) 0xA6;
  59. SID[1] = (byte) 0x33;
  60. }
  61. /** Batch commit size for copying other result sets into this database */
  62. private static final int COPY_TABLE_BATCH_SIZE = 200;
  63. /** System catalog always lives on page 2 */
  64. private static final int PAGE_SYSTEM_CATALOG = 2;
  65. private static final int ACM = 1048319;
  66. /** Free space left in page for new usage map definition pages */
  67. private static final short USAGE_MAP_DEF_FREE_SPACE = 3940;
  68. private static final String COL_ACM = "ACM";
  69. /** System catalog column name of the date a system object was created */
  70. private static final String COL_DATE_CREATE = "DateCreate";
  71. /** System catalog column name of the date a system object was updated */
  72. private static final String COL_DATE_UPDATE = "DateUpdate";
  73. private static final String COL_F_INHERITABLE = "FInheritable";
  74. private static final String COL_FLAGS = "Flags";
  75. /**
  76. * System catalog column name of the page on which system object definitions
  77. * are stored
  78. */
  79. private static final String COL_ID = "Id";
  80. /** System catalog column name of the name of a system object */
  81. private static final String COL_NAME = "Name";
  82. private static final String COL_OBJECT_ID = "ObjectId";
  83. private static final String COL_OWNER = "Owner";
  84. /** System catalog column name of a system object's parent's id */
  85. private static final String COL_PARENT_ID = "ParentId";
  86. private static final String COL_SID = "SID";
  87. /** System catalog column name of the type of a system object */
  88. private static final String COL_TYPE = "Type";
  89. /** Empty database template for creating new databases */
  90. private static final String EMPTY_MDB = "com/healthmarketscience/jackcess/empty.mdb";
  91. /** Prefix for column or table names that are reserved words */
  92. private static final String ESCAPE_PREFIX = "x";
  93. /** Prefix that flags system tables */
  94. private static final String PREFIX_SYSTEM = "MSys";
  95. /** Name of the system object that is the parent of all tables */
  96. private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
  97. /** Name of the table that contains system access control entries */
  98. private static final String TABLE_SYSTEM_ACES = "MSysACEs";
  99. /** System object type for table definitions */
  100. private static final Short TYPE_TABLE = (short) 1;
  101. /**
  102. * All of the reserved words in Access that should be escaped when creating
  103. * table or column names
  104. */
  105. private static final Set<String> RESERVED_WORDS = new HashSet<String>();
  106. static {
  107. //Yup, there's a lot.
  108. RESERVED_WORDS.addAll(Arrays.asList(
  109. "add", "all", "alphanumeric", "alter", "and", "any", "application", "as",
  110. "asc", "assistant", "autoincrement", "avg", "between", "binary", "bit",
  111. "boolean", "by", "byte", "char", "character", "column", "compactdatabase",
  112. "constraint", "container", "count", "counter", "create", "createdatabase",
  113. "createfield", "creategroup", "createindex", "createobject", "createproperty",
  114. "createrelation", "createtabledef", "createuser", "createworkspace",
  115. "currency", "currentuser", "database", "date", "datetime", "delete",
  116. "desc", "description", "disallow", "distinct", "distinctrow", "document",
  117. "double", "drop", "echo", "else", "end", "eqv", "error", "exists", "exit",
  118. "false", "field", "fields", "fillcache", "float", "float4", "float8",
  119. "foreign", "form", "forms", "from", "full", "function", "general",
  120. "getobject", "getoption", "gotopage", "group", "group by", "guid", "having",
  121. "idle", "ieeedouble", "ieeesingle", "if", "ignore", "imp", "in", "index",
  122. "indexes", "inner", "insert", "inserttext", "int", "integer", "integer1",
  123. "integer2", "integer4", "into", "is", "join", "key", "lastmodified", "left",
  124. "level", "like", "logical", "logical1", "long", "longbinary", "longtext",
  125. "macro", "match", "max", "min", "mod", "memo", "module", "money", "move",
  126. "name", "newpassword", "no", "not", "null", "number", "numeric", "object",
  127. "oleobject", "off", "on", "openrecordset", "option", "or", "order", "outer",
  128. "owneraccess", "parameter", "parameters", "partial", "percent", "pivot",
  129. "primary", "procedure", "property", "queries", "query", "quit", "real",
  130. "recalc", "recordset", "references", "refresh", "refreshlink",
  131. "registerdatabase", "relation", "repaint", "repairdatabase", "report",
  132. "reports", "requery", "right", "screen", "section", "select", "set",
  133. "setfocus", "setoption", "short", "single", "smallint", "some", "sql",
  134. "stdev", "stdevp", "string", "sum", "table", "tabledef", "tabledefs",
  135. "tableid", "text", "time", "timestamp", "top", "transform", "true", "type",
  136. "union", "unique", "update", "user", "value", "values", "var", "varp",
  137. "varbinary", "varchar", "where", "with", "workspace", "xor", "year", "yes",
  138. "yesno"
  139. ));
  140. }
  141. /** Buffer to hold database pages */
  142. private ByteBuffer _buffer;
  143. /** ID of the Tables system object */
  144. private Integer _tableParentId;
  145. /** Format that the containing database is in */
  146. private JetFormat _format;
  147. /**
  148. * Map of table names to page numbers containing their definition
  149. */
  150. private Map<String, Integer> _tables = new HashMap<String, Integer>();
  151. /** Reads and writes database pages */
  152. private PageChannel _pageChannel;
  153. /** System catalog table */
  154. private Table _systemCatalog;
  155. /** System access control entries table */
  156. private Table _accessControlEntries;
  157. /**
  158. * Open an existing Database
  159. * @param mdbFile File containing the database
  160. */
  161. public static Database open(File mdbFile) throws IOException, SQLException {
  162. return new Database(openChannel(mdbFile));
  163. }
  164. /**
  165. * Create a new Database
  166. * @param mdbFile Location to write the new database to. <b>If this file
  167. * already exists, it will be overwritten.</b>
  168. */
  169. public static Database create(File mdbFile) throws IOException, SQLException {
  170. FileChannel channel = openChannel(mdbFile);
  171. channel.transferFrom(Channels.newChannel(
  172. Thread.currentThread().getContextClassLoader().getResourceAsStream(
  173. EMPTY_MDB)), 0, (long) Integer.MAX_VALUE);
  174. return new Database(channel);
  175. }
  176. private static FileChannel openChannel(File mdbFile) throws FileNotFoundException {
  177. return new RandomAccessFile(mdbFile, "rw").getChannel();
  178. }
  179. /**
  180. * Create a new database by reading it in from a FileChannel.
  181. * @param channel File channel of the database. This needs to be a
  182. * FileChannel instead of a ReadableByteChannel because we need to
  183. * randomly jump around to various points in the file.
  184. */
  185. protected Database(FileChannel channel) throws IOException, SQLException {
  186. _format = JetFormat.getFormat(channel);
  187. _pageChannel = new PageChannel(channel, _format);
  188. _buffer = _pageChannel.createPageBuffer();
  189. readSystemCatalog();
  190. }
  191. public PageChannel getPageChannel() {
  192. return _pageChannel;
  193. }
  194. /**
  195. * @return The system catalog table
  196. */
  197. public Table getSystemCatalog() {
  198. return _systemCatalog;
  199. }
  200. public Table getAccessControlEntries() {
  201. return _accessControlEntries;
  202. }
  203. /**
  204. * Read the system catalog
  205. */
  206. private void readSystemCatalog() throws IOException, SQLException {
  207. _pageChannel.readPage(_buffer, PAGE_SYSTEM_CATALOG);
  208. byte pageType = _buffer.get();
  209. if (pageType != PageTypes.TABLE_DEF) {
  210. throw new IOException("Looking for system catalog at page " +
  211. PAGE_SYSTEM_CATALOG + ", but page type is " + pageType);
  212. }
  213. _systemCatalog = new Table(_buffer, _pageChannel, _format, PAGE_SYSTEM_CATALOG);
  214. Map row;
  215. while ( (row = _systemCatalog.getNextRow(Arrays.asList(
  216. COL_NAME, COL_TYPE, COL_ID))) != null)
  217. {
  218. String name = (String) row.get(COL_NAME);
  219. if (name != null && TYPE_TABLE.equals(row.get(COL_TYPE))) {
  220. if (!name.startsWith(PREFIX_SYSTEM)) {
  221. _tables.put((String) row.get(COL_NAME), (Integer) row.get(COL_ID));
  222. } else if (TABLE_SYSTEM_ACES.equals(name)) {
  223. readAccessControlEntries(((Integer) row.get(COL_ID)).intValue());
  224. }
  225. } else if (SYSTEM_OBJECT_NAME_TABLES.equals(name)) {
  226. _tableParentId = (Integer) row.get(COL_ID);
  227. }
  228. }
  229. if (LOG.isDebugEnabled()) {
  230. LOG.debug("Finished reading system catalog. Tables: " + _tables);
  231. }
  232. }
  233. /**
  234. * Read the system access control entries table
  235. * @param pageNum Page number of the table def
  236. */
  237. private void readAccessControlEntries(int pageNum) throws IOException, SQLException {
  238. ByteBuffer buffer = _pageChannel.createPageBuffer();
  239. _pageChannel.readPage(buffer, pageNum);
  240. byte pageType = buffer.get();
  241. if (pageType != PageTypes.TABLE_DEF) {
  242. throw new IOException("Looking for MSysACEs at page " + pageNum +
  243. ", but page type is " + pageType);
  244. }
  245. _accessControlEntries = new Table(buffer, _pageChannel, _format, pageNum);
  246. }
  247. /**
  248. * @return The names of all of the user tables (String)
  249. */
  250. public Set getTableNames() {
  251. return _tables.keySet();
  252. }
  253. /**
  254. * @param name Table name
  255. * @return The table, or null if it doesn't exist
  256. */
  257. public Table getTable(String name) throws IOException, SQLException {
  258. Integer pageNumber = (Integer) _tables.get(name);
  259. if (pageNumber == null) {
  260. // Bug workaround:
  261. pageNumber = (Integer) _tables.get(Character.toUpperCase(name.charAt(0)) +
  262. name.substring(1));
  263. }
  264. if (pageNumber == null) {
  265. return null;
  266. } else {
  267. _pageChannel.readPage(_buffer, pageNumber.intValue());
  268. return new Table(_buffer, _pageChannel, _format, pageNumber.intValue());
  269. }
  270. }
  271. /**
  272. * Create a new table in this database
  273. * @param name Name of the table to create
  274. * @param columns List of Columns in the table
  275. */
  276. //XXX Set up 1-page rollback buffer?
  277. public void createTable(String name, List<Column> columns) throws IOException {
  278. //There is some really bizarre bug in here where tables that start with
  279. //the letters a-m (only lower case) won't open in Access. :)
  280. name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
  281. //We are creating a new page at the end of the db for the tdef.
  282. int pageNumber = _pageChannel.getPageCount();
  283. ByteBuffer buffer = _pageChannel.createPageBuffer();
  284. writeTableDefinition(buffer, columns, pageNumber);
  285. writeColumnDefinitions(buffer, columns);
  286. //End of tabledef
  287. buffer.put((byte) 0xff);
  288. buffer.put((byte) 0xff);
  289. buffer.putInt(8, buffer.position()); //Overwrite length of data for this page
  290. //Write the tdef and usage map pages to disk.
  291. _pageChannel.writeNewPage(buffer);
  292. _pageChannel.writeNewPage(createUsageMapDefinitionBuffer(pageNumber));
  293. _pageChannel.writeNewPage(createUsageMapDataBuffer()); //Usage map
  294. //Add this table to our internal list.
  295. _tables.put(name, new Integer(pageNumber));
  296. //Add this table to system tables
  297. addToSystemCatalog(name, pageNumber);
  298. addToAccessControlEntries(pageNumber);
  299. }
  300. /**
  301. * @param buffer Buffer to write to
  302. * @param columns List of Columns in the table
  303. * @param pageNumber Page number that this table definition will be written to
  304. */
  305. private void writeTableDefinition(ByteBuffer buffer, List<Column> columns,
  306. int pageNumber)
  307. throws IOException {
  308. //Start writing the tdef
  309. buffer.put(PageTypes.TABLE_DEF); //Page type
  310. buffer.put((byte) 0x01); //Unknown
  311. buffer.put((byte) 0); //Unknown
  312. buffer.put((byte) 0); //Unknown
  313. buffer.putInt(0); //Next TDEF page pointer
  314. buffer.putInt(0); //Length of data for this page
  315. buffer.put((byte) 0x59); //Unknown
  316. buffer.put((byte) 0x06); //Unknown
  317. buffer.putShort((short) 0); //Unknown
  318. buffer.putInt(0); //Number of rows
  319. buffer.putInt(0); //Autonumber
  320. for (int i = 0; i < 16; i++) { //Unknown
  321. buffer.put((byte) 0);
  322. }
  323. buffer.put(Table.TYPE_USER); //Table type
  324. buffer.putShort((short) columns.size()); //Max columns a row will have
  325. buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table
  326. buffer.putShort((short) columns.size()); //Number of columns in table
  327. buffer.putInt(0); //Number of indexes in table
  328. buffer.putInt(0); //Number of indexes in table
  329. buffer.put((byte) 0); //Usage map row number
  330. int usageMapPage = pageNumber + 1;
  331. buffer.put(ByteUtil.to3ByteInt(usageMapPage)); //Usage map page number
  332. buffer.put((byte) 1); //Free map row number
  333. buffer.put(ByteUtil.to3ByteInt(usageMapPage)); //Free map page number
  334. if (LOG.isDebugEnabled()) {
  335. int position = buffer.position();
  336. buffer.rewind();
  337. LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString(
  338. buffer, _format.SIZE_TDEF_BLOCK));
  339. buffer.position(position);
  340. }
  341. }
  342. /**
  343. * @param buffer Buffer to write to
  344. * @param columns List of Columns to write definitions for
  345. */
  346. private void writeColumnDefinitions(ByteBuffer buffer, List columns)
  347. throws IOException {
  348. Iterator iter;
  349. short columnNumber = (short) 0;
  350. short fixedOffset = (short) 0;
  351. short variableOffset = (short) 0;
  352. for (iter = columns.iterator(); iter.hasNext(); columnNumber++) {
  353. Column col = (Column) iter.next();
  354. int position = buffer.position();
  355. buffer.put(col.getType().getValue());
  356. buffer.put((byte) 0x59); //Unknown
  357. buffer.put((byte) 0x06); //Unknown
  358. buffer.putShort((short) 0); //Unknown
  359. buffer.putShort(columnNumber); //Column Number
  360. if (col.getType().isVariableLength()) {
  361. buffer.putShort(variableOffset++);
  362. } else {
  363. buffer.putShort((short) 0);
  364. }
  365. buffer.putShort(columnNumber); //Column Number again
  366. buffer.put((byte) 0x09); //Unknown
  367. buffer.put((byte) 0x04); //Unknown
  368. buffer.putShort((short) 0); //Unknown
  369. if (col.getType().isVariableLength()) { //Variable length
  370. buffer.put((byte) 0x2);
  371. } else {
  372. buffer.put((byte) 0x3);
  373. }
  374. if (col.isCompressedUnicode()) { //Compressed
  375. buffer.put((byte) 1);
  376. } else {
  377. buffer.put((byte) 0);
  378. }
  379. buffer.putInt(0); //Unknown, but always 0.
  380. //Offset for fixed length columns
  381. if (col.getType().isVariableLength()) {
  382. buffer.putShort((short) 0);
  383. } else {
  384. buffer.putShort(fixedOffset);
  385. fixedOffset += col.getType().getSize();
  386. }
  387. buffer.putShort(col.getLength()); //Column length
  388. if (LOG.isDebugEnabled()) {
  389. LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
  390. buffer, position, _format.SIZE_COLUMN_DEF_BLOCK));
  391. }
  392. }
  393. iter = columns.iterator();
  394. while (iter.hasNext()) {
  395. Column col = (Column) iter.next();
  396. ByteBuffer colName = _format.CHARSET.encode(col.getName());
  397. buffer.putShort((short) colName.remaining());
  398. buffer.put(colName);
  399. }
  400. }
  401. /**
  402. * Create the usage map definition page buffer. It will be stored on the page
  403. * immediately after the tdef page.
  404. * @param pageNumber Page number that the corresponding table definition will
  405. * be written to
  406. */
  407. private ByteBuffer createUsageMapDefinitionBuffer(int pageNumber) throws IOException {
  408. ByteBuffer rtn = _pageChannel.createPageBuffer();
  409. rtn.put(PageTypes.DATA);
  410. rtn.put((byte) 0x1); //Unknown
  411. rtn.putShort(USAGE_MAP_DEF_FREE_SPACE); //Free space in page
  412. rtn.putInt(0); //Table definition
  413. rtn.putInt(0); //Unknown
  414. rtn.putShort((short) 2); //Number of records on this page
  415. rtn.putShort((short) _format.OFFSET_USED_PAGES_USAGE_MAP_DEF); //First location
  416. rtn.putShort((short) _format.OFFSET_FREE_PAGES_USAGE_MAP_DEF); //Second location
  417. rtn.position(_format.OFFSET_USED_PAGES_USAGE_MAP_DEF);
  418. rtn.put((byte) UsageMap.MAP_TYPE_REFERENCE);
  419. rtn.putInt(pageNumber + 2); //First referenced page number
  420. rtn.position(_format.OFFSET_FREE_PAGES_USAGE_MAP_DEF);
  421. rtn.put((byte) UsageMap.MAP_TYPE_INLINE);
  422. return rtn;
  423. }
  424. /**
  425. * Create a usage map data page buffer.
  426. */
  427. private ByteBuffer createUsageMapDataBuffer() throws IOException {
  428. ByteBuffer rtn = _pageChannel.createPageBuffer();
  429. rtn.put(PageTypes.USAGE_MAP);
  430. rtn.put((byte) 0x01); //Unknown
  431. rtn.putShort((short) 0); //Unknown
  432. return rtn;
  433. }
  434. /**
  435. * Add a new table to the system catalog
  436. * @param name Table name
  437. * @param pageNumber Page number that contains the table definition
  438. */
  439. private void addToSystemCatalog(String name, int pageNumber) throws IOException {
  440. Object[] catalogRow = new Object[_systemCatalog.getColumns().size()];
  441. int idx = 0;
  442. Iterator iter;
  443. for (iter = _systemCatalog.getColumns().iterator(); iter.hasNext(); idx++) {
  444. Column col = (Column) iter.next();
  445. if (COL_ID.equals(col.getName())) {
  446. catalogRow[idx] = new Integer(pageNumber);
  447. } else if (COL_NAME.equals(col.getName())) {
  448. catalogRow[idx] = name;
  449. } else if (COL_TYPE.equals(col.getName())) {
  450. catalogRow[idx] = TYPE_TABLE;
  451. } else if (COL_DATE_CREATE.equals(col.getName()) ||
  452. COL_DATE_UPDATE.equals(col.getName()))
  453. {
  454. catalogRow[idx] = new Date();
  455. } else if (COL_PARENT_ID.equals(col.getName())) {
  456. catalogRow[idx] = _tableParentId;
  457. } else if (COL_FLAGS.equals(col.getName())) {
  458. catalogRow[idx] = new Integer(0);
  459. } else if (COL_OWNER.equals(col.getName())) {
  460. byte[] owner = new byte[2];
  461. catalogRow[idx] = owner;
  462. owner[0] = (byte) 0xcf;
  463. owner[1] = (byte) 0x5f;
  464. }
  465. }
  466. _systemCatalog.addRow(catalogRow);
  467. }
  468. /**
  469. * Add a new table to the system's access control entries
  470. * @param pageNumber Page number that contains the table definition
  471. */
  472. private void addToAccessControlEntries(int pageNumber) throws IOException {
  473. Object[] aceRow = new Object[_accessControlEntries.getColumns().size()];
  474. int idx = 0;
  475. Iterator iter;
  476. for (iter = _accessControlEntries.getColumns().iterator(); iter.hasNext(); idx++) {
  477. Column col = (Column) iter.next();
  478. if (col.getName().equals(COL_ACM)) {
  479. aceRow[idx] = ACM;
  480. } else if (col.getName().equals(COL_F_INHERITABLE)) {
  481. aceRow[idx] = Boolean.FALSE;
  482. } else if (col.getName().equals(COL_OBJECT_ID)) {
  483. aceRow[idx] = new Integer(pageNumber);
  484. } else if (col.getName().equals(COL_SID)) {
  485. aceRow[idx] = SID;
  486. }
  487. }
  488. _accessControlEntries.addRow(aceRow);
  489. }
  490. /**
  491. * Copy an existing JDBC ResultSet into a new table in this database
  492. * @param name Name of the new table to create
  493. * @param source ResultSet to copy from
  494. */
  495. public void copyTable(String name, ResultSet source) throws SQLException, IOException {
  496. ResultSetMetaData md = source.getMetaData();
  497. List<Column> columns = new LinkedList<Column>();
  498. int textCount = 0;
  499. int totalSize = 0;
  500. for (int i = 1; i <= md.getColumnCount(); i++) {
  501. DataType accessColumnType = DataType.fromSQLType(md.getColumnType(i));
  502. switch (accessColumnType) {
  503. case BYTE:
  504. case INT:
  505. case LONG:
  506. case MONEY:
  507. case FLOAT:
  508. case NUMERIC:
  509. totalSize += 4;
  510. break;
  511. case DOUBLE:
  512. case SHORT_DATE_TIME:
  513. totalSize += 8;
  514. break;
  515. case BINARY:
  516. case TEXT:
  517. case OLE:
  518. case MEMO:
  519. textCount++;
  520. break;
  521. }
  522. }
  523. short textSize = 0;
  524. if (textCount > 0) {
  525. textSize = (short) ((JetFormat.MAX_RECORD_SIZE - totalSize) / textCount);
  526. if (textSize > JetFormat.TEXT_FIELD_MAX_LENGTH) {
  527. textSize = JetFormat.TEXT_FIELD_MAX_LENGTH;
  528. }
  529. }
  530. for (int i = 1; i <= md.getColumnCount(); i++) {
  531. Column column = new Column();
  532. column.setName(escape(md.getColumnName(i)));
  533. column.setType(DataType.fromSQLType(md.getColumnType(i)));
  534. if (column.getType() == DataType.TEXT) {
  535. column.setLength(textSize);
  536. }
  537. columns.add(column);
  538. }
  539. createTable(escape(name), columns);
  540. Table table = getTable(escape(name));
  541. List<Object[]> rows = new ArrayList<Object[]>();
  542. while (source.next()) {
  543. Object[] row = new Object[md.getColumnCount()];
  544. for (int i = 0; i < row.length; i++) {
  545. row[i] = source.getObject(i + 1);
  546. }
  547. rows.add(row);
  548. if (rows.size() == COPY_TABLE_BATCH_SIZE) {
  549. table.addRows(rows);
  550. rows.clear();
  551. }
  552. }
  553. if (rows.size() > 0) {
  554. table.addRows(rows);
  555. }
  556. }
  557. /**
  558. * Copy a delimited text file into a new table in this database
  559. * @param name Name of the new table to create
  560. * @param f Source file to import
  561. * @param delim Regular expression representing the delimiter string.
  562. */
  563. public void importFile(String name, File f, String delim)
  564. throws IOException, SQLException
  565. {
  566. BufferedReader in = null;
  567. try {
  568. in = new BufferedReader(new FileReader(f));
  569. importReader(name, in, delim);
  570. } finally {
  571. if (in != null) {
  572. try {
  573. in.close();
  574. } catch (IOException ex) {
  575. LOG.warn("Could not close file " + f.getAbsolutePath(), ex);
  576. }
  577. }
  578. }
  579. }
  580. /**
  581. * Copy a delimited text file into a new table in this database
  582. * @param name Name of the new table to create
  583. * @param in Source reader to import
  584. * @param delim Regular expression representing the delimiter string.
  585. */
  586. public void importReader(String name, BufferedReader in, String delim)
  587. throws IOException, SQLException
  588. {
  589. String line = in.readLine();
  590. if (line == null || line.trim().length() == 0) {
  591. return;
  592. }
  593. String tableName = escape(name);
  594. int counter = 0;
  595. while(getTable(tableName) != null) {
  596. tableName = escape(name + (counter++));
  597. }
  598. List<Column> columns = new LinkedList<Column>();
  599. String[] columnNames = line.split(delim);
  600. short textSize = (short) ((JetFormat.MAX_RECORD_SIZE) / columnNames.length);
  601. if (textSize > JetFormat.TEXT_FIELD_MAX_LENGTH) {
  602. textSize = JetFormat.TEXT_FIELD_MAX_LENGTH;
  603. }
  604. for (int i = 0; i < columnNames.length; i++) {
  605. Column column = new Column();
  606. column.setName(escape(columnNames[i]));
  607. column.setType(DataType.TEXT);
  608. column.setLength(textSize);
  609. columns.add(column);
  610. }
  611. createTable(tableName, columns);
  612. Table table = getTable(tableName);
  613. List<String[]> rows = new ArrayList<String[]>();
  614. while ((line = in.readLine()) != null)
  615. {
  616. //
  617. // Handle the situation where the end of the line
  618. // may have null fields. We always want to add the
  619. // same number of columns to the table each time.
  620. //
  621. String[] data = new String[columnNames.length];
  622. String[] splitData = line.split(delim);
  623. System.arraycopy(splitData, 0, data, 0, splitData.length);
  624. rows.add(data);
  625. if (rows.size() == COPY_TABLE_BATCH_SIZE) {
  626. table.addRows(rows);
  627. rows.clear();
  628. }
  629. }
  630. if (rows.size() > 0) {
  631. table.addRows(rows);
  632. }
  633. }
  634. /**
  635. * Close the database file
  636. */
  637. public void close() throws IOException {
  638. _pageChannel.close();
  639. }
  640. /**
  641. * @return A table or column name escaped for Access
  642. */
  643. private String escape(String s) {
  644. if (RESERVED_WORDS.contains(s.toLowerCase())) {
  645. return ESCAPE_PREFIX + s;
  646. } else {
  647. return s;
  648. }
  649. }
  650. public String toString() {
  651. return ToStringBuilder.reflectionToString(this);
  652. }
  653. }