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 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  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.Collection;
  39. import java.util.Date;
  40. import java.util.HashMap;
  41. import java.util.HashSet;
  42. import java.util.Iterator;
  43. import java.util.LinkedList;
  44. import java.util.List;
  45. import java.util.Map;
  46. import java.util.NoSuchElementException;
  47. import java.util.Set;
  48. import org.apache.commons.lang.builder.ToStringBuilder;
  49. import org.apache.commons.logging.Log;
  50. import org.apache.commons.logging.LogFactory;
  51. /**
  52. * An Access database.
  53. *
  54. * @author Tim McCune
  55. */
  56. public class Database
  57. implements Iterable<Table>
  58. {
  59. private static final Log LOG = LogFactory.getLog(Database.class);
  60. private static final byte[] SID = new byte[2];
  61. static {
  62. SID[0] = (byte) 0xA6;
  63. SID[1] = (byte) 0x33;
  64. }
  65. /** Batch commit size for copying other result sets into this database */
  66. private static final int COPY_TABLE_BATCH_SIZE = 200;
  67. /** System catalog always lives on page 2 */
  68. private static final int PAGE_SYSTEM_CATALOG = 2;
  69. private static final int ACM = 1048319;
  70. /** Free space left in page for new usage map definition pages */
  71. private static final short USAGE_MAP_DEF_FREE_SPACE = 3940;
  72. private static final String COL_ACM = "ACM";
  73. /** System catalog column name of the date a system object was created */
  74. private static final String COL_DATE_CREATE = "DateCreate";
  75. /** System catalog column name of the date a system object was updated */
  76. private static final String COL_DATE_UPDATE = "DateUpdate";
  77. private static final String COL_F_INHERITABLE = "FInheritable";
  78. private static final String COL_FLAGS = "Flags";
  79. /**
  80. * System catalog column name of the page on which system object definitions
  81. * are stored
  82. */
  83. private static final String COL_ID = "Id";
  84. /** System catalog column name of the name of a system object */
  85. private static final String COL_NAME = "Name";
  86. private static final String COL_OBJECT_ID = "ObjectId";
  87. private static final String COL_OWNER = "Owner";
  88. /** System catalog column name of a system object's parent's id */
  89. private static final String COL_PARENT_ID = "ParentId";
  90. private static final String COL_SID = "SID";
  91. /** System catalog column name of the type of a system object */
  92. private static final String COL_TYPE = "Type";
  93. /** Empty database template for creating new databases */
  94. private static final String EMPTY_MDB = "com/healthmarketscience/jackcess/empty.mdb";
  95. /** Prefix for column or table names that are reserved words */
  96. private static final String ESCAPE_PREFIX = "x";
  97. /** Prefix that flags system tables */
  98. private static final String PREFIX_SYSTEM = "MSys";
  99. /** Name of the system object that is the parent of all tables */
  100. private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
  101. /** Name of the table that contains system access control entries */
  102. private static final String TABLE_SYSTEM_ACES = "MSysACEs";
  103. /** System object type for table definitions */
  104. private static final Short TYPE_TABLE = (short) 1;
  105. /** the columns to read when reading system catalog initially */
  106. private static Collection<String> SYSTEM_CATALOG_COLUMNS =
  107. new HashSet<String>(Arrays.asList(COL_NAME, COL_TYPE, COL_ID));
  108. /**
  109. * All of the reserved words in Access that should be escaped when creating
  110. * table or column names
  111. */
  112. private static final Set<String> RESERVED_WORDS = new HashSet<String>();
  113. static {
  114. //Yup, there's a lot.
  115. RESERVED_WORDS.addAll(Arrays.asList(
  116. "add", "all", "alphanumeric", "alter", "and", "any", "application", "as",
  117. "asc", "assistant", "autoincrement", "avg", "between", "binary", "bit",
  118. "boolean", "by", "byte", "char", "character", "column", "compactdatabase",
  119. "constraint", "container", "count", "counter", "create", "createdatabase",
  120. "createfield", "creategroup", "createindex", "createobject", "createproperty",
  121. "createrelation", "createtabledef", "createuser", "createworkspace",
  122. "currency", "currentuser", "database", "date", "datetime", "delete",
  123. "desc", "description", "disallow", "distinct", "distinctrow", "document",
  124. "double", "drop", "echo", "else", "end", "eqv", "error", "exists", "exit",
  125. "false", "field", "fields", "fillcache", "float", "float4", "float8",
  126. "foreign", "form", "forms", "from", "full", "function", "general",
  127. "getobject", "getoption", "gotopage", "group", "group by", "guid", "having",
  128. "idle", "ieeedouble", "ieeesingle", "if", "ignore", "imp", "in", "index",
  129. "indexes", "inner", "insert", "inserttext", "int", "integer", "integer1",
  130. "integer2", "integer4", "into", "is", "join", "key", "lastmodified", "left",
  131. "level", "like", "logical", "logical1", "long", "longbinary", "longtext",
  132. "macro", "match", "max", "min", "mod", "memo", "module", "money", "move",
  133. "name", "newpassword", "no", "not", "null", "number", "numeric", "object",
  134. "oleobject", "off", "on", "openrecordset", "option", "or", "order", "outer",
  135. "owneraccess", "parameter", "parameters", "partial", "percent", "pivot",
  136. "primary", "procedure", "property", "queries", "query", "quit", "real",
  137. "recalc", "recordset", "references", "refresh", "refreshlink",
  138. "registerdatabase", "relation", "repaint", "repairdatabase", "report",
  139. "reports", "requery", "right", "screen", "section", "select", "set",
  140. "setfocus", "setoption", "short", "single", "smallint", "some", "sql",
  141. "stdev", "stdevp", "string", "sum", "table", "tabledef", "tabledefs",
  142. "tableid", "text", "time", "timestamp", "top", "transform", "true", "type",
  143. "union", "unique", "update", "user", "value", "values", "var", "varp",
  144. "varbinary", "varchar", "where", "with", "workspace", "xor", "year", "yes",
  145. "yesno"
  146. ));
  147. }
  148. /** Buffer to hold database pages */
  149. private ByteBuffer _buffer;
  150. /** ID of the Tables system object */
  151. private Integer _tableParentId;
  152. /** Format that the containing database is in */
  153. private JetFormat _format;
  154. /**
  155. * Map of UPPERCASE table names to page numbers containing their definition
  156. * and their stored table name.
  157. */
  158. private Map<String, TableInfo> _tableLookup =
  159. new HashMap<String, TableInfo>();
  160. /** set of table names as stored in the mdb file, created on demand */
  161. private Set<String> _tableNames;
  162. /** Reads and writes database pages */
  163. private PageChannel _pageChannel;
  164. /** System catalog table */
  165. private Table _systemCatalog;
  166. /** System access control entries table */
  167. private Table _accessControlEntries;
  168. /**
  169. * Open an existing Database. If the existing file is not writeable, the
  170. * file will be opened read-only.
  171. * @param mdbFile File containing the database
  172. */
  173. public static Database open(File mdbFile) throws IOException {
  174. return open(mdbFile, false);
  175. }
  176. /**
  177. * Open an existing Database. If the existing file is not writeable or the
  178. * readOnly flag is <code>true</code>, the file will be opened read-only.
  179. * @param mdbFile File containing the database
  180. * @param readOnly iff <code>true</code>, force opening file in read-only
  181. * mode
  182. */
  183. public static Database open(File mdbFile, boolean readOnly)
  184. throws IOException
  185. {
  186. if(!mdbFile.exists() || !mdbFile.canRead()) {
  187. throw new FileNotFoundException("given file does not exist: " + mdbFile);
  188. }
  189. return new Database(openChannel(mdbFile,
  190. (!mdbFile.canWrite() || readOnly)));
  191. }
  192. /**
  193. * Create a new Database
  194. * @param mdbFile Location to write the new database to. <b>If this file
  195. * already exists, it will be overwritten.</b>
  196. */
  197. public static Database create(File mdbFile) throws IOException {
  198. FileChannel channel = openChannel(mdbFile, false);
  199. channel.transferFrom(Channels.newChannel(
  200. Thread.currentThread().getContextClassLoader().getResourceAsStream(
  201. EMPTY_MDB)), 0, (long) Integer.MAX_VALUE);
  202. return new Database(channel);
  203. }
  204. private static FileChannel openChannel(File mdbFile, boolean readOnly)
  205. throws FileNotFoundException
  206. {
  207. String mode = (readOnly ? "r" : "rw");
  208. return new RandomAccessFile(mdbFile, mode).getChannel();
  209. }
  210. /**
  211. * Create a new database by reading it in from a FileChannel.
  212. * @param channel File channel of the database. This needs to be a
  213. * FileChannel instead of a ReadableByteChannel because we need to
  214. * randomly jump around to various points in the file.
  215. */
  216. protected Database(FileChannel channel) throws IOException {
  217. _format = JetFormat.getFormat(channel);
  218. _pageChannel = new PageChannel(channel, _format);
  219. _buffer = _pageChannel.createPageBuffer();
  220. readSystemCatalog();
  221. }
  222. public PageChannel getPageChannel() {
  223. return _pageChannel;
  224. }
  225. /**
  226. * @return The system catalog table
  227. */
  228. public Table getSystemCatalog() {
  229. return _systemCatalog;
  230. }
  231. public Table getAccessControlEntries() {
  232. return _accessControlEntries;
  233. }
  234. /**
  235. * Read the system catalog
  236. */
  237. private void readSystemCatalog() throws IOException {
  238. _pageChannel.readPage(_buffer, PAGE_SYSTEM_CATALOG);
  239. byte pageType = _buffer.get();
  240. if (pageType != PageTypes.TABLE_DEF) {
  241. throw new IOException("Looking for system catalog at page " +
  242. PAGE_SYSTEM_CATALOG + ", but page type is " + pageType);
  243. }
  244. _systemCatalog = new Table(_buffer, _pageChannel, _format, PAGE_SYSTEM_CATALOG, "System Catalog");
  245. Map row;
  246. while ( (row = _systemCatalog.getNextRow(SYSTEM_CATALOG_COLUMNS)) != null)
  247. {
  248. String name = (String) row.get(COL_NAME);
  249. if (name != null && TYPE_TABLE.equals(row.get(COL_TYPE))) {
  250. if (!name.startsWith(PREFIX_SYSTEM)) {
  251. addTable((String) row.get(COL_NAME), (Integer) row.get(COL_ID));
  252. } else if (TABLE_SYSTEM_ACES.equals(name)) {
  253. readAccessControlEntries(((Integer) row.get(COL_ID)).intValue());
  254. }
  255. } else if (SYSTEM_OBJECT_NAME_TABLES.equals(name)) {
  256. _tableParentId = (Integer) row.get(COL_ID);
  257. }
  258. }
  259. if (LOG.isDebugEnabled()) {
  260. LOG.debug("Finished reading system catalog. Tables: " +
  261. getTableNames());
  262. }
  263. }
  264. /**
  265. * Read the system access control entries table
  266. * @param pageNum Page number of the table def
  267. */
  268. private void readAccessControlEntries(int pageNum) throws IOException {
  269. ByteBuffer buffer = _pageChannel.createPageBuffer();
  270. _pageChannel.readPage(buffer, pageNum);
  271. byte pageType = buffer.get();
  272. if (pageType != PageTypes.TABLE_DEF) {
  273. throw new IOException("Looking for MSysACEs at page " + pageNum +
  274. ", but page type is " + pageType);
  275. }
  276. _accessControlEntries = new Table(buffer, _pageChannel, _format, pageNum, "Access Control Entries");
  277. }
  278. /**
  279. * @return The names of all of the user tables (String)
  280. */
  281. public Set<String> getTableNames() {
  282. if(_tableNames == null) {
  283. _tableNames = new HashSet<String>();
  284. for(TableInfo tableInfo : _tableLookup.values()) {
  285. _tableNames.add(tableInfo.tableName);
  286. }
  287. }
  288. return _tableNames;
  289. }
  290. /**
  291. * @return an unmodifiable Iterator of the user Tables in this Database.
  292. * @throws IllegalStateException if an IOException is thrown by one of the
  293. * operations, the actual exception will be contained within
  294. * @throws ConcurrentModificationException if a table is added to the
  295. * database while an Iterator is in use.
  296. */
  297. public Iterator<Table> iterator() {
  298. return new TableIterator();
  299. }
  300. /**
  301. * @param name Table name
  302. * @return The table, or null if it doesn't exist
  303. */
  304. public Table getTable(String name) throws IOException {
  305. TableInfo tableInfo = lookupTable(name);
  306. if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
  307. return null;
  308. } else {
  309. int pageNumber = tableInfo.pageNumber.intValue();
  310. _pageChannel.readPage(_buffer, pageNumber);
  311. return new Table(_buffer, _pageChannel, _format, pageNumber,
  312. tableInfo.tableName);
  313. }
  314. }
  315. /**
  316. * Create a new table in this database
  317. * @param name Name of the table to create
  318. * @param columns List of Columns in the table
  319. */
  320. //XXX Set up 1-page rollback buffer?
  321. public void createTable(String name, List<Column> columns)
  322. throws IOException
  323. {
  324. if(getTable(name) != null) {
  325. throw new IllegalArgumentException(
  326. "Cannot create table with name of existing table");
  327. }
  328. if(columns.isEmpty()) {
  329. throw new IllegalArgumentException(
  330. "Cannot create table with no columns");
  331. }
  332. Set<String> colNames = new HashSet<String>();
  333. // next, validate the column definitions
  334. for(Column column : columns) {
  335. column.validate();
  336. if(!colNames.add(column.getName().toUpperCase())) {
  337. throw new IllegalArgumentException("duplicate column name: " +
  338. column.getName());
  339. }
  340. }
  341. //We are creating a new page at the end of the db for the tdef.
  342. int pageNumber = _pageChannel.getPageCount();
  343. ByteBuffer buffer = _pageChannel.createPageBuffer();
  344. writeTableDefinition(buffer, columns, pageNumber);
  345. writeColumnDefinitions(buffer, columns);
  346. //End of tabledef
  347. buffer.put((byte) 0xff);
  348. buffer.put((byte) 0xff);
  349. int tableDefLen = buffer.position();
  350. buffer.putShort(2, (short)(_format.PAGE_SIZE - tableDefLen - 8)); // overwrite page free space
  351. buffer.putInt(8, tableDefLen); //Overwrite length of data for this page
  352. //Write the tdef and usage map pages to disk.
  353. _pageChannel.writeNewPage(buffer);
  354. _pageChannel.writeNewPage(createUsageMapDefinitionBuffer(pageNumber));
  355. _pageChannel.writeNewPage(createUsageMapDataBuffer()); //Usage map
  356. //Add this table to our internal list.
  357. addTable(name, new Integer(pageNumber));
  358. //Add this table to system tables
  359. addToSystemCatalog(name, pageNumber);
  360. addToAccessControlEntries(pageNumber);
  361. }
  362. /**
  363. * @param buffer Buffer to write to
  364. * @param columns List of Columns in the table
  365. * @param pageNumber Page number that this table definition will be written to
  366. */
  367. private void writeTableDefinition(ByteBuffer buffer, List<Column> columns,
  368. int pageNumber)
  369. throws IOException {
  370. //Start writing the tdef
  371. buffer.put(PageTypes.TABLE_DEF); //Page type
  372. buffer.put((byte) 0x01); //Unknown
  373. buffer.put((byte) 0); //Unknown
  374. buffer.put((byte) 0); //Unknown
  375. buffer.putInt(0); //Next TDEF page pointer
  376. buffer.putInt(0); //Length of data for this page
  377. buffer.put((byte) 0x59); //Unknown
  378. buffer.put((byte) 0x06); //Unknown
  379. buffer.putShort((short) 0); //Unknown
  380. buffer.putInt(0); //Number of rows
  381. buffer.putInt(0); //Autonumber
  382. for (int i = 0; i < 16; i++) { //Unknown
  383. buffer.put((byte) 0);
  384. }
  385. buffer.put(Table.TYPE_USER); //Table type
  386. buffer.putShort((short) columns.size()); //Max columns a row will have
  387. buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table
  388. buffer.putShort((short) columns.size()); //Number of columns in table
  389. buffer.putInt(0); //Number of indexes in table
  390. buffer.putInt(0); //Number of indexes in table
  391. buffer.put((byte) 0); //Usage map row number
  392. int usageMapPage = pageNumber + 1;
  393. ByteUtil.put3ByteInt(buffer, usageMapPage); //Usage map page number
  394. buffer.put((byte) 1); //Free map row number
  395. ByteUtil.put3ByteInt(buffer, usageMapPage); //Free map page number
  396. if (LOG.isDebugEnabled()) {
  397. int position = buffer.position();
  398. buffer.rewind();
  399. LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString(
  400. buffer, _format.SIZE_TDEF_BLOCK));
  401. buffer.position(position);
  402. }
  403. }
  404. /**
  405. * @param buffer Buffer to write to
  406. * @param columns List of Columns to write definitions for
  407. */
  408. private void writeColumnDefinitions(ByteBuffer buffer, List columns)
  409. throws IOException {
  410. Iterator iter;
  411. short columnNumber = (short) 0;
  412. short fixedOffset = (short) 0;
  413. short variableOffset = (short) 0;
  414. for (iter = columns.iterator(); iter.hasNext(); columnNumber++) {
  415. Column col = (Column) iter.next();
  416. int position = buffer.position();
  417. buffer.put(col.getType().getValue());
  418. buffer.put((byte) 0x59); //Unknown
  419. buffer.put((byte) 0x06); //Unknown
  420. buffer.putShort((short) 0); //Unknown
  421. buffer.putShort(columnNumber); //Column Number
  422. if (col.isVariableLength()) {
  423. buffer.putShort(variableOffset++);
  424. } else {
  425. buffer.putShort((short) 0);
  426. }
  427. buffer.putShort(columnNumber); //Column Number again
  428. if(col.getType().getHasScalePrecision()) {
  429. buffer.put((byte) col.getPrecision()); // numeric precision
  430. buffer.put((byte) col.getScale()); // numeric scale
  431. } else {
  432. buffer.put((byte) 0x00); //unused
  433. buffer.put((byte) 0x00); //unused
  434. }
  435. buffer.putShort((short) 0); //Unknown
  436. if (col.isVariableLength()) { //Variable length
  437. buffer.put((byte) 0x2);
  438. } else {
  439. buffer.put((byte) 0x3);
  440. }
  441. if (col.isCompressedUnicode()) { //Compressed
  442. buffer.put((byte) 1);
  443. } else {
  444. buffer.put((byte) 0);
  445. }
  446. buffer.putInt(0); //Unknown, but always 0.
  447. //Offset for fixed length columns
  448. if (col.isVariableLength()) {
  449. buffer.putShort((short) 0);
  450. } else {
  451. buffer.putShort(fixedOffset);
  452. fixedOffset += col.getType().getFixedSize();
  453. }
  454. if(!col.getType().isLongValue()) {
  455. buffer.putShort(col.getLength()); //Column length
  456. } else {
  457. buffer.putShort((short)0x0000); // unused
  458. }
  459. if (LOG.isDebugEnabled()) {
  460. LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
  461. buffer, position, _format.SIZE_COLUMN_DEF_BLOCK));
  462. }
  463. }
  464. iter = columns.iterator();
  465. while (iter.hasNext()) {
  466. Column col = (Column) iter.next();
  467. ByteBuffer colName = _format.CHARSET.encode(col.getName());
  468. buffer.putShort((short) colName.remaining());
  469. buffer.put(colName);
  470. }
  471. }
  472. /**
  473. * Create the usage map definition page buffer. It will be stored on the page
  474. * immediately after the tdef page.
  475. * @param pageNumber Page number that the corresponding table definition will
  476. * be written to
  477. */
  478. private ByteBuffer createUsageMapDefinitionBuffer(int pageNumber) throws IOException {
  479. ByteBuffer rtn = _pageChannel.createPageBuffer();
  480. rtn.put(PageTypes.DATA);
  481. rtn.put((byte) 0x1); //Unknown
  482. rtn.putShort(USAGE_MAP_DEF_FREE_SPACE); //Free space in page
  483. rtn.putInt(0); //Table definition
  484. rtn.putInt(0); //Unknown
  485. rtn.putShort((short) 2); //Number of records on this page
  486. rtn.putShort((short) _format.OFFSET_USED_PAGES_USAGE_MAP_DEF); //First location
  487. rtn.putShort((short) _format.OFFSET_FREE_PAGES_USAGE_MAP_DEF); //Second location
  488. rtn.position(_format.OFFSET_USED_PAGES_USAGE_MAP_DEF);
  489. rtn.put((byte) UsageMap.MAP_TYPE_REFERENCE);
  490. rtn.putInt(pageNumber + 2); //First referenced page number
  491. rtn.position(_format.OFFSET_FREE_PAGES_USAGE_MAP_DEF);
  492. rtn.put((byte) UsageMap.MAP_TYPE_INLINE);
  493. return rtn;
  494. }
  495. /**
  496. * Create a usage map data page buffer.
  497. */
  498. private ByteBuffer createUsageMapDataBuffer() throws IOException {
  499. ByteBuffer rtn = _pageChannel.createPageBuffer();
  500. rtn.put(PageTypes.USAGE_MAP);
  501. rtn.put((byte) 0x01); //Unknown
  502. rtn.putShort((short) 0); //Unknown
  503. return rtn;
  504. }
  505. /**
  506. * Add a new table to the system catalog
  507. * @param name Table name
  508. * @param pageNumber Page number that contains the table definition
  509. */
  510. private void addToSystemCatalog(String name, int pageNumber) throws IOException {
  511. Object[] catalogRow = new Object[_systemCatalog.getColumns().size()];
  512. int idx = 0;
  513. Iterator iter;
  514. for (iter = _systemCatalog.getColumns().iterator(); iter.hasNext(); idx++) {
  515. Column col = (Column) iter.next();
  516. if (COL_ID.equals(col.getName())) {
  517. catalogRow[idx] = new Integer(pageNumber);
  518. } else if (COL_NAME.equals(col.getName())) {
  519. catalogRow[idx] = name;
  520. } else if (COL_TYPE.equals(col.getName())) {
  521. catalogRow[idx] = TYPE_TABLE;
  522. } else if (COL_DATE_CREATE.equals(col.getName()) ||
  523. COL_DATE_UPDATE.equals(col.getName()))
  524. {
  525. catalogRow[idx] = new Date();
  526. } else if (COL_PARENT_ID.equals(col.getName())) {
  527. catalogRow[idx] = _tableParentId;
  528. } else if (COL_FLAGS.equals(col.getName())) {
  529. catalogRow[idx] = new Integer(0);
  530. } else if (COL_OWNER.equals(col.getName())) {
  531. byte[] owner = new byte[2];
  532. catalogRow[idx] = owner;
  533. owner[0] = (byte) 0xcf;
  534. owner[1] = (byte) 0x5f;
  535. }
  536. }
  537. _systemCatalog.addRow(catalogRow);
  538. }
  539. /**
  540. * Add a new table to the system's access control entries
  541. * @param pageNumber Page number that contains the table definition
  542. */
  543. private void addToAccessControlEntries(int pageNumber) throws IOException {
  544. Object[] aceRow = new Object[_accessControlEntries.getColumns().size()];
  545. int idx = 0;
  546. Iterator iter;
  547. for (iter = _accessControlEntries.getColumns().iterator(); iter.hasNext(); idx++) {
  548. Column col = (Column) iter.next();
  549. if (col.getName().equals(COL_ACM)) {
  550. aceRow[idx] = ACM;
  551. } else if (col.getName().equals(COL_F_INHERITABLE)) {
  552. aceRow[idx] = Boolean.FALSE;
  553. } else if (col.getName().equals(COL_OBJECT_ID)) {
  554. aceRow[idx] = new Integer(pageNumber);
  555. } else if (col.getName().equals(COL_SID)) {
  556. aceRow[idx] = SID;
  557. }
  558. }
  559. _accessControlEntries.addRow(aceRow);
  560. }
  561. /**
  562. * Copy an existing JDBC ResultSet into a new table in this database
  563. * @param name Name of the new table to create
  564. * @param source ResultSet to copy from
  565. */
  566. public void copyTable(String name, ResultSet source)
  567. throws SQLException, IOException
  568. {
  569. copyTable(name, source, SimpleImportFilter.INSTANCE);
  570. }
  571. /**
  572. * Copy an existing JDBC ResultSet into a new table in this database
  573. * @param name Name of the new table to create
  574. * @param source ResultSet to copy from
  575. * @param filter valid import filter
  576. */
  577. public void copyTable(String name, ResultSet source, ImportFilter filter)
  578. throws SQLException, IOException
  579. {
  580. ResultSetMetaData md = source.getMetaData();
  581. List<Column> columns = new LinkedList<Column>();
  582. for (int i = 1; i <= md.getColumnCount(); i++) {
  583. Column column = new Column();
  584. column.setName(escape(md.getColumnName(i)));
  585. int lengthInUnits = md.getColumnDisplaySize(i);
  586. column.setSQLType(md.getColumnType(i), lengthInUnits);
  587. DataType type = column.getType();
  588. if(type.isVariableLength()) {
  589. column.setLengthInUnits((short)lengthInUnits);
  590. }
  591. if(type.getHasScalePrecision()) {
  592. int scale = md.getScale(i);
  593. int precision = md.getPrecision(i);
  594. if(type.isValidScale(scale)) {
  595. column.setScale((byte)scale);
  596. }
  597. if(type.isValidPrecision(precision)) {
  598. column.setPrecision((byte)precision);
  599. }
  600. }
  601. columns.add(column);
  602. }
  603. createTable(escape(name), filter.filterColumns(columns, md));
  604. Table table = getTable(escape(name));
  605. List<Object[]> rows = new ArrayList<Object[]>(COPY_TABLE_BATCH_SIZE);
  606. while (source.next()) {
  607. Object[] row = new Object[md.getColumnCount()];
  608. for (int i = 0; i < row.length; i++) {
  609. row[i] = source.getObject(i + 1);
  610. }
  611. rows.add(filter.filterRow(row));
  612. if (rows.size() == COPY_TABLE_BATCH_SIZE) {
  613. table.addRows(rows);
  614. rows.clear();
  615. }
  616. }
  617. if (rows.size() > 0) {
  618. table.addRows(rows);
  619. }
  620. }
  621. /**
  622. * Copy a delimited text file into a new table in this database
  623. * @param name Name of the new table to create
  624. * @param f Source file to import
  625. * @param delim Regular expression representing the delimiter string.
  626. */
  627. public void importFile(String name, File f, String delim)
  628. throws IOException
  629. {
  630. importFile(name, f, delim, SimpleImportFilter.INSTANCE);
  631. }
  632. /**
  633. * Copy a delimited text file into a new table in this database
  634. * @param name Name of the new table to create
  635. * @param f Source file to import
  636. * @param delim Regular expression representing the delimiter string.
  637. * @param filter valid import filter
  638. */
  639. public void importFile(String name, File f, String delim,
  640. ImportFilter filter)
  641. throws IOException
  642. {
  643. BufferedReader in = null;
  644. try {
  645. in = new BufferedReader(new FileReader(f));
  646. importReader(name, in, delim, filter);
  647. } finally {
  648. if (in != null) {
  649. try {
  650. in.close();
  651. } catch (IOException ex) {
  652. LOG.warn("Could not close file " + f.getAbsolutePath(), ex);
  653. }
  654. }
  655. }
  656. }
  657. /**
  658. * Copy a delimited text file into a new table in this database
  659. * @param name Name of the new table to create
  660. * @param in Source reader to import
  661. * @param delim Regular expression representing the delimiter string.
  662. */
  663. public void importReader(String name, BufferedReader in, String delim)
  664. throws IOException
  665. {
  666. importReader(name, in, delim, SimpleImportFilter.INSTANCE);
  667. }
  668. /**
  669. * Copy a delimited text file into a new table in this database
  670. * @param name Name of the new table to create
  671. * @param in Source reader to import
  672. * @param delim Regular expression representing the delimiter string.
  673. * @param filter valid import filter
  674. */
  675. public void importReader(String name, BufferedReader in, String delim,
  676. ImportFilter filter)
  677. throws IOException
  678. {
  679. String line = in.readLine();
  680. if (line == null || line.trim().length() == 0) {
  681. return;
  682. }
  683. String tableName = escape(name);
  684. int counter = 0;
  685. while(getTable(tableName) != null) {
  686. tableName = escape(name + (counter++));
  687. }
  688. List<Column> columns = new LinkedList<Column>();
  689. String[] columnNames = line.split(delim);
  690. for (int i = 0; i < columnNames.length; i++) {
  691. Column column = new Column();
  692. column.setName(escape(columnNames[i]));
  693. column.setType(DataType.TEXT);
  694. column.setLength((short)DataType.TEXT.getMaxSize());
  695. columns.add(column);
  696. }
  697. try {
  698. createTable(tableName, filter.filterColumns(columns, null));
  699. Table table = getTable(tableName);
  700. List<Object[]> rows = new ArrayList<Object[]>(COPY_TABLE_BATCH_SIZE);
  701. while ((line = in.readLine()) != null)
  702. {
  703. //
  704. // Handle the situation where the end of the line
  705. // may have null fields. We always want to add the
  706. // same number of columns to the table each time.
  707. //
  708. String[] data = new String[columnNames.length];
  709. String[] splitData = line.split(delim);
  710. System.arraycopy(splitData, 0, data, 0, splitData.length);
  711. rows.add(filter.filterRow(data));
  712. if (rows.size() == COPY_TABLE_BATCH_SIZE) {
  713. table.addRows(rows);
  714. rows.clear();
  715. }
  716. }
  717. if (rows.size() > 0) {
  718. table.addRows(rows);
  719. }
  720. } catch(SQLException e) {
  721. throw (IOException)new IOException(e.getMessage()).initCause(e);
  722. }
  723. }
  724. /**
  725. * Close the database file
  726. */
  727. public void close() throws IOException {
  728. _pageChannel.close();
  729. }
  730. /**
  731. * @return A table or column name escaped for Access
  732. */
  733. private String escape(String s) {
  734. if (RESERVED_WORDS.contains(s.toLowerCase())) {
  735. return ESCAPE_PREFIX + s;
  736. } else {
  737. return s;
  738. }
  739. }
  740. public String toString() {
  741. return ToStringBuilder.reflectionToString(this);
  742. }
  743. /**
  744. * Adds a table to the _tableLookup and resets the _tableNames set
  745. */
  746. private void addTable(String tableName, Integer pageNumber)
  747. {
  748. _tableLookup.put(toLookupTableName(tableName),
  749. new TableInfo(pageNumber, tableName));
  750. // clear this, will be created next time needed
  751. _tableNames = null;
  752. }
  753. /**
  754. * @returns the tableInfo of the given table, if any
  755. */
  756. private TableInfo lookupTable(String tableName) {
  757. return _tableLookup.get(toLookupTableName(tableName));
  758. }
  759. /**
  760. * @return a string usable in the _tableLookup map.
  761. */
  762. private String toLookupTableName(String tableName) {
  763. return ((tableName != null) ? tableName.toUpperCase() : null);
  764. }
  765. /**
  766. * Utility class for storing table page number and actual name.
  767. */
  768. private static class TableInfo
  769. {
  770. public Integer pageNumber;
  771. public String tableName;
  772. private TableInfo(Integer newPageNumber,
  773. String newTableName) {
  774. pageNumber = newPageNumber;
  775. tableName = newTableName;
  776. }
  777. }
  778. /**
  779. * Table iterator for this database, unmodifiable.
  780. */
  781. private class TableIterator implements Iterator<Table>
  782. {
  783. private Iterator<String> _tableNameIter;
  784. private TableIterator() {
  785. _tableNameIter = getTableNames().iterator();
  786. }
  787. public boolean hasNext() {
  788. return _tableNameIter.hasNext();
  789. }
  790. public void remove() {
  791. throw new UnsupportedOperationException();
  792. }
  793. public Table next() {
  794. if(!hasNext()) {
  795. throw new NoSuchElementException();
  796. }
  797. try {
  798. return getTable(_tableNameIter.next());
  799. } catch(IOException e) {
  800. throw new IllegalStateException(e);
  801. }
  802. }
  803. }
  804. }