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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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. if(!JetFormat.mayBeMdbFile(mdbFile)) {
  163. throw new FileNotFoundException("database file is empty or nonexistent");
  164. }
  165. return new Database(openChannel(mdbFile));
  166. }
  167. /**
  168. * Create a new Database
  169. * @param mdbFile Location to write the new database to. <b>If this file
  170. * already exists, it will be overwritten.</b>
  171. */
  172. public static Database create(File mdbFile) throws IOException, SQLException {
  173. FileChannel channel = openChannel(mdbFile);
  174. channel.transferFrom(Channels.newChannel(
  175. Thread.currentThread().getContextClassLoader().getResourceAsStream(
  176. EMPTY_MDB)), 0, (long) Integer.MAX_VALUE);
  177. return new Database(channel);
  178. }
  179. private static FileChannel openChannel(File mdbFile) throws FileNotFoundException {
  180. return new RandomAccessFile(mdbFile, "rw").getChannel();
  181. }
  182. /**
  183. * Create a new database by reading it in from a FileChannel.
  184. * @param channel File channel of the database. This needs to be a
  185. * FileChannel instead of a ReadableByteChannel because we need to
  186. * randomly jump around to various points in the file.
  187. */
  188. protected Database(FileChannel channel) throws IOException, SQLException {
  189. _format = JetFormat.getFormat(channel);
  190. _pageChannel = new PageChannel(channel, _format);
  191. _buffer = _pageChannel.createPageBuffer();
  192. readSystemCatalog();
  193. }
  194. public PageChannel getPageChannel() {
  195. return _pageChannel;
  196. }
  197. /**
  198. * @return The system catalog table
  199. */
  200. public Table getSystemCatalog() {
  201. return _systemCatalog;
  202. }
  203. public Table getAccessControlEntries() {
  204. return _accessControlEntries;
  205. }
  206. /**
  207. * Read the system catalog
  208. */
  209. private void readSystemCatalog() throws IOException, SQLException {
  210. _pageChannel.readPage(_buffer, PAGE_SYSTEM_CATALOG);
  211. byte pageType = _buffer.get();
  212. if (pageType != PageTypes.TABLE_DEF) {
  213. throw new IOException("Looking for system catalog at page " +
  214. PAGE_SYSTEM_CATALOG + ", but page type is " + pageType);
  215. }
  216. _systemCatalog = new Table(_buffer, _pageChannel, _format, PAGE_SYSTEM_CATALOG);
  217. Map row;
  218. while ( (row = _systemCatalog.getNextRow(Arrays.asList(
  219. COL_NAME, COL_TYPE, COL_ID))) != null)
  220. {
  221. String name = (String) row.get(COL_NAME);
  222. if (name != null && TYPE_TABLE.equals(row.get(COL_TYPE))) {
  223. if (!name.startsWith(PREFIX_SYSTEM)) {
  224. _tables.put((String) row.get(COL_NAME), (Integer) row.get(COL_ID));
  225. } else if (TABLE_SYSTEM_ACES.equals(name)) {
  226. readAccessControlEntries(((Integer) row.get(COL_ID)).intValue());
  227. }
  228. } else if (SYSTEM_OBJECT_NAME_TABLES.equals(name)) {
  229. _tableParentId = (Integer) row.get(COL_ID);
  230. }
  231. }
  232. if (LOG.isDebugEnabled()) {
  233. LOG.debug("Finished reading system catalog. Tables: " + _tables);
  234. }
  235. }
  236. /**
  237. * Read the system access control entries table
  238. * @param pageNum Page number of the table def
  239. */
  240. private void readAccessControlEntries(int pageNum) throws IOException, SQLException {
  241. ByteBuffer buffer = _pageChannel.createPageBuffer();
  242. _pageChannel.readPage(buffer, pageNum);
  243. byte pageType = buffer.get();
  244. if (pageType != PageTypes.TABLE_DEF) {
  245. throw new IOException("Looking for MSysACEs at page " + pageNum +
  246. ", but page type is " + pageType);
  247. }
  248. _accessControlEntries = new Table(buffer, _pageChannel, _format, pageNum);
  249. }
  250. /**
  251. * @return The names of all of the user tables (String)
  252. */
  253. public Set<String> getTableNames() {
  254. return _tables.keySet();
  255. }
  256. /**
  257. * @param name Table name
  258. * @return The table, or null if it doesn't exist
  259. */
  260. public Table getTable(String name) throws IOException, SQLException {
  261. Integer pageNumber = (Integer) _tables.get(name);
  262. if (pageNumber == null) {
  263. // Bug workaround:
  264. pageNumber = (Integer) _tables.get(Character.toUpperCase(name.charAt(0)) +
  265. name.substring(1));
  266. }
  267. if (pageNumber == null) {
  268. return null;
  269. } else {
  270. _pageChannel.readPage(_buffer, pageNumber.intValue());
  271. return new Table(_buffer, _pageChannel, _format, pageNumber.intValue());
  272. }
  273. }
  274. /**
  275. * Create a new table in this database
  276. * @param name Name of the table to create
  277. * @param columns List of Columns in the table
  278. */
  279. //XXX Set up 1-page rollback buffer?
  280. public void createTable(String name, List<Column> columns) throws IOException {
  281. //There is some really bizarre bug in here where tables that start with
  282. //the letters a-m (only lower case) won't open in Access. :)
  283. name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
  284. //We are creating a new page at the end of the db for the tdef.
  285. int pageNumber = _pageChannel.getPageCount();
  286. ByteBuffer buffer = _pageChannel.createPageBuffer();
  287. writeTableDefinition(buffer, columns, pageNumber);
  288. writeColumnDefinitions(buffer, columns);
  289. //End of tabledef
  290. buffer.put((byte) 0xff);
  291. buffer.put((byte) 0xff);
  292. buffer.putInt(8, buffer.position()); //Overwrite length of data for this page
  293. //Write the tdef and usage map pages to disk.
  294. _pageChannel.writeNewPage(buffer);
  295. _pageChannel.writeNewPage(createUsageMapDefinitionBuffer(pageNumber));
  296. _pageChannel.writeNewPage(createUsageMapDataBuffer()); //Usage map
  297. //Add this table to our internal list.
  298. _tables.put(name, new Integer(pageNumber));
  299. //Add this table to system tables
  300. addToSystemCatalog(name, pageNumber);
  301. addToAccessControlEntries(pageNumber);
  302. }
  303. /**
  304. * @param buffer Buffer to write to
  305. * @param columns List of Columns in the table
  306. * @param pageNumber Page number that this table definition will be written to
  307. */
  308. private void writeTableDefinition(ByteBuffer buffer, List<Column> columns,
  309. int pageNumber)
  310. throws IOException {
  311. //Start writing the tdef
  312. buffer.put(PageTypes.TABLE_DEF); //Page type
  313. buffer.put((byte) 0x01); //Unknown
  314. buffer.put((byte) 0); //Unknown
  315. buffer.put((byte) 0); //Unknown
  316. buffer.putInt(0); //Next TDEF page pointer
  317. buffer.putInt(0); //Length of data for this page
  318. buffer.put((byte) 0x59); //Unknown
  319. buffer.put((byte) 0x06); //Unknown
  320. buffer.putShort((short) 0); //Unknown
  321. buffer.putInt(0); //Number of rows
  322. buffer.putInt(0); //Autonumber
  323. for (int i = 0; i < 16; i++) { //Unknown
  324. buffer.put((byte) 0);
  325. }
  326. buffer.put(Table.TYPE_USER); //Table type
  327. buffer.putShort((short) columns.size()); //Max columns a row will have
  328. buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table
  329. buffer.putShort((short) columns.size()); //Number of columns in table
  330. buffer.putInt(0); //Number of indexes in table
  331. buffer.putInt(0); //Number of indexes in table
  332. buffer.put((byte) 0); //Usage map row number
  333. int usageMapPage = pageNumber + 1;
  334. buffer.put(ByteUtil.to3ByteInt(usageMapPage)); //Usage map page number
  335. buffer.put((byte) 1); //Free map row number
  336. buffer.put(ByteUtil.to3ByteInt(usageMapPage)); //Free map page number
  337. if (LOG.isDebugEnabled()) {
  338. int position = buffer.position();
  339. buffer.rewind();
  340. LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString(
  341. buffer, _format.SIZE_TDEF_BLOCK));
  342. buffer.position(position);
  343. }
  344. }
  345. /**
  346. * @param buffer Buffer to write to
  347. * @param columns List of Columns to write definitions for
  348. */
  349. private void writeColumnDefinitions(ByteBuffer buffer, List columns)
  350. throws IOException {
  351. Iterator iter;
  352. short columnNumber = (short) 0;
  353. short fixedOffset = (short) 0;
  354. short variableOffset = (short) 0;
  355. for (iter = columns.iterator(); iter.hasNext(); columnNumber++) {
  356. Column col = (Column) iter.next();
  357. int position = buffer.position();
  358. buffer.put(col.getType().getValue());
  359. buffer.put((byte) 0x59); //Unknown
  360. buffer.put((byte) 0x06); //Unknown
  361. buffer.putShort((short) 0); //Unknown
  362. buffer.putShort(columnNumber); //Column Number
  363. if (col.getType().isVariableLength()) {
  364. buffer.putShort(variableOffset++);
  365. } else {
  366. buffer.putShort((short) 0);
  367. }
  368. buffer.putShort(columnNumber); //Column Number again
  369. buffer.put((byte) 0x09); //Unknown
  370. buffer.put((byte) 0x04); //Unknown
  371. buffer.putShort((short) 0); //Unknown
  372. if (col.getType().isVariableLength()) { //Variable length
  373. buffer.put((byte) 0x2);
  374. } else {
  375. buffer.put((byte) 0x3);
  376. }
  377. if (col.isCompressedUnicode()) { //Compressed
  378. buffer.put((byte) 1);
  379. } else {
  380. buffer.put((byte) 0);
  381. }
  382. buffer.putInt(0); //Unknown, but always 0.
  383. //Offset for fixed length columns
  384. if (col.getType().isVariableLength()) {
  385. buffer.putShort((short) 0);
  386. } else {
  387. buffer.putShort(fixedOffset);
  388. fixedOffset += col.getType().getSize();
  389. }
  390. buffer.putShort(col.getLength()); //Column length
  391. if (LOG.isDebugEnabled()) {
  392. LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
  393. buffer, position, _format.SIZE_COLUMN_DEF_BLOCK));
  394. }
  395. }
  396. iter = columns.iterator();
  397. while (iter.hasNext()) {
  398. Column col = (Column) iter.next();
  399. ByteBuffer colName = _format.CHARSET.encode(col.getName());
  400. buffer.putShort((short) colName.remaining());
  401. buffer.put(colName);
  402. }
  403. }
  404. /**
  405. * Create the usage map definition page buffer. It will be stored on the page
  406. * immediately after the tdef page.
  407. * @param pageNumber Page number that the corresponding table definition will
  408. * be written to
  409. */
  410. private ByteBuffer createUsageMapDefinitionBuffer(int pageNumber) throws IOException {
  411. ByteBuffer rtn = _pageChannel.createPageBuffer();
  412. rtn.put(PageTypes.DATA);
  413. rtn.put((byte) 0x1); //Unknown
  414. rtn.putShort(USAGE_MAP_DEF_FREE_SPACE); //Free space in page
  415. rtn.putInt(0); //Table definition
  416. rtn.putInt(0); //Unknown
  417. rtn.putShort((short) 2); //Number of records on this page
  418. rtn.putShort((short) _format.OFFSET_USED_PAGES_USAGE_MAP_DEF); //First location
  419. rtn.putShort((short) _format.OFFSET_FREE_PAGES_USAGE_MAP_DEF); //Second location
  420. rtn.position(_format.OFFSET_USED_PAGES_USAGE_MAP_DEF);
  421. rtn.put((byte) UsageMap.MAP_TYPE_REFERENCE);
  422. rtn.putInt(pageNumber + 2); //First referenced page number
  423. rtn.position(_format.OFFSET_FREE_PAGES_USAGE_MAP_DEF);
  424. rtn.put((byte) UsageMap.MAP_TYPE_INLINE);
  425. return rtn;
  426. }
  427. /**
  428. * Create a usage map data page buffer.
  429. */
  430. private ByteBuffer createUsageMapDataBuffer() throws IOException {
  431. ByteBuffer rtn = _pageChannel.createPageBuffer();
  432. rtn.put(PageTypes.USAGE_MAP);
  433. rtn.put((byte) 0x01); //Unknown
  434. rtn.putShort((short) 0); //Unknown
  435. return rtn;
  436. }
  437. /**
  438. * Add a new table to the system catalog
  439. * @param name Table name
  440. * @param pageNumber Page number that contains the table definition
  441. */
  442. private void addToSystemCatalog(String name, int pageNumber) throws IOException {
  443. Object[] catalogRow = new Object[_systemCatalog.getColumns().size()];
  444. int idx = 0;
  445. Iterator iter;
  446. for (iter = _systemCatalog.getColumns().iterator(); iter.hasNext(); idx++) {
  447. Column col = (Column) iter.next();
  448. if (COL_ID.equals(col.getName())) {
  449. catalogRow[idx] = new Integer(pageNumber);
  450. } else if (COL_NAME.equals(col.getName())) {
  451. catalogRow[idx] = name;
  452. } else if (COL_TYPE.equals(col.getName())) {
  453. catalogRow[idx] = TYPE_TABLE;
  454. } else if (COL_DATE_CREATE.equals(col.getName()) ||
  455. COL_DATE_UPDATE.equals(col.getName()))
  456. {
  457. catalogRow[idx] = new Date();
  458. } else if (COL_PARENT_ID.equals(col.getName())) {
  459. catalogRow[idx] = _tableParentId;
  460. } else if (COL_FLAGS.equals(col.getName())) {
  461. catalogRow[idx] = new Integer(0);
  462. } else if (COL_OWNER.equals(col.getName())) {
  463. byte[] owner = new byte[2];
  464. catalogRow[idx] = owner;
  465. owner[0] = (byte) 0xcf;
  466. owner[1] = (byte) 0x5f;
  467. }
  468. }
  469. _systemCatalog.addRow(catalogRow);
  470. }
  471. /**
  472. * Add a new table to the system's access control entries
  473. * @param pageNumber Page number that contains the table definition
  474. */
  475. private void addToAccessControlEntries(int pageNumber) throws IOException {
  476. Object[] aceRow = new Object[_accessControlEntries.getColumns().size()];
  477. int idx = 0;
  478. Iterator iter;
  479. for (iter = _accessControlEntries.getColumns().iterator(); iter.hasNext(); idx++) {
  480. Column col = (Column) iter.next();
  481. if (col.getName().equals(COL_ACM)) {
  482. aceRow[idx] = ACM;
  483. } else if (col.getName().equals(COL_F_INHERITABLE)) {
  484. aceRow[idx] = Boolean.FALSE;
  485. } else if (col.getName().equals(COL_OBJECT_ID)) {
  486. aceRow[idx] = new Integer(pageNumber);
  487. } else if (col.getName().equals(COL_SID)) {
  488. aceRow[idx] = SID;
  489. }
  490. }
  491. _accessControlEntries.addRow(aceRow);
  492. }
  493. /**
  494. * Copy an existing JDBC ResultSet into a new table in this database
  495. * @param name Name of the new table to create
  496. * @param source ResultSet to copy from
  497. */
  498. public void copyTable(String name, ResultSet source) throws SQLException, IOException {
  499. ResultSetMetaData md = source.getMetaData();
  500. List<Column> columns = new LinkedList<Column>();
  501. int textCount = 0;
  502. int totalSize = 0;
  503. for (int i = 1; i <= md.getColumnCount(); i++) {
  504. DataType accessColumnType = DataType.fromSQLType(md.getColumnType(i));
  505. switch (accessColumnType) {
  506. case BYTE:
  507. case INT:
  508. case LONG:
  509. case MONEY:
  510. case FLOAT:
  511. case NUMERIC:
  512. totalSize += 4;
  513. break;
  514. case DOUBLE:
  515. case SHORT_DATE_TIME:
  516. totalSize += 8;
  517. break;
  518. case BINARY:
  519. case TEXT:
  520. case OLE:
  521. case MEMO:
  522. textCount++;
  523. break;
  524. }
  525. }
  526. short textSize = 0;
  527. if (textCount > 0) {
  528. textSize = (short) ((JetFormat.MAX_RECORD_SIZE - totalSize) / textCount);
  529. if (textSize > JetFormat.TEXT_FIELD_MAX_LENGTH) {
  530. textSize = JetFormat.TEXT_FIELD_MAX_LENGTH;
  531. }
  532. }
  533. for (int i = 1; i <= md.getColumnCount(); i++) {
  534. Column column = new Column();
  535. column.setName(escape(md.getColumnName(i)));
  536. column.setType(DataType.fromSQLType(md.getColumnType(i)));
  537. if (column.getType() == DataType.TEXT) {
  538. column.setLength(textSize);
  539. }
  540. columns.add(column);
  541. }
  542. createTable(escape(name), columns);
  543. Table table = getTable(escape(name));
  544. List<Object[]> rows = new ArrayList<Object[]>();
  545. while (source.next()) {
  546. Object[] row = new Object[md.getColumnCount()];
  547. for (int i = 0; i < row.length; i++) {
  548. row[i] = source.getObject(i + 1);
  549. }
  550. rows.add(row);
  551. if (rows.size() == COPY_TABLE_BATCH_SIZE) {
  552. table.addRows(rows);
  553. rows.clear();
  554. }
  555. }
  556. if (rows.size() > 0) {
  557. table.addRows(rows);
  558. }
  559. }
  560. /**
  561. * Copy a delimited text file into a new table in this database
  562. * @param name Name of the new table to create
  563. * @param f Source file to import
  564. * @param delim Regular expression representing the delimiter string.
  565. */
  566. public void importFile(String name, File f, String delim)
  567. throws IOException, SQLException
  568. {
  569. BufferedReader in = null;
  570. try {
  571. in = new BufferedReader(new FileReader(f));
  572. importReader(name, in, delim);
  573. } finally {
  574. if (in != null) {
  575. try {
  576. in.close();
  577. } catch (IOException ex) {
  578. LOG.warn("Could not close file " + f.getAbsolutePath(), ex);
  579. }
  580. }
  581. }
  582. }
  583. /**
  584. * Copy a delimited text file into a new table in this database
  585. * @param name Name of the new table to create
  586. * @param in Source reader to import
  587. * @param delim Regular expression representing the delimiter string.
  588. */
  589. public void importReader(String name, BufferedReader in, String delim)
  590. throws IOException, SQLException
  591. {
  592. String line = in.readLine();
  593. if (line == null || line.trim().length() == 0) {
  594. return;
  595. }
  596. String tableName = escape(name);
  597. int counter = 0;
  598. while(getTable(tableName) != null) {
  599. tableName = escape(name + (counter++));
  600. }
  601. List<Column> columns = new LinkedList<Column>();
  602. String[] columnNames = line.split(delim);
  603. short textSize = (short) ((JetFormat.MAX_RECORD_SIZE) / columnNames.length);
  604. if (textSize > JetFormat.TEXT_FIELD_MAX_LENGTH) {
  605. textSize = JetFormat.TEXT_FIELD_MAX_LENGTH;
  606. }
  607. for (int i = 0; i < columnNames.length; i++) {
  608. Column column = new Column();
  609. column.setName(escape(columnNames[i]));
  610. column.setType(DataType.TEXT);
  611. column.setLength(textSize);
  612. columns.add(column);
  613. }
  614. createTable(tableName, columns);
  615. Table table = getTable(tableName);
  616. List<String[]> rows = new ArrayList<String[]>();
  617. while ((line = in.readLine()) != null)
  618. {
  619. //
  620. // Handle the situation where the end of the line
  621. // may have null fields. We always want to add the
  622. // same number of columns to the table each time.
  623. //
  624. String[] data = new String[columnNames.length];
  625. String[] splitData = line.split(delim);
  626. System.arraycopy(splitData, 0, data, 0, splitData.length);
  627. rows.add(data);
  628. if (rows.size() == COPY_TABLE_BATCH_SIZE) {
  629. table.addRows(rows);
  630. rows.clear();
  631. }
  632. }
  633. if (rows.size() > 0) {
  634. table.addRows(rows);
  635. }
  636. }
  637. /**
  638. * Close the database file
  639. */
  640. public void close() throws IOException {
  641. _pageChannel.close();
  642. }
  643. /**
  644. * @return A table or column name escaped for Access
  645. */
  646. private String escape(String s) {
  647. if (RESERVED_WORDS.contains(s.toLowerCase())) {
  648. return ESCAPE_PREFIX + s;
  649. } else {
  650. return s;
  651. }
  652. }
  653. public String toString() {
  654. return ToStringBuilder.reflectionToString(this);
  655. }
  656. }