1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.lang.ref.ReferenceQueue;
24 import java.lang.ref.WeakReference;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.Channels;
27 import java.nio.channels.FileChannel;
28 import java.nio.channels.ReadableByteChannel;
29 import java.nio.charset.Charset;
30 import java.nio.file.Files;
31 import java.nio.file.OpenOption;
32 import java.nio.file.Path;
33 import java.nio.file.StandardOpenOption;
34 import java.text.SimpleDateFormat;
35 import java.time.LocalDateTime;
36 import java.time.ZoneId;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.Date;
42 import java.util.EnumMap;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Locale;
48 import java.util.Map;
49 import java.util.NoSuchElementException;
50 import java.util.Set;
51 import java.util.TimeZone;
52 import java.util.TreeSet;
53 import java.util.regex.Pattern;
54
55 import com.healthmarketscience.jackcess.ColumnBuilder;
56 import com.healthmarketscience.jackcess.Cursor;
57 import com.healthmarketscience.jackcess.CursorBuilder;
58 import com.healthmarketscience.jackcess.DataType;
59 import com.healthmarketscience.jackcess.Database;
60 import com.healthmarketscience.jackcess.DatabaseBuilder;
61 import com.healthmarketscience.jackcess.DateTimeType;
62 import com.healthmarketscience.jackcess.Index;
63 import com.healthmarketscience.jackcess.IndexBuilder;
64 import com.healthmarketscience.jackcess.IndexCursor;
65 import com.healthmarketscience.jackcess.PropertyMap;
66 import com.healthmarketscience.jackcess.Relationship;
67 import com.healthmarketscience.jackcess.Row;
68 import com.healthmarketscience.jackcess.RuntimeIOException;
69 import com.healthmarketscience.jackcess.Table;
70 import com.healthmarketscience.jackcess.TableBuilder;
71 import com.healthmarketscience.jackcess.TableDefinition;
72 import com.healthmarketscience.jackcess.TableMetaData;
73 import com.healthmarketscience.jackcess.expr.EvalConfig;
74 import com.healthmarketscience.jackcess.impl.query.QueryImpl;
75 import com.healthmarketscience.jackcess.query.Query;
76 import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
77 import com.healthmarketscience.jackcess.util.ColumnValidatorFactory;
78 import com.healthmarketscience.jackcess.util.ErrorHandler;
79 import com.healthmarketscience.jackcess.util.LinkResolver;
80 import com.healthmarketscience.jackcess.util.ReadOnlyFileChannel;
81 import com.healthmarketscience.jackcess.util.SimpleColumnValidatorFactory;
82 import com.healthmarketscience.jackcess.util.TableIterableBuilder;
83 import org.apache.commons.lang3.builder.ToStringBuilder;
84 import org.apache.commons.logging.Log;
85 import org.apache.commons.logging.LogFactory;
86
87
88
89
90
91
92
93 public class DatabaseImpl implements Database, DateTimeContext
94 {
95 private static final Log LOG = LogFactory.getLog(DatabaseImpl.class);
96
97
98
99 private static final byte[] SYS_DEFAULT_SID = new byte[] {
100 (byte) 0xA6, (byte) 0x33};
101
102
103
104
105 public static final String DEFAULT_RESOURCE_PATH =
106 "com/healthmarketscience/jackcess/";
107
108
109 static final String RESOURCE_PATH =
110 SystemConfig.getProperty(RESOURCE_PATH_PROPERTY, DEFAULT_RESOURCE_PATH);
111
112
113 static final boolean BROKEN_NIO = Boolean.TRUE.toString().equalsIgnoreCase(
114 SystemConfig.getProperty(BROKEN_NIO_PROPERTY));
115
116
117 private static final Map<Database.FileFormat,FileFormatDetails> FILE_FORMAT_DETAILS =
118 new EnumMap<Database.FileFormat,FileFormatDetails>(Database.FileFormat.class);
119
120 static {
121 addFileFormatDetails(FileFormat.V1997, null, JetFormat.VERSION_3);
122 addFileFormatDetails(FileFormat.GENERIC_JET4, null, JetFormat.VERSION_4);
123 addFileFormatDetails(FileFormat.V2000, "empty", JetFormat.VERSION_4);
124 addFileFormatDetails(FileFormat.V2003, "empty2003", JetFormat.VERSION_4);
125 addFileFormatDetails(FileFormat.V2007, "empty2007", JetFormat.VERSION_12);
126 addFileFormatDetails(FileFormat.V2010, "empty2010", JetFormat.VERSION_14);
127 addFileFormatDetails(FileFormat.V2016, "empty2016", JetFormat.VERSION_16);
128 addFileFormatDetails(FileFormat.V2019, "empty2019", JetFormat.VERSION_17);
129 addFileFormatDetails(FileFormat.MSISAM, null, JetFormat.VERSION_MSISAM);
130 }
131
132
133 private static final int PAGE_SYSTEM_CATALOG = 2;
134
135 private static final String TABLE_SYSTEM_CATALOG = "MSysObjects";
136
137
138
139
140 private static final Integer SYS_FULL_ACCESS_ACM = 1048575;
141
142
143 private static final String ACE_COL_ACM = "ACM";
144
145 private static final String ACE_COL_F_INHERITABLE = "FInheritable";
146
147 private static final String ACE_COL_OBJECT_ID = "ObjectId";
148
149 private static final String ACE_COL_SID = "SID";
150
151
152 private static final String REL_COL_COLUMN_COUNT = "ccolumn";
153
154 private static final String REL_COL_FLAGS = "grbit";
155
156 private static final String REL_COL_COLUMN_INDEX = "icolumn";
157
158 private static final String REL_COL_TO_COLUMN = "szColumn";
159
160 private static final String REL_COL_TO_TABLE = "szObject";
161
162 private static final String REL_COL_FROM_COLUMN = "szReferencedColumn";
163
164 private static final String REL_COL_FROM_TABLE = "szReferencedObject";
165
166 private static final String REL_COL_NAME = "szRelationship";
167
168
169
170 private static final String CAT_COL_ID = "Id";
171
172 private static final String CAT_COL_NAME = "Name";
173 private static final String CAT_COL_OWNER = "Owner";
174
175 private static final String CAT_COL_PARENT_ID = "ParentId";
176
177 private static final String CAT_COL_TYPE = "Type";
178
179 private static final String CAT_COL_DATE_CREATE = "DateCreate";
180
181 private static final String CAT_COL_DATE_UPDATE = "DateUpdate";
182
183 private static final String CAT_COL_FLAGS = "Flags";
184
185 static final String CAT_COL_PROPS = "LvProp";
186
187 private static final String CAT_COL_DATABASE = "Database";
188
189 private static final String CAT_COL_FOREIGN_NAME = "ForeignName";
190
191 private static final String CAT_COL_CONNECT_NAME = "Connect";
192
193
194 private static final int DB_PARENT_ID = 0xF000000;
195
196
197 private static final long MAX_EMPTYDB_SIZE = 440000L;
198
199
200 static final int SYSTEM_OBJECT_FLAG = 0x80000000;
201
202 static final int ALT_SYSTEM_OBJECT_FLAG = 0x02;
203
204 public static final int HIDDEN_OBJECT_FLAG = 0x08;
205
206 static final int SYSTEM_OBJECT_FLAGS =
207 SYSTEM_OBJECT_FLAG | ALT_SYSTEM_OBJECT_FLAG;
208
209
210 public static final OpenOption[] RO_CHANNEL_OPTS =
211 {StandardOpenOption.READ};
212
213 public static final OpenOption[] RW_CHANNEL_OPTS =
214 {StandardOpenOption.READ, StandardOpenOption.WRITE};
215
216 public static final OpenOption[] RWC_CHANNEL_OPTS =
217 {StandardOpenOption.READ, StandardOpenOption.WRITE,
218 StandardOpenOption.CREATE};
219
220
221 private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
222
223 private static final String SYSTEM_OBJECT_NAME_DATABASES = "Databases";
224
225 private static final String SYSTEM_OBJECT_NAME_RELATIONSHIPS = "Relationships";
226
227 private static final String TABLE_SYSTEM_ACES = "MSysACEs";
228
229 private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships";
230
231 private static final String TABLE_SYSTEM_QUERIES = "MSysQueries";
232
233 private static final String TABLE_SYSTEM_COMPLEX_COLS = "MSysComplexColumns";
234
235 private static final String OBJECT_NAME_DB_PROPS = "MSysDb";
236
237 private static final String OBJECT_NAME_SUMMARY_PROPS = "SummaryInfo";
238
239 private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined";
240
241 static final Short TYPE_TABLE = 1;
242
243 private static final Short TYPE_LINKED_ODBC_TABLE = 4;
244
245 private static final Short TYPE_QUERY = 5;
246
247 private static final Short TYPE_LINKED_TABLE = 6;
248
249 private static final Short TYPE_RELATIONSHIP = 8;
250
251
252 private static final int MAX_CACHED_LOOKUP_TABLES = 50;
253
254
255 private static Collection<String> SYSTEM_CATALOG_COLUMNS =
256 new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
257 CAT_COL_FLAGS, CAT_COL_PARENT_ID));
258
259 private static Collection<String> SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS =
260 new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
261 CAT_COL_FLAGS, CAT_COL_PARENT_ID,
262 CAT_COL_DATABASE, CAT_COL_FOREIGN_NAME,
263 CAT_COL_CONNECT_NAME));
264
265 private static Collection<String> SYSTEM_CATALOG_PROPS_COLUMNS =
266 new HashSet<String>(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS));
267
268 private static Collection<String> SYSTEM_CATALOG_DATE_COLUMNS =
269 new HashSet<String>(Arrays.asList(CAT_COL_ID,
270 CAT_COL_DATE_CREATE, CAT_COL_DATE_UPDATE));
271
272
273 private static final Pattern INVALID_IDENTIFIER_CHARS =
274 Pattern.compile("[\\p{Cntrl}.!`\\]\\[]");
275
276
277 private static final Pattern ODBC_PWD_PATTERN = Pattern.compile("\\bPWD=[^;]+");
278
279
280 private final Path _file;
281
282 private final String _name;
283
284 private final boolean _readOnly;
285
286 private ByteBuffer _buffer;
287
288 private Integer _tableParentId;
289
290 private final JetFormat _format;
291
292
293
294
295
296 private final Map<String, TableInfo> _tableLookup =
297 new SimpleCache<String,TableInfo>(MAX_CACHED_LOOKUP_TABLES);
298
299 private Set<String> _tableNames;
300
301 private final PageChannel _pageChannel;
302
303 private TableImpl _systemCatalog;
304
305 private TableFinder _tableFinder;
306
307 private TableImpl _accessControlEntries;
308
309 private Integer _relParentId;
310
311 private final List<byte[]> _newRelSIDs = new ArrayList<byte[]>();
312
313 private TableImpl _relationships;
314
315 private TableImpl _queries;
316
317 private TableImpl _complexCols;
318
319 private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>();
320
321 private ErrorHandler _dbErrorHandler;
322
323 private FileFormat _fileFormat;
324
325 private Charset _charset;
326
327 private TimeZone _timeZone;
328
329 private ZoneId _zoneId;
330
331 private ColumnImpl.SortOrder _defaultSortOrder;
332
333 private Short _defaultCodePage;
334
335 private Table.ColumnOrder _columnOrder;
336
337 private boolean _enforceForeignKeys;
338
339 private boolean _allowAutoNumInsert;
340
341 private boolean _evaluateExpressions;
342
343 private boolean _writeBrokenIndex;
344
345 private ColumnValidatorFactory _validatorFactory = SimpleColumnValidatorFactory.INSTANCE;
346
347 private final TableCache _tableCache = new TableCache();
348
349 private PropertyMaps.Handler _propsHandler;
350
351 private Integer _dbParentId;
352
353
354
355
356
357
358
359 private Integer _dynamicDbParentId;
360
361
362
363
364
365
366
367 private Integer _msysParentId;
368
369 private byte[] _newObjOwner;
370
371 private PropertyMaps _dbPropMaps;
372
373 private PropertyMaps _summaryPropMaps;
374
375 private PropertyMaps _userDefPropMaps;
376
377 private LinkResolver _linkResolver;
378
379 private Map<String,Database> _linkedDbs;
380
381 private final FKEnforcer.SharedState _fkEnforcerSharedState =
382 FKEnforcer.initSharedState();
383
384 private DBEvalContext _evalCtx;
385
386 private ColumnImpl.DateTimeFactory _dtf;
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411 public static DatabaseImpl open(
412 Path mdbFile, boolean readOnly, FileChannel channel,
413 boolean autoSync, Charset charset, TimeZone timeZone,
414 CodecProvider provider, boolean ignoreSystemCatalogIndex)
415 throws IOException
416 {
417 boolean closeChannel = false;
418 if(channel == null) {
419 if(!Files.isReadable(mdbFile)) {
420 throw new FileNotFoundException("given file does not exist: " +
421 mdbFile);
422 }
423
424
425 readOnly |= !Files.isWritable(mdbFile);
426
427
428 channel = openChannel(mdbFile, readOnly, false);
429 closeChannel = true;
430 }
431
432 boolean success = false;
433 try {
434
435 boolean wrapChannelRO = false;
436 if(!readOnly) {
437
438 JetFormat jetFormat = JetFormat.getFormat(channel);
439
440 if(jetFormat.READ_ONLY) {
441
442 wrapChannelRO = true;
443 readOnly = true;
444 }
445 } else if(!closeChannel) {
446
447
448
449 wrapChannelRO = true;
450 }
451
452 if(wrapChannelRO) {
453
454
455 channel = new ReadOnlyFileChannel(channel);
456 }
457
458 DatabaseImplss/impl/DatabaseImpl.html#DatabaseImpl">DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync,
459 null, charset, timeZone, provider,
460 readOnly, ignoreSystemCatalogIndex);
461 success = true;
462 return db;
463
464 } finally {
465 if(!success && closeChannel) {
466
467 ByteUtil.closeQuietly(channel);
468 }
469 }
470 }
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492 public static DatabaseImpl create(FileFormat fileFormat, Path mdbFile,
493 FileChannel channel, boolean autoSync,
494 Charset charset, TimeZone timeZone)
495 throws IOException
496 {
497 FileFormatDetails details = getFileFormatDetails(fileFormat);
498 if (details.getFormat().READ_ONLY) {
499 throw new IOException("File format " + fileFormat +
500 " does not support writing for " + mdbFile);
501 }
502 if(details.getEmptyFilePath() == null) {
503 throw new IOException("File format " + fileFormat +
504 " does not support file creation for " + mdbFile);
505 }
506
507 boolean closeChannel = false;
508 if(channel == null) {
509 channel = openChannel(mdbFile, false, true);
510 closeChannel = true;
511 }
512
513 boolean success = false;
514 try {
515 channel.truncate(0);
516 transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath()));
517 channel.force(true);
518 DatabaseImplss/impl/DatabaseImpl.html#DatabaseImpl">DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync,
519 fileFormat, charset, timeZone, null,
520 false, false);
521 success = true;
522 return db;
523 } finally {
524 if(!success && closeChannel) {
525
526 ByteUtil.closeQuietly(channel);
527 }
528 }
529 }
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544 static FileChannel openChannel(
545 Path mdbFile, boolean readOnly, boolean create)
546 throws IOException
547 {
548 OpenOption[] opts = (readOnly ? RO_CHANNEL_OPTS :
549 (create ? RWC_CHANNEL_OPTS : RW_CHANNEL_OPTS));
550 return FileChannel.open(mdbFile, opts);
551 }
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572 protected DatabaseImpl(Path file, FileChannel channel, boolean closeChannel,
573 boolean autoSync, FileFormat fileFormat, Charset charset,
574 TimeZone timeZone, CodecProvider provider,
575 boolean readOnly, boolean ignoreSystemCatalogIndex)
576 throws IOException
577 {
578 _file = file;
579 _name = getName(file);
580 _readOnly = readOnly;
581 _format = JetFormat.getFormat(channel);
582 _charset = ((charset == null) ? getDefaultCharset(_format) : charset);
583 _columnOrder = getDefaultColumnOrder();
584 _enforceForeignKeys = getDefaultEnforceForeignKeys();
585 _allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
586 _evaluateExpressions = getDefaultEvaluateExpressions();
587 _writeBrokenIndex = getDefaultWriteBrokenIndex();
588 _fileFormat = fileFormat;
589 setZoneInfo(timeZone, null);
590 _dtf = ColumnImpl.getDateTimeFactory(getDefaultDateTimeType());
591 _pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
592 if(provider == null) {
593 provider = DefaultCodecProvider.INSTANCE;
594 }
595
596
597
598 _pageChannel.initialize(this, provider);
599 _buffer = _pageChannel.createPageBuffer();
600 readSystemCatalog(ignoreSystemCatalogIndex);
601 }
602
603 @Override
604 public File getFile() {
605 return ((_file != null) ? _file.toFile() : null);
606 }
607
608 @Override
609 public Path getPath() {
610 return _file;
611 }
612
613 public String getName() {
614 return _name;
615 }
616
617 public boolean isReadOnly() {
618 return _readOnly;
619 }
620
621
622
623
624 public PageChannel getPageChannel() {
625 return _pageChannel;
626 }
627
628
629
630
631 public JetFormat getFormat() {
632 return _format;
633 }
634
635
636
637
638
639 public TableImpl getSystemCatalog() {
640 return _systemCatalog;
641 }
642
643
644
645
646
647 public TableImpl getAccessControlEntries() throws IOException {
648 if(_accessControlEntries == null) {
649 _accessControlEntries = getRequiredSystemTable(TABLE_SYSTEM_ACES);
650 }
651 return _accessControlEntries;
652 }
653
654
655
656
657
658 public TableImpl getSystemComplexColumns() throws IOException {
659 if(_complexCols == null) {
660 _complexCols = getRequiredSystemTable(TABLE_SYSTEM_COMPLEX_COLS);
661 }
662 return _complexCols;
663 }
664
665 @Override
666 public ErrorHandler getErrorHandler() {
667 return((_dbErrorHandler != null) ? _dbErrorHandler : ErrorHandler.DEFAULT);
668 }
669
670 @Override
671 public void setErrorHandler(ErrorHandler newErrorHandler) {
672 _dbErrorHandler = newErrorHandler;
673 }
674
675 @Override
676 public LinkResolver getLinkResolver() {
677 return((_linkResolver != null) ? _linkResolver : LinkResolver.DEFAULT);
678 }
679
680 @Override
681 public void setLinkResolver(LinkResolver newLinkResolver) {
682 _linkResolver = newLinkResolver;
683 }
684
685 @Override
686 public Map<String,Database> getLinkedDatabases() {
687 return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() :
688 Collections.unmodifiableMap(_linkedDbs));
689 }
690
691 @Override
692 public boolean isLinkedTable(Table table) throws IOException {
693
694 if((table == null) || (this == table.getDatabase())) {
695
696 return false;
697 }
698
699
700 TableInfo tableInfo = lookupTable(table.getName());
701 if((tableInfo != null) &&
702 (tableInfo.getType() == TableMetaData.Type.LINKED) &&
703 matchesLinkedTable(table, tableInfo.getLinkedTableName(),
704 tableInfo.getLinkedDbName())) {
705 return true;
706 }
707
708
709
710 return _tableFinder.isLinkedTable(table);
711 }
712
713 private boolean matchesLinkedTable(Table table, String linkedTableName,
714 String linkedDbName) {
715 return (table.getName().equalsIgnoreCase(linkedTableName) &&
716 (_linkedDbs != null) &&
717 (_linkedDbs.get(linkedDbName) == table.getDatabase()));
718 }
719
720 @Override
721 public TimeZone getTimeZone() {
722 return _timeZone;
723 }
724
725 @Override
726 public void setTimeZone(TimeZone newTimeZone) {
727 setZoneInfo(newTimeZone, null);
728 }
729
730 @Override
731 public ZoneId getZoneId() {
732 return _zoneId;
733 }
734
735 @Override
736 public void setZoneId(ZoneId newZoneId) {
737 setZoneInfo(null, newZoneId);
738 }
739
740 private void setZoneInfo(TimeZone newTimeZone, ZoneId newZoneId) {
741 if(newTimeZone != null) {
742 newZoneId = newTimeZone.toZoneId();
743 } else if(newZoneId != null) {
744 newTimeZone = TimeZone.getTimeZone(newZoneId);
745 } else {
746 newTimeZone = getDefaultTimeZone();
747 newZoneId = newTimeZone.toZoneId();
748 }
749
750 _timeZone = newTimeZone;
751 _zoneId = newZoneId;
752 }
753
754 @Override
755 public DateTimeType getDateTimeType() {
756 return _dtf.getType();
757 }
758
759 @Override
760 public void setDateTimeType(DateTimeType dateTimeType) {
761 _dtf = ColumnImpl.getDateTimeFactory(dateTimeType);
762 }
763
764 @Override
765 public ColumnImpl.DateTimeFactory getDateTimeFactory() {
766 return _dtf;
767 }
768
769 @Override
770 public Charset getCharset()
771 {
772 return _charset;
773 }
774
775 @Override
776 public void setCharset(Charset newCharset) {
777 if(newCharset == null) {
778 newCharset = getDefaultCharset(getFormat());
779 }
780 _charset = newCharset;
781 }
782
783 @Override
784 public Table.ColumnOrder getColumnOrder() {
785 return _columnOrder;
786 }
787
788 @Override
789 public void setColumnOrder(Table.ColumnOrder newColumnOrder) {
790 if(newColumnOrder == null) {
791 newColumnOrder = getDefaultColumnOrder();
792 }
793 _columnOrder = newColumnOrder;
794 }
795
796 @Override
797 public boolean isEnforceForeignKeys() {
798 return _enforceForeignKeys;
799 }
800
801 @Override
802 public void setEnforceForeignKeys(Boolean newEnforceForeignKeys) {
803 if(newEnforceForeignKeys == null) {
804 newEnforceForeignKeys = getDefaultEnforceForeignKeys();
805 }
806 _enforceForeignKeys = newEnforceForeignKeys;
807 }
808
809 @Override
810 public boolean isAllowAutoNumberInsert() {
811 return _allowAutoNumInsert;
812 }
813
814 @Override
815 public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
816 if(allowAutoNumInsert == null) {
817 allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
818 }
819 _allowAutoNumInsert = allowAutoNumInsert;
820 }
821
822 @Override
823 public boolean isEvaluateExpressions() {
824 return _evaluateExpressions;
825 }
826
827 @Override
828 public void setEvaluateExpressions(Boolean evaluateExpressions) {
829 if(evaluateExpressions == null) {
830 evaluateExpressions = getDefaultEvaluateExpressions();
831 }
832 _evaluateExpressions = evaluateExpressions;
833 }
834
835 @Override
836 public boolean isWriteBrokenIndex() {
837 return _writeBrokenIndex;
838 }
839
840 @Override
841 public void setWriteBrokenIndex(Boolean writeBrokenIndex) {
842 if(writeBrokenIndex == null) {
843 writeBrokenIndex = getDefaultWriteBrokenIndex();
844 }
845 _writeBrokenIndex = writeBrokenIndex;
846 }
847
848 @Override
849 public ColumnValidatorFactory getColumnValidatorFactory() {
850 return _validatorFactory;
851 }
852
853 @Override
854 public void setColumnValidatorFactory(ColumnValidatorFactory newFactory) {
855 if(newFactory == null) {
856 newFactory = SimpleColumnValidatorFactory.INSTANCE;
857 }
858 _validatorFactory = newFactory;
859 }
860
861
862
863
864 FKEnforcer.SharedState getFKEnforcerSharedState() {
865 return _fkEnforcerSharedState;
866 }
867
868 @Override
869 public EvalConfig getEvalConfig() {
870 return getEvalContext();
871 }
872
873
874
875
876 DBEvalContext getEvalContext() {
877 if(_evalCtx == null) {
878 _evalCtx = new DBEvalContext(this);
879 }
880 return _evalCtx;
881 }
882
883
884
885
886
887
888
889 public SimpleDateFormat createDateFormat(String formatStr) {
890 SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr);
891 sdf.setTimeZone(getTimeZone());
892 return sdf;
893 }
894
895
896
897
898
899 private PropertyMaps.Handler getPropsHandler() {
900 if(_propsHandler == null) {
901 _propsHandler = new PropertyMaps.Handler(this);
902 }
903 return _propsHandler;
904 }
905
906 @Override
907 public FileFormat getFileFormat() throws IOException {
908
909 if(_fileFormat == null) {
910
911 Map<String,FileFormat> possibleFileFormats =
912 getFormat().getPossibleFileFormats();
913
914 if(possibleFileFormats.size() == 1) {
915
916
917 _fileFormat = possibleFileFormats.get(null);
918
919 } else {
920
921
922 String accessVersion = (String)getDatabaseProperties().getValue(
923 PropertyMap.ACCESS_VERSION_PROP);
924
925 if(StringUtil.isBlank(accessVersion)) {
926
927 accessVersion = null;
928 }
929
930 _fileFormat = possibleFileFormats.get(accessVersion);
931
932 if(_fileFormat == null) {
933 throw new IllegalStateException(withErrorContext(
934 "Could not determine FileFormat"));
935 }
936 }
937 }
938 return _fileFormat;
939 }
940
941
942
943
944
945
946 private ByteBuffer takeSharedBuffer() {
947
948
949
950
951
952 if(_buffer != null) {
953 ByteBuffer curBuffer = _buffer;
954 _buffer = null;
955 return curBuffer;
956 }
957 return _pageChannel.createPageBuffer();
958 }
959
960
961
962
963
964 private void releaseSharedBuffer(ByteBuffer buffer) {
965
966
967
968 _buffer = buffer;
969 }
970
971
972
973
974
975
976 public ColumnImpl.SortOrder getDefaultSortOrder() throws IOException {
977
978 if(_defaultSortOrder == null) {
979 initRootPageInfo();
980 }
981 return _defaultSortOrder;
982 }
983
984
985
986
987
988
989 public short getDefaultCodePage() throws IOException {
990
991 if(_defaultCodePage == null) {
992 initRootPageInfo();
993 }
994 return _defaultCodePage;
995 }
996
997
998
999
1000 private void initRootPageInfo() throws IOException {
1001 ByteBuffer buffer = takeSharedBuffer();
1002 try {
1003 _pageChannel.readRootPage(buffer);
1004 _defaultSortOrder = ColumnImpl.readSortOrder(
1005 buffer, _format.OFFSET_SORT_ORDER, _format);
1006 _defaultCodePage = buffer.getShort(_format.OFFSET_CODE_PAGE);
1007 } finally {
1008 releaseSharedBuffer(buffer);
1009 }
1010 }
1011
1012
1013
1014
1015
1016
1017 public PropertyMaps readProperties(byte[] propsBytes, int objectId,
1018 RowIdImpl rowId)
1019 throws IOException
1020 {
1021 return getPropsHandler().read(propsBytes, objectId, rowId, null);
1022 }
1023
1024
1025
1026
1027 private void readSystemCatalog(boolean ignoreSystemCatalogIndex)
1028 throws IOException {
1029 _systemCatalog = loadTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG,
1030 SYSTEM_OBJECT_FLAGS, TYPE_TABLE);
1031
1032 boolean forceScan = ignoreSystemCatalogIndex;
1033 if(!forceScan) {
1034
1035
1036
1037
1038
1039 IndexImpl catIdx = _systemCatalog.findIndexForColumns(
1040 Arrays.asList(CAT_COL_PARENT_ID, CAT_COL_NAME),
1041 TableImpl.IndexFeature.EXACT_MATCH);
1042 if((catIdx != null) && !catIdx.getIndexData().isValid()) {
1043 forceScan = true;
1044 if(LOG.isDebugEnabled()) {
1045 LOG.debug(withErrorContext(
1046 "System catalog index unsupported (" +
1047 catIdx.getIndexData().getUnsupportedReason() +
1048 "), forcing table scan"));
1049 }
1050 }
1051 }
1052
1053 if(!forceScan) {
1054 try {
1055 _tableFinder = new DefaultTableFinder(
1056 _systemCatalog.newCursor()
1057 .setIndexByColumnNames(CAT_COL_PARENT_ID, CAT_COL_NAME)
1058 .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
1059 .toIndexCursor());
1060 } catch(IllegalArgumentException e) {
1061 if(LOG.isDebugEnabled()) {
1062 LOG.debug(withErrorContext(
1063 "Could not find expected index on table " +
1064 _systemCatalog.getName()));
1065 }
1066 forceScan = true;
1067 }
1068 }
1069
1070 if(forceScan) {
1071 _tableFinder = new FallbackTableFinder(
1072 _systemCatalog.newCursor()
1073 .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
1074 .toCursor());
1075 }
1076
1077
1078
1079
1080
1081 if(forceScan) {
1082 for(Row row : CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
1083 SYSTEM_CATALOG_COLUMNS)) {
1084 String name = row.getString(CAT_COL_NAME);
1085 if(SYSTEM_OBJECT_NAME_TABLES.equalsIgnoreCase(name) && _tableParentId == null) {
1086 _tableParentId = row.getInt(CAT_COL_ID);
1087 if(LOG.isDebugEnabled()) {
1088 LOG.debug(withErrorContext("Resolved _tableParentId=" + _tableParentId +
1089 " from '" + SYSTEM_OBJECT_NAME_TABLES + "' row"));
1090 }
1091 } else if(SYSTEM_OBJECT_NAME_DATABASES.equalsIgnoreCase(name) &&
1092 _dynamicDbParentId == null) {
1093 _dynamicDbParentId = row.getInt(CAT_COL_ID);
1094 if(LOG.isDebugEnabled()) {
1095 LOG.debug(withErrorContext("Resolved _dynamicDbParentId=" + _dynamicDbParentId +
1096 " from '" + SYSTEM_OBJECT_NAME_DATABASES + "' row"));
1097 }
1098 } else if(TABLE_SYSTEM_CATALOG.equalsIgnoreCase(name) && _msysParentId == null) {
1099 _msysParentId = row.getInt(CAT_COL_PARENT_ID);
1100 if(LOG.isDebugEnabled()) {
1101 LOG.debug(withErrorContext("Resolved _msysParentId=" + _msysParentId +
1102 " from '" + TABLE_SYSTEM_CATALOG + "' row"));
1103 }
1104 }
1105 if(_tableParentId != null && _dynamicDbParentId != null && _msysParentId != null) {
1106 break;
1107 }
1108 }
1109 }
1110
1111 if(_tableParentId == null) {
1112 _tableParentId = _tableFinder.findObjectId(DB_PARENT_ID,
1113 SYSTEM_OBJECT_NAME_TABLES);
1114 }
1115
1116 if(_tableParentId == null) {
1117 if(LOG.isWarnEnabled()) {
1118 for(Row row : CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
1119 SYSTEM_CATALOG_COLUMNS)) {
1120 LOG.warn(withErrorContext(
1121 "MSysObjects row during failure scan: name=" + row.getString(CAT_COL_NAME) +
1122 ", parentId=" + row.getInt(CAT_COL_PARENT_ID)));
1123 }
1124 }
1125 throw new IOException(withErrorContext(
1126 "Did not find required parent table id"));
1127 }
1128
1129 if (LOG.isDebugEnabled()) {
1130 LOG.debug(withErrorContext(
1131 "Finished reading system catalog. Tables: " + getTableNames()));
1132 }
1133 }
1134
1135 @Override
1136 public Set<String> getTableNames() throws IOException {
1137 if(_tableNames == null) {
1138 _tableNames = getTableNames(true, false, true);
1139 }
1140 return _tableNames;
1141 }
1142
1143 @Override
1144 public Set<String> getSystemTableNames() throws IOException {
1145 return getTableNames(false, true, false);
1146 }
1147
1148 private Set<String> getTableNames(boolean normalTables, boolean systemTables,
1149 boolean linkedTables)
1150 throws IOException
1151 {
1152 Set<String> tableNames = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
1153 _tableFinder.getTableNames(tableNames, normalTables, systemTables,
1154 linkedTables);
1155 return tableNames;
1156 }
1157
1158 @Override
1159 public Iterator<Table> iterator() {
1160 try {
1161 return new TableIterator(getTableNames());
1162 } catch(IOException e) {
1163 throw new RuntimeIOException(e);
1164 }
1165 }
1166
1167 public Iterator<Table> iterator(TableIterableBuilder builder) {
1168 try {
1169 return new TableIterator(getTableNames(builder.isIncludeNormalTables(),
1170 builder.isIncludeSystemTables(),
1171 builder.isIncludeLinkedTables()));
1172 } catch(IOException e) {
1173 throw new RuntimeIOException(e);
1174 }
1175 }
1176
1177 @Override
1178 public TableIterableBuilder newIterable() {
1179 return new TableIterableBuilder(this);
1180 }
1181
1182 @Override
1183 public Iterable<TableMetaData> newTableMetaDataIterable() {
1184 return new Iterable<TableMetaData>() {
1185 @Override
1186 public Iterator<TableMetaData> iterator() {
1187 try {
1188 return _tableFinder.iterateTableMetaData();
1189 } catch(IOException e) {
1190 throw new RuntimeIOException(e);
1191 }
1192 }
1193 };
1194 }
1195
1196 @Override
1197 public TableImpl getTable(String name) throws IOException {
1198 return getTable(name, false);
1199 }
1200
1201 @Override
1202 public TableMetaData getTableMetaData(String name) throws IOException {
1203 return getTableInfo(name, true);
1204 }
1205
1206
1207
1208
1209
1210
1211 public TableImpl getTable(int tableDefPageNumber) throws IOException {
1212 return loadTable(null, tableDefPageNumber, 0, null);
1213 }
1214
1215
1216
1217
1218
1219
1220 protected TableImpl getTable(String name, boolean includeSystemTables)
1221 throws IOException
1222 {
1223 TableInfo tableInfo = getTableInfo(name, includeSystemTables);
1224 return ((tableInfo != null) ?
1225 getTable(tableInfo, includeSystemTables) : null);
1226 }
1227
1228 private TableInfo getTableInfo(String name, boolean includeSystemTables)
1229 throws IOException
1230 {
1231 TableInfo tableInfo = lookupTable(name);
1232
1233 if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
1234 return null;
1235 }
1236 if(!includeSystemTables && tableInfo.isSystem()) {
1237 return null;
1238 }
1239
1240 return tableInfo;
1241 }
1242
1243 private TableImpl getTable(TableInfo tableInfo, boolean includeSystemTables)
1244 throws IOException
1245 {
1246 if(tableInfo.getType() == TableMetaData.Type.LINKED) {
1247
1248 if(_linkedDbs == null) {
1249 _linkedDbs = new HashMap<String,Database>();
1250 }
1251
1252 String linkedDbName = tableInfo.getLinkedDbName();
1253 String linkedTableName = tableInfo.getLinkedTableName();
1254 Database linkedDb = _linkedDbs.get(linkedDbName);
1255 if(linkedDb == null) {
1256 linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName);
1257 _linkedDbs.put(linkedDbName, linkedDb);
1258 }
1259
1260 return ((DatabaseImpl)linkedDb).getTable(linkedTableName,
1261 includeSystemTables);
1262 }
1263
1264 return loadTable(tableInfo.tableName, tableInfo.pageNumber,
1265 tableInfo.flags, tableInfo.tableType);
1266 }
1267
1268
1269
1270
1271
1272
1273
1274 @Deprecated
1275 public void createTable(String name, List<ColumnBuilder> columns)
1276 throws IOException
1277 {
1278 createTable(name, columns, null);
1279 }
1280
1281
1282
1283
1284
1285
1286
1287
1288 @Deprecated
1289 public void createTable(String name, List<ColumnBuilder> columns,
1290 List<IndexBuilder> indexes)
1291 throws IOException
1292 {
1293 new TableBuilder(name)
1294 .addColumns(columns)
1295 .addIndexes(indexes)
1296 .toTable(this);
1297 }
1298
1299 @Override
1300 public void createLinkedTable(String name, String linkedDbName,
1301 String linkedTableName)
1302 throws IOException
1303 {
1304 if(lookupTable(name) != null) {
1305 throw new IllegalArgumentException(withErrorContext(
1306 "Cannot create linked table with name of existing table '" + name +
1307 "'"));
1308 }
1309
1310 validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
1311 validateName(linkedDbName, DataType.MEMO.getMaxSize(),
1312 "linked database");
1313 validateIdentifierName(linkedTableName, getFormat().MAX_TABLE_NAME_LENGTH,
1314 "linked table");
1315
1316 getPageChannel().startWrite();
1317 try {
1318
1319 int linkedTableId = _tableFinder.getNextFreeSyntheticId();
1320
1321 addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName,
1322 linkedTableName);
1323
1324 } finally {
1325 getPageChannel().finishWrite();
1326 }
1327 }
1328
1329
1330
1331
1332 void addNewTable(String name, int tdefPageNumber, Short type,
1333 String linkedDbName, String linkedTableName)
1334 throws IOException
1335 {
1336
1337 addTable(name, Integer.valueOf(tdefPageNumber), type, linkedDbName,
1338 linkedTableName);
1339
1340
1341 addToSystemCatalog(name, tdefPageNumber, type, linkedDbName,
1342 linkedTableName, _tableParentId);
1343 addToAccessControlEntries(tdefPageNumber, _tableParentId, _newTableSIDs);
1344 }
1345
1346 @Override
1347 public List<Relationship> getRelationships(Tablef="../../../../com/healthmarketscience/jackcess/Table.html#Table">Table table1, Table table2)
1348 throws IOException
1349 {
1350 return getRelationships((TableImpl/../../../com/healthmarketscience/jackcess/impl/TableImpl.html#TableImpl">TableImpl)table1, (TableImpl)table2);
1351 }
1352
1353 public List<Relationship> getRelationships(
1354 TableImpl./../../../com/healthmarketscience/jackcess/impl/TableImpl.html#TableImpl">TableImpl table1, TableImpl table2)
1355 throws IOException
1356 {
1357 int nameCmp = table1.getName().compareTo(table2.getName());
1358 if(nameCmp == 0) {
1359 throw new IllegalArgumentException(withErrorContext(
1360 "Must provide two different tables"));
1361 }
1362 if(nameCmp > 0) {
1363
1364
1365
1366 TableImpl tmp = table1;
1367 table1 = table2;
1368 table2 = tmp;
1369 }
1370
1371 return getRelationshipsImpl(table1, table2, true);
1372 }
1373
1374 @Override
1375 public List<Relationship> getRelationships(Table table)
1376 throws IOException
1377 {
1378 if(table == null) {
1379 throw new IllegalArgumentException(withErrorContext("Must provide a table"));
1380 }
1381
1382
1383 return getRelationshipsImpl((TableImpl)table, null, true);
1384 }
1385
1386 @Override
1387 public List<Relationship> getRelationships()
1388 throws IOException
1389 {
1390 return getRelationshipsImpl(null, null, false);
1391 }
1392
1393 @Override
1394 public List<Relationship> getSystemRelationships()
1395 throws IOException
1396 {
1397 return getRelationshipsImpl(null, null, true);
1398 }
1399
1400 private List<Relationship> getRelationshipsImpl(
1401 TableImpl./../../../com/healthmarketscience/jackcess/impl/TableImpl.html#TableImpl">TableImpl table1, TableImpl table2, boolean includeSystemTables)
1402 throws IOException
1403 {
1404 initRelationships();
1405
1406 List<Relationship> relationships = new ArrayList<Relationship>();
1407
1408 if(table1 != null) {
1409 Cursor cursor = createCursorWithOptionalIndex(
1410 _relationships, REL_COL_FROM_TABLE, table1.getName());
1411 collectRelationships(cursor, table1, table2, relationships,
1412 includeSystemTables);
1413 cursor = createCursorWithOptionalIndex(
1414 _relationships, REL_COL_TO_TABLE, table1.getName());
1415 collectRelationships(cursor, table2, table1, relationships,
1416 includeSystemTables);
1417 } else {
1418 collectRelationships(new CursorBuilder(_relationships).toCursor(),
1419 null, null, relationships, includeSystemTables);
1420 }
1421
1422 return relationships;
1423 }
1424
1425 RelationshipImpl writeRelationship(RelationshipCreator creator)
1426 throws IOException
1427 {
1428 initRelationships();
1429
1430 String name = createRelationshipName(creator);
1431 RelationshipImpl newRel = creator.createRelationshipImpl(name);
1432
1433 ColumnImpl ccol = _relationships.getColumn(REL_COL_COLUMN_COUNT);
1434 ColumnImpl flagCol = _relationships.getColumn(REL_COL_FLAGS);
1435 ColumnImpl icol = _relationships.getColumn(REL_COL_COLUMN_INDEX);
1436 ColumnImpl nameCol = _relationships.getColumn(REL_COL_NAME);
1437 ColumnImpl fromTableCol = _relationships.getColumn(REL_COL_FROM_TABLE);
1438 ColumnImpl fromColCol = _relationships.getColumn(REL_COL_FROM_COLUMN);
1439 ColumnImpl toTableCol = _relationships.getColumn(REL_COL_TO_TABLE);
1440 ColumnImpl toColCol = _relationships.getColumn(REL_COL_TO_COLUMN);
1441
1442 int numCols = newRel.getFromColumns().size();
1443 List<Object[]> rows = new ArrayList<Object[]>(numCols);
1444 for(int i = 0; i < numCols; ++i) {
1445 Object[] row = new Object[_relationships.getColumnCount()];
1446 ccol.setRowValue(row, numCols);
1447 flagCol.setRowValue(row, newRel.getFlags());
1448 icol.setRowValue(row, i);
1449 nameCol.setRowValue(row, name);
1450 fromTableCol.setRowValue(row, newRel.getFromTable().getName());
1451 fromColCol.setRowValue(row, newRel.getFromColumns().get(i).getName());
1452 toTableCol.setRowValue(row, newRel.getToTable().getName());
1453 toColCol.setRowValue(row, newRel.getToColumns().get(i).getName());
1454 rows.add(row);
1455 }
1456
1457 getPageChannel().startWrite();
1458 try {
1459
1460 int relObjId = _tableFinder.getNextFreeSyntheticId();
1461 _relationships.addRows(rows);
1462 addToSystemCatalog(name, relObjId, TYPE_RELATIONSHIP, null, null,
1463 _relParentId);
1464 addToAccessControlEntries(relObjId, _relParentId, _newRelSIDs);
1465
1466 } finally {
1467 getPageChannel().finishWrite();
1468 }
1469
1470 return newRel;
1471 }
1472
1473 private void initRelationships() throws IOException {
1474
1475 if(_relationships == null) {
1476
1477 _relParentId = _tableFinder.findObjectId(DB_PARENT_ID,
1478 SYSTEM_OBJECT_NAME_RELATIONSHIPS);
1479 _relationships = getRequiredSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
1480 }
1481 }
1482
1483 private String createRelationshipName(RelationshipCreator creator) {
1484
1485
1486
1487 int maxIdLen = getFormat().MAX_INDEX_NAME_LENGTH;
1488 int limit = (maxIdLen / 2) - 3;
1489 String origName = creator.getName();
1490 if (origName == null) {
1491 origName = creator.getPrimaryTable().getName();
1492 if(origName.length() > limit) {
1493 origName = origName.substring(0, limit);
1494 }
1495 origName += creator.getSecondaryTable().getName();
1496 }
1497 limit = maxIdLen - 3;
1498 if(origName.length() > limit) {
1499 origName = origName.substring(0, limit);
1500 }
1501
1502
1503 Set<String> names = new HashSet<String>();
1504
1505
1506 for(Row row :
1507 CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
1508 SYSTEM_CATALOG_COLUMNS))
1509 {
1510 String name = row.getString(CAT_COL_NAME);
1511 if (name != null && TYPE_RELATIONSHIP.equals(row.get(CAT_COL_TYPE))) {
1512 names.add(toLookupName(name));
1513 }
1514 }
1515
1516 if(creator.hasReferentialIntegrity()) {
1517
1518
1519 for(Index idx : creator.getSecondaryTable().getIndexes()) {
1520 names.add(toLookupName(idx.getName()));
1521 }
1522 }
1523
1524 String baseName = toLookupName(origName);
1525 String name = baseName;
1526 int i = 0;
1527 while(names.contains(name)) {
1528 name = baseName + (++i);
1529 }
1530
1531 return ((i == 0) ? origName : (origName + i));
1532 }
1533
1534 @Override
1535 public List<Query> getQueries() throws IOException
1536 {
1537
1538 if(_queries == null) {
1539 _queries = getRequiredSystemTable(TABLE_SYSTEM_QUERIES);
1540 }
1541
1542
1543 List<Row> queryInfo = new ArrayList<Row>();
1544 Map<Integer,List<QueryImpl.Row>> queryRowMap =
1545 new HashMap<Integer,List<QueryImpl.Row>>();
1546 for(Row row :
1547 CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
1548 SYSTEM_CATALOG_COLUMNS))
1549 {
1550 String name = row.getString(CAT_COL_NAME);
1551 if (name != null && TYPE_QUERY.equals(row.get(CAT_COL_TYPE))) {
1552 queryInfo.add(row);
1553 Integer id = row.getInt(CAT_COL_ID);
1554 queryRowMap.put(id, new ArrayList<QueryImpl.Row>());
1555 }
1556 }
1557
1558
1559 for(Row row : CursorImpl.createCursor(_queries)) {
1560 QueryImpl.Row queryRow = new QueryImpl.Row(row);
1561 List<QueryImpl.Row> queryRows = queryRowMap.get(queryRow.objectId);
1562 if(queryRows == null) {
1563 LOG.warn(withErrorContext(
1564 "Found rows for query with id " + queryRow.objectId +
1565 " missing from system catalog"));
1566 continue;
1567 }
1568 queryRows.add(queryRow);
1569 }
1570
1571
1572 List<Query> queries = new ArrayList<Query>();
1573 for(Row row : queryInfo) {
1574 String name = row.getString(CAT_COL_NAME);
1575 Integer id = row.getInt(CAT_COL_ID);
1576 int flags = row.getInt(CAT_COL_FLAGS);
1577 List<QueryImpl.Row> queryRows = queryRowMap.get(id);
1578 queries.add(QueryImpl.create(flags, name, queryRows, id));
1579 }
1580
1581 return queries;
1582 }
1583
1584 @Override
1585 public TableImpl getSystemTable(String tableName) throws IOException
1586 {
1587
1588
1589
1590 if(_msysParentId != null) {
1591 TableInfo tableInfo = _tableFinder.lookupTable(tableName, _msysParentId);
1592 if(tableInfo != null) {
1593 return getTable(tableInfo, true);
1594 }
1595 }
1596 return getTable(tableName, true);
1597 }
1598
1599 private TableImpl getRequiredSystemTable(String tableName) throws IOException
1600 {
1601 TableImpl table = getSystemTable(tableName);
1602 if(table == null) {
1603 throw new IOException(withErrorContext(
1604 "Could not find system table " + tableName));
1605 }
1606 return table;
1607 }
1608
1609 @Override
1610 public PropertyMap getDatabaseProperties() throws IOException {
1611 if(_dbPropMaps == null) {
1612 _dbPropMaps = getPropertiesForDbObject(OBJECT_NAME_DB_PROPS);
1613 }
1614 return _dbPropMaps.getDefault();
1615 }
1616
1617 @Override
1618 public PropertyMap getSummaryProperties() throws IOException {
1619 if(_summaryPropMaps == null) {
1620 _summaryPropMaps = getPropertiesForDbObject(OBJECT_NAME_SUMMARY_PROPS);
1621 }
1622 return _summaryPropMaps.getDefault();
1623 }
1624
1625 @Override
1626 public PropertyMap getUserDefinedProperties() throws IOException {
1627 if(_userDefPropMaps == null) {
1628 _userDefPropMaps = getPropertiesForDbObject(OBJECT_NAME_USERDEF_PROPS);
1629 }
1630 return _userDefPropMaps.getDefault();
1631 }
1632
1633
1634
1635
1636
1637 public PropertyMaps getPropertiesForObject(
1638 int objectId, PropertyMaps.Owner owner)
1639 throws IOException
1640 {
1641 return readProperties(
1642 objectId, _tableFinder.getObjectRow(
1643 objectId, SYSTEM_CATALOG_PROPS_COLUMNS), owner);
1644 }
1645
1646 LocalDateTime getCreateDateForObject(int objectId) throws IOException {
1647 return getDateForObject(objectId, CAT_COL_DATE_CREATE);
1648 }
1649
1650 LocalDateTime getUpdateDateForObject(int objectId) throws IOException {
1651 return getDateForObject(objectId, CAT_COL_DATE_UPDATE);
1652 }
1653
1654 private LocalDateTime getDateForObject(int objectId, String dateCol)
1655 throws IOException {
1656 Row row = _tableFinder.getObjectRow(objectId, SYSTEM_CATALOG_DATE_COLUMNS);
1657 if(row == null) {
1658 return null;
1659 }
1660 Object date = row.get(dateCol);
1661 return ((date != null) ? ColumnImpl.toLocalDateTime(date, this) : null);
1662 }
1663
1664 private Integer getDbParentId() throws IOException {
1665
1666
1667
1668 if(_dynamicDbParentId != null) {
1669 return _dynamicDbParentId;
1670 }
1671 if(_dbParentId == null) {
1672 _dbParentId = _tableFinder.findObjectId(DB_PARENT_ID,
1673 SYSTEM_OBJECT_NAME_DATABASES);
1674 if(_dbParentId == null) {
1675 throw new IOException(withErrorContext(
1676 "Did not find required parent db id"));
1677 }
1678 }
1679 return _dbParentId;
1680 }
1681
1682 private byte[] getNewObjectOwner() throws IOException {
1683 if(_newObjOwner == null) {
1684
1685
1686
1687
1688 Row msysDbRow = _tableFinder.getObjectRow(
1689 getDbParentId(), OBJECT_NAME_DB_PROPS,
1690 Collections.singleton(CAT_COL_OWNER));
1691 byte[] owner = null;
1692 if(msysDbRow != null) {
1693 owner = msysDbRow.getBytes(CAT_COL_OWNER);
1694 }
1695 _newObjOwner = (((owner != null) && (owner.length > 0)) ?
1696 owner : SYS_DEFAULT_SID);
1697 }
1698 return _newObjOwner;
1699 }
1700
1701
1702
1703
1704 private PropertyMaps getPropertiesForDbObject(String dbName)
1705 throws IOException
1706 {
1707 return readProperties(
1708 -1, _tableFinder.getObjectRow(
1709 getDbParentId(), dbName, SYSTEM_CATALOG_PROPS_COLUMNS), null);
1710 }
1711
1712 private PropertyMaps readProperties(int objectId, Row objectRow,
1713 PropertyMaps.Owner owner)
1714 throws IOException
1715 {
1716 byte[] propsBytes = null;
1717 RowIdImpl rowId = null;
1718 if(objectRow != null) {
1719 propsBytes = objectRow.getBytes(CAT_COL_PROPS);
1720 objectId = objectRow.getInt(CAT_COL_ID);
1721 rowId = (RowIdImpl)objectRow.getId();
1722 }
1723 return getPropsHandler().read(propsBytes, objectId, rowId, owner);
1724 }
1725
1726 @Override
1727 public String getDatabasePassword() throws IOException
1728 {
1729 ByteBuffer buffer = takeSharedBuffer();
1730 try {
1731 _pageChannel.readRootPage(buffer);
1732
1733 byte[] pwdBytes = new byte[_format.SIZE_PASSWORD];
1734 buffer.position(_format.OFFSET_PASSWORD);
1735 buffer.get(pwdBytes);
1736
1737
1738
1739
1740 byte[] pwdMask = getPasswordMask(buffer, _format);
1741 if(pwdMask != null) {
1742 for(int i = 0; i < pwdBytes.length; ++i) {
1743 pwdBytes[i] ^= pwdMask[i % pwdMask.length];
1744 }
1745 }
1746
1747 boolean hasPassword = false;
1748 for(int i = 0; i < pwdBytes.length; ++i) {
1749 if(pwdBytes[i] != 0) {
1750 hasPassword = true;
1751 break;
1752 }
1753 }
1754
1755 if(!hasPassword) {
1756 return null;
1757 }
1758
1759 String pwd = ColumnImpl.decodeUncompressedText(pwdBytes, getCharset());
1760
1761
1762 int idx = pwd.indexOf('\0');
1763 if(idx >= 0) {
1764 pwd = pwd.substring(0, idx);
1765 }
1766
1767 return pwd;
1768 } finally {
1769 releaseSharedBuffer(buffer);
1770 }
1771 }
1772
1773
1774
1775
1776
1777 private void collectRelationships(
1778 Cursor cursor, TableImpl./../../com/healthmarketscience/jackcess/impl/TableImpl.html#TableImpl">TableImpl fromTable, TableImpl toTable,
1779 List<Relationship> relationships, boolean includeSystemTables)
1780 throws IOException
1781 {
1782 String fromTableName = ((fromTable != null) ? fromTable.getName() : null);
1783 String toTableName = ((toTable != null) ? toTable.getName() : null);
1784
1785 for(Row row : cursor) {
1786 String fromName = row.getString(REL_COL_FROM_TABLE);
1787 String toName = row.getString(REL_COL_TO_TABLE);
1788
1789 if(((fromTableName == null) ||
1790 fromTableName.equalsIgnoreCase(fromName)) &&
1791 ((toTableName == null) ||
1792 toTableName.equalsIgnoreCase(toName))) {
1793
1794 String relName = row.getString(REL_COL_NAME);
1795
1796
1797
1798 Relationship rel = null;
1799 for(Relationship tmp : relationships) {
1800 if(tmp.getName().equalsIgnoreCase(relName)) {
1801 rel = tmp;
1802 break;
1803 }
1804 }
1805
1806 TableImpl relFromTable = fromTable;
1807 if(relFromTable == null) {
1808 relFromTable = getTable(fromName, includeSystemTables);
1809 if(relFromTable == null) {
1810
1811 continue;
1812 }
1813 }
1814 TableImpl relToTable = toTable;
1815 if(relToTable == null) {
1816 relToTable = getTable(toName, includeSystemTables);
1817 if(relToTable == null) {
1818
1819 continue;
1820 }
1821 }
1822
1823 if(rel == null) {
1824
1825 int numCols = row.getInt(REL_COL_COLUMN_COUNT);
1826 int flags = row.getInt(REL_COL_FLAGS);
1827 rel = new RelationshipImpl(relName, relFromTable, relToTable,
1828 flags, numCols);
1829 relationships.add(rel);
1830 }
1831
1832
1833 int colIdx = row.getInt(REL_COL_COLUMN_INDEX);
1834 ColumnImpl fromCol = relFromTable.getColumn(
1835 row.getString(REL_COL_FROM_COLUMN));
1836 ColumnImpl toCol = relToTable.getColumn(
1837 row.getString(REL_COL_TO_COLUMN));
1838
1839 rel.getFromColumns().set(colIdx, fromCol);
1840 rel.getToColumns().set(colIdx, toCol);
1841 }
1842 }
1843 }
1844
1845
1846
1847
1848
1849
1850 private void addToSystemCatalog(String name, int objectId, Short type,
1851 String linkedDbName, String linkedTableName,
1852 Integer parentId)
1853 throws IOException
1854 {
1855 byte[] owner = getNewObjectOwner();
1856 Object[] catalogRow = new Object[_systemCatalog.getColumnCount()];
1857 int idx = 0;
1858 Date creationTime = new Date();
1859 for (Iterator<ColumnImpl> iter = _systemCatalog.getColumns().iterator();
1860 iter.hasNext(); idx++)
1861 {
1862 ColumnImpl col = iter.next();
1863 if (CAT_COL_ID.equals(col.getName())) {
1864 catalogRow[idx] = Integer.valueOf(objectId);
1865 } else if (CAT_COL_NAME.equals(col.getName())) {
1866 catalogRow[idx] = name;
1867 } else if (CAT_COL_TYPE.equals(col.getName())) {
1868 catalogRow[idx] = type;
1869 } else if (CAT_COL_DATE_CREATE.equals(col.getName()) ||
1870 CAT_COL_DATE_UPDATE.equals(col.getName())) {
1871 catalogRow[idx] = creationTime;
1872 } else if (CAT_COL_PARENT_ID.equals(col.getName())) {
1873 catalogRow[idx] = parentId;
1874 } else if (CAT_COL_FLAGS.equals(col.getName())) {
1875 catalogRow[idx] = Integer.valueOf(0);
1876 } else if (CAT_COL_OWNER.equals(col.getName())) {
1877 catalogRow[idx] = owner;
1878 } else if (CAT_COL_DATABASE.equals(col.getName())) {
1879 catalogRow[idx] = linkedDbName;
1880 } else if (CAT_COL_FOREIGN_NAME.equals(col.getName())) {
1881 catalogRow[idx] = linkedTableName;
1882 }
1883 }
1884 _systemCatalog.addRow(catalogRow);
1885 }
1886
1887
1888
1889
1890 private void addToAccessControlEntries(
1891 Integer objectId, Integer parentId, List<byte[]> sids)
1892 throws IOException
1893 {
1894 if(sids.isEmpty()) {
1895 collectNewObjectSIDs(parentId, sids);
1896 }
1897
1898 TableImpl acEntries = getAccessControlEntries();
1899 ColumnImpl acmCol = acEntries.getColumn(ACE_COL_ACM);
1900 ColumnImpl inheritCol = acEntries.getColumn(ACE_COL_F_INHERITABLE);
1901 ColumnImpl objIdCol = acEntries.getColumn(ACE_COL_OBJECT_ID);
1902 ColumnImpl sidCol = acEntries.getColumn(ACE_COL_SID);
1903
1904
1905 List<Object[]> aceRows = new ArrayList<Object[]>(sids.size());
1906 for(byte[] sid : sids) {
1907 Object[] aceRow = new Object[acEntries.getColumnCount()];
1908 acmCol.setRowValue(aceRow, SYS_FULL_ACCESS_ACM);
1909 inheritCol.setRowValue(aceRow, Boolean.FALSE);
1910 objIdCol.setRowValue(aceRow, objectId);
1911 sidCol.setRowValue(aceRow, sid);
1912 aceRows.add(aceRow);
1913 }
1914 acEntries.addRows(aceRows);
1915 }
1916
1917
1918
1919
1920 private void collectNewObjectSIDs(Integer parentId, List<byte[]> sids)
1921 throws IOException
1922 {
1923
1924
1925 Cursor cursor = createCursorWithOptionalIndex(
1926 getAccessControlEntries(), ACE_COL_OBJECT_ID, parentId);
1927
1928 for(Row row : cursor) {
1929 Integer objId = row.getInt(ACE_COL_OBJECT_ID);
1930 if(parentId.equals(objId)) {
1931 sids.add(row.getBytes(ACE_COL_SID));
1932 }
1933 }
1934
1935 if(sids.isEmpty()) {
1936
1937 sids.add(SYS_DEFAULT_SID);
1938 }
1939 }
1940
1941
1942
1943
1944 private TableImpl loadTable(String name, int pageNumber, int flags, Short type)
1945 throws IOException
1946 {
1947
1948 TableImpl table = _tableCache.get(pageNumber);
1949 if(table != null) {
1950 return table;
1951 }
1952
1953 if(name == null) {
1954
1955 Row objectRow = _tableFinder.getObjectRow(
1956 pageNumber, SYSTEM_CATALOG_COLUMNS);
1957 if(objectRow == null) {
1958 return null;
1959 }
1960
1961 name = objectRow.getString(CAT_COL_NAME);
1962 flags = objectRow.getInt(CAT_COL_FLAGS);
1963 type = objectRow.getShort(CAT_COL_TYPE);
1964 }
1965
1966
1967 return _tableCache.put(readTable(name, pageNumber, flags, type));
1968 }
1969
1970
1971
1972
1973 private TableImpl readTable(
1974 String name, int pageNumber, int flags, Short type)
1975 throws IOException
1976 {
1977 ByteBuffer buffer = takeSharedBuffer();
1978 try {
1979
1980 _pageChannel.readPage(buffer, pageNumber);
1981 byte pageType = buffer.get(0);
1982 if (pageType != PageTypes.TABLE_DEF) {
1983 throw new IOException(withErrorContext(
1984 "Looking for " + name + " at page " + pageNumber +
1985 ", but page type is " + pageType));
1986 }
1987 return (!TYPE_LINKED_ODBC_TABLE.equals(type) ?
1988 new TableImpl(this, buffer, pageNumber, name, flags) :
1989 new TableDefinitionImpl(this, buffer, pageNumber, name, flags));
1990 } finally {
1991 releaseSharedBuffer(buffer);
1992 }
1993 }
1994
1995
1996
1997
1998
1999 private Cursor createCursorWithOptionalIndex(
2000 TableImpl table, String colName, Object colValue)
2001 throws IOException
2002 {
2003 try {
2004 return table.newCursor()
2005 .setIndexByColumnNames(colName)
2006 .setSpecificEntry(colValue)
2007 .toCursor();
2008 } catch(IllegalArgumentException e) {
2009 if(LOG.isDebugEnabled()) {
2010 LOG.debug(withErrorContext(
2011 "Could not find expected index on table " + table.getName()));
2012 }
2013 }
2014
2015 return CursorImpl.createCursor(table);
2016 }
2017
2018 @Override
2019 public void flush() throws IOException {
2020 if(_linkedDbs != null) {
2021 for(Database linkedDb : _linkedDbs.values()) {
2022 linkedDb.flush();
2023 }
2024 }
2025 _pageChannel.flush();
2026 }
2027
2028 @Override
2029 public void close() throws IOException {
2030 if(_linkedDbs != null) {
2031 for(Database linkedDb : _linkedDbs.values()) {
2032 linkedDb.close();
2033 }
2034 }
2035 _pageChannel.close();
2036 }
2037
2038 public void validateNewTableName(String name) throws IOException {
2039 validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
2040
2041 if(lookupTable(name) != null) {
2042 throw new IllegalArgumentException(withErrorContext(
2043 "Cannot create table with name of existing table '" + name + "'"));
2044 }
2045 }
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061 public static void validateIdentifierName(String name,
2062 int maxLength,
2063 String identifierType)
2064 {
2065
2066 validateName(name, maxLength, identifierType);
2067
2068
2069 if(INVALID_IDENTIFIER_CHARS.matcher(name).find()) {
2070 throw new IllegalArgumentException(
2071 identifierType + " name '" + name + "' contains invalid characters");
2072 }
2073
2074
2075 if(name.charAt(0) == ' ') {
2076 throw new IllegalArgumentException(
2077 identifierType + " name '" + name +
2078 "' cannot start with a space character");
2079 }
2080 }
2081
2082
2083
2084
2085 private static void validateName(String name, int maxLength, String nameType)
2086 {
2087 if(StringUtil.isBlank(name)) {
2088 throw new IllegalArgumentException(
2089 nameType + " must have non-blank name");
2090 }
2091 if(name.length() > maxLength) {
2092 throw new IllegalArgumentException(
2093 nameType + " name is longer than max length of " + maxLength +
2094 ": " + name);
2095 }
2096 }
2097
2098 @Override
2099 public String toString() {
2100 return ToStringBuilder.reflectionToString(this);
2101 }
2102
2103
2104
2105
2106 private void addTable(String tableName, Integer pageNumber, Short type,
2107 String linkedDbName, String linkedTableName)
2108 {
2109 _tableLookup.put(toLookupName(tableName),
2110 createTableInfo(tableName, pageNumber, 0, type,
2111 linkedDbName, linkedTableName, null));
2112
2113 _tableNames = null;
2114 }
2115
2116 private static TableInfo createTableInfo(
2117 String tableName, Short type, Row row) {
2118
2119 Integer pageNumber = row.getInt(CAT_COL_ID);
2120 int flags = row.getInt(CAT_COL_FLAGS);
2121 String linkedDbName = row.getString(CAT_COL_DATABASE);
2122 String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME);
2123 String connectName = row.getString(CAT_COL_CONNECT_NAME);
2124
2125 return createTableInfo(tableName, pageNumber, flags, type, linkedDbName,
2126 linkedTableName, connectName);
2127 }
2128
2129
2130
2131
2132 private static TableInfo createTableInfo(
2133 String tableName, Integer pageNumber, int flags, Short type,
2134 String linkedDbName, String linkedTableName, String connectName)
2135 {
2136 if(TYPE_LINKED_TABLE.equals(type)) {
2137 return new LinkedTableInfo(pageNumber, tableName, flags, type,
2138 linkedDbName, linkedTableName);
2139 } else if(TYPE_LINKED_ODBC_TABLE.equals(type)) {
2140 return new LinkedODBCTableInfo(pageNumber, tableName, flags, type,
2141 connectName, linkedTableName);
2142 }
2143 return new TableInfo(pageNumber, tableName, flags, type);
2144 }
2145
2146
2147
2148
2149 private TableInfo lookupTable(String tableName) throws IOException {
2150
2151 String lookupTableName = toLookupName(tableName);
2152 TableInfo tableInfo = _tableLookup.get(lookupTableName);
2153 if(tableInfo != null) {
2154 return tableInfo;
2155 }
2156
2157 tableInfo = _tableFinder.lookupTable(tableName);
2158
2159 if(tableInfo != null) {
2160
2161 _tableLookup.put(lookupTableName, tableInfo);
2162 }
2163
2164 return tableInfo;
2165 }
2166
2167
2168
2169
2170 public static String toLookupName(String name) {
2171 return ((name != null) ? name.toUpperCase(Locale.ROOT) : null);
2172 }
2173
2174
2175
2176
2177
2178 private static boolean isSystemObject(int flags) {
2179 return ((flags & SYSTEM_OBJECT_FLAGS) != 0);
2180 }
2181
2182
2183
2184
2185
2186
2187
2188
2189 public static TimeZone getDefaultTimeZone()
2190 {
2191 String tzProp = SystemConfig.getProperty(TIMEZONE_PROPERTY);
2192 if(tzProp != null) {
2193 tzProp = tzProp.trim();
2194 if(tzProp.length() > 0) {
2195 return TimeZone.getTimeZone(tzProp);
2196 }
2197 }
2198
2199
2200 return TimeZone.getDefault();
2201 }
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212 public static Charset getDefaultCharset(JetFormat format)
2213 {
2214 String csProp = SystemConfig.getProperty(CHARSET_PROPERTY_PREFIX + format);
2215 if(csProp != null) {
2216 csProp = csProp.trim();
2217 if(csProp.length() > 0) {
2218 return Charset.forName(csProp);
2219 }
2220 }
2221
2222
2223 return format.CHARSET;
2224 }
2225
2226
2227
2228
2229
2230
2231
2232 public static Table.ColumnOrder getDefaultColumnOrder()
2233 {
2234 return getEnumSystemProperty(Table.ColumnOrder.class, COLUMN_ORDER_PROPERTY,
2235 DEFAULT_COLUMN_ORDER);
2236 }
2237
2238
2239
2240
2241
2242
2243
2244 public static boolean getDefaultEnforceForeignKeys()
2245 {
2246 String prop = SystemConfig.getProperty(FK_ENFORCE_PROPERTY);
2247 return ((prop == null) || Boolean.TRUE.toString().equalsIgnoreCase(prop));
2248 }
2249
2250
2251
2252
2253
2254
2255
2256 public static boolean getDefaultAllowAutoNumberInsert()
2257 {
2258 String prop = SystemConfig.getProperty(ALLOW_AUTONUM_INSERT_PROPERTY);
2259 return ((prop != null) && Boolean.TRUE.toString().equalsIgnoreCase(prop));
2260 }
2261
2262
2263
2264
2265
2266
2267
2268 public static boolean getDefaultEvaluateExpressions()
2269 {
2270 String prop = SystemConfig.getProperty(ENABLE_EXPRESSION_EVALUATION_PROPERTY);
2271 return ((prop == null) || Boolean.TRUE.toString().equalsIgnoreCase(prop));
2272 }
2273
2274
2275
2276
2277
2278
2279
2280 public static boolean getDefaultWriteBrokenIndex()
2281 {
2282 String prop = SystemConfig.getProperty(WRITE_BROKEN_INDEX_PROPERTY);
2283 return ((prop != null) && Boolean.TRUE.toString().equalsIgnoreCase(prop));
2284 }
2285
2286
2287
2288
2289
2290
2291
2292 public static DateTimeType getDefaultDateTimeType() {
2293 return getEnumSystemProperty(DateTimeType.class, DATE_TIME_TYPE_PROPERTY,
2294 DateTimeType.LOCAL_DATE_TIME);
2295 }
2296
2297
2298
2299
2300
2301 protected static void transferDbFrom(FileChannel channel, InputStream in)
2302 throws IOException
2303 {
2304 ReadableByteChannel readChannel = Channels.newChannel(in);
2305 if(!BROKEN_NIO) {
2306
2307 channel.transferFrom(readChannel, 0, MAX_EMPTYDB_SIZE);
2308 } else {
2309
2310 ByteBuffer bb = ByteBuffer.allocate(8096);
2311 while(readChannel.read(bb) >= 0) {
2312 bb.flip();
2313 channel.write(bb);
2314 bb.clear();
2315 }
2316 }
2317 }
2318
2319
2320
2321
2322
2323
2324 public static byte[] getPasswordMask(ByteBuffer buffer, JetFormat format)
2325 {
2326
2327
2328 int pwdMaskPos = format.OFFSET_HEADER_DATE;
2329 if(pwdMaskPos < 0) {
2330 return null;
2331 }
2332
2333 buffer.position(pwdMaskPos);
2334 double dateVal = Double.longBitsToDouble(buffer.getLong());
2335
2336 byte[] pwdMask = new byte[4];
2337 PageChannel.wrap(pwdMask).putInt((int)dateVal);
2338
2339 return pwdMask;
2340 }
2341
2342 protected static InputStream getResourceAsStream(String resourceName)
2343 throws IOException
2344 {
2345 InputStream stream = DatabaseImpl.class.getClassLoader()
2346 .getResourceAsStream(resourceName);
2347
2348 if(stream == null) {
2349
2350 stream = Thread.currentThread().getContextClassLoader()
2351 .getResourceAsStream(resourceName);
2352
2353 if(stream == null) {
2354 throw new IOException("Could not load jackcess resource " +
2355 resourceName);
2356 }
2357 }
2358
2359 return stream;
2360 }
2361
2362 private static boolean isTableType(Short objType) {
2363 return(TYPE_TABLE.equals(objType) || isAnyLinkedTableType(objType));
2364 }
2365
2366 public static FileFormatDetails getFileFormatDetails(FileFormat fileFormat) {
2367 return FILE_FORMAT_DETAILS.get(fileFormat);
2368 }
2369
2370 private static void addFileFormatDetails(
2371 FileFormat fileFormat, String emptyFileName, JetFormat format)
2372 {
2373 String emptyFile =
2374 ((emptyFileName != null) ?
2375 RESOURCE_PATH + emptyFileName + fileFormat.getFileExtension() : null);
2376 FILE_FORMAT_DETAILS.put(fileFormat, new FileFormatDetails(emptyFile, format));
2377 }
2378
2379 private static String getName(Path file) {
2380 if(file == null) {
2381 return "<UNKNOWN.DB>";
2382 }
2383 return file.getFileName().toString();
2384 }
2385
2386 private String withErrorContext(String msg) {
2387 return withErrorContext(msg, getName());
2388 }
2389
2390 private static String withErrorContext(String msg, String dbName) {
2391 return msg + " (Db=" + dbName + ")";
2392 }
2393
2394 private static <E extends Enum<E>> E getEnumSystemProperty(
2395 Class<E> enumClass, String propName, E defaultValue)
2396 {
2397 String prop = SystemConfig.getProperty(propName);
2398 if(prop != null) {
2399 prop = prop.trim().toUpperCase();
2400 if(!prop.isEmpty()) {
2401 return Enum.valueOf(enumClass, prop);
2402 }
2403 }
2404 return defaultValue;
2405 }
2406
2407 private static boolean isAnyLinkedTableType(Short type) {
2408 return (TYPE_LINKED_TABLE.equals(type) ||
2409 TYPE_LINKED_ODBC_TABLE.equals(type));
2410 }
2411
2412
2413
2414
2415 private static class TableInfo implements TableMetaData
2416 {
2417 public final Integer pageNumber;
2418 public final String tableName;
2419 public final int flags;
2420 public final Short tableType;
2421
2422 private TableInfo(Integer newPageNumber, String newTableName, int newFlags,
2423 Short newTableType) {
2424 pageNumber = newPageNumber;
2425 tableName = newTableName;
2426 flags = newFlags;
2427 tableType = newTableType;
2428 }
2429
2430 @Override
2431 public Type getType() {
2432 return Type.LOCAL;
2433 }
2434
2435 @Override
2436 public String getName() {
2437 return tableName;
2438 }
2439
2440 @Override
2441 public boolean isLinked() {
2442 return false;
2443 }
2444
2445 @Override
2446 public boolean isSystem() {
2447 return isSystemObject(flags);
2448 }
2449
2450 @Override
2451 public String getLinkedTableName() {
2452 return null;
2453 }
2454
2455 @Override
2456 public String getLinkedDbName() {
2457 return null;
2458 }
2459
2460 @Override
2461 public String getConnectionName() {
2462 return null;
2463 }
2464
2465 @Override
2466 public Table open(Database db) throws IOException {
2467 return ((DatabaseImpl)db).getTable(this, true);
2468 }
2469
2470 @Override
2471 public TableDefinition getTableDefinition(Database db) throws IOException {
2472 return null;
2473 }
2474
2475 @Override
2476 public String toString() {
2477 ToStringBuilder sb = CustomToStringStyle.valueBuilder("TableMetaData")
2478 .append("name", getName());
2479 if(isSystem()) {
2480 sb.append("isSystem", isSystem());
2481 }
2482 if(isLinked()) {
2483 sb.append("isLinked", isLinked())
2484 .append("linkedTableName", getLinkedTableName())
2485 .append("linkedDbName", getLinkedDbName())
2486 .append("connectionName", maskPassword(getConnectionName()));
2487 }
2488 return sb.toString();
2489 }
2490
2491 private static String maskPassword(String connectionName) {
2492 return ((connectionName != null) ?
2493 ODBC_PWD_PATTERN.matcher(connectionName).replaceAll("PWD=XXXXXX") :
2494 null);
2495 }
2496 }
2497
2498
2499
2500
2501 private static class LinkedTableInfo extends TableInfo
2502 {
2503 private final String _linkedDbName;
2504 private final String _linkedTableName;
2505
2506 private LinkedTableInfo(Integer newPageNumber, String newTableName,
2507 int newFlags, Short newTableType,
2508 String newLinkedDbName,
2509 String newLinkedTableName) {
2510 super(newPageNumber, newTableName, newFlags, newTableType);
2511 _linkedDbName = newLinkedDbName;
2512 _linkedTableName = newLinkedTableName;
2513 }
2514
2515 @Override
2516 public Type getType() {
2517 return Type.LINKED;
2518 }
2519
2520 @Override
2521 public boolean isLinked() {
2522 return true;
2523 }
2524
2525 @Override
2526 public String getLinkedTableName() {
2527 return _linkedTableName;
2528 }
2529
2530 @Override
2531 public String getLinkedDbName() {
2532 return _linkedDbName;
2533 }
2534 }
2535
2536
2537
2538
2539 private static class LinkedODBCTableInfo extends TableInfo
2540 {
2541 private final String _linkedTableName;
2542 private final String _connectionName;
2543
2544 private LinkedODBCTableInfo(Integer newPageNumber, String newTableName,
2545 int newFlags, Short newTableType,
2546 String connectName,
2547 String newLinkedTableName) {
2548 super(newPageNumber, newTableName, newFlags, newTableType);
2549 _linkedTableName = newLinkedTableName;
2550 _connectionName = connectName;
2551 }
2552
2553 @Override
2554 public Type getType() {
2555 return Type.LINKED_ODBC;
2556 }
2557
2558 @Override
2559 public boolean isLinked() {
2560 return true;
2561 }
2562
2563 @Override
2564 public String getLinkedTableName() {
2565 return _linkedTableName;
2566 }
2567
2568 @Override
2569 public String getConnectionName() {
2570 return _connectionName;
2571 }
2572
2573 @Override
2574 public Table open(Database db) {
2575 return null;
2576 }
2577
2578 @Override
2579 public TableDefinition getTableDefinition(Database db) throws IOException {
2580 return (((pageNumber != null) && (pageNumber > 0)) ?
2581 ((DatabaseImpl)db).getTable(this, true) :
2582 null);
2583 }
2584 }
2585
2586
2587
2588
2589 private class TableIterator implements Iterator<Table>
2590 {
2591 private final Iterator<String> _tableNameIter;
2592
2593 private TableIterator(Set<String> tableNames) {
2594 _tableNameIter = tableNames.iterator();
2595 }
2596
2597 @Override
2598 public boolean hasNext() {
2599 return _tableNameIter.hasNext();
2600 }
2601
2602 @Override
2603 public Table next() {
2604 if(!hasNext()) {
2605 throw new NoSuchElementException();
2606 }
2607 try {
2608 return getTable(_tableNameIter.next(), true);
2609 } catch(IOException e) {
2610 throw new RuntimeIOException(e);
2611 }
2612 }
2613 }
2614
2615
2616
2617
2618 private abstract class TableFinder
2619 {
2620 public Integer findObjectId(Integer parentId, String name)
2621 throws IOException
2622 {
2623 Cursor cur = findRow(parentId, name);
2624 if(cur == null) {
2625 return null;
2626 }
2627 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2628 return (Integer)cur.getCurrentRowValue(idCol);
2629 }
2630
2631 public Row getObjectRow(Integer parentId, String name,
2632 Collection<String> columns)
2633 throws IOException
2634 {
2635 Cursor cur = findRow(parentId, name);
2636 return ((cur != null) ? cur.getCurrentRow(columns) : null);
2637 }
2638
2639 public Row getObjectRow(
2640 Integer objectId, Collection<String> columns)
2641 throws IOException
2642 {
2643 Cursor cur = findRow(objectId);
2644 return ((cur != null) ? cur.getCurrentRow(columns) : null);
2645 }
2646
2647 public void getTableNames(Set<String> tableNames,
2648 boolean normalTables,
2649 boolean systemTables,
2650 boolean linkedTables)
2651 throws IOException
2652 {
2653 for(Row row : getTableNamesCursor().newIterable().setColumnNames(
2654 SYSTEM_CATALOG_COLUMNS)) {
2655
2656 String tableName = row.getString(CAT_COL_NAME);
2657 int flags = row.getInt(CAT_COL_FLAGS);
2658 Short type = row.getShort(CAT_COL_TYPE);
2659 int parentId = row.getInt(CAT_COL_PARENT_ID);
2660
2661 if(parentId != _tableParentId) {
2662 continue;
2663 }
2664
2665 if(TYPE_TABLE.equals(type)) {
2666 if(!isSystemObject(flags)) {
2667 if(normalTables) {
2668 tableNames.add(tableName);
2669 }
2670 } else if(systemTables) {
2671 tableNames.add(tableName);
2672 }
2673 } else if(linkedTables && isAnyLinkedTableType(type)) {
2674 tableNames.add(tableName);
2675 }
2676 }
2677 }
2678
2679 public boolean isLinkedTable(Table table) throws IOException
2680 {
2681 for(Row row : getTableNamesCursor().newIterable().setColumnNames(
2682 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS)) {
2683 Short type = row.getShort(CAT_COL_TYPE);
2684 String linkedDbName = row.getString(CAT_COL_DATABASE);
2685 String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME);
2686
2687 if(TYPE_LINKED_TABLE.equals(type) &&
2688 matchesLinkedTable(table, linkedTableName, linkedDbName)) {
2689 return true;
2690 }
2691 }
2692 return false;
2693 }
2694
2695 public int getNextFreeSyntheticId() throws IOException {
2696 int maxSynthId = findMaxSyntheticId();
2697 if(maxSynthId >= -1) {
2698
2699 throw new IllegalStateException(withErrorContext(
2700 "Too many database objects"));
2701 }
2702 return maxSynthId + 1;
2703 }
2704
2705 public Iterator<TableMetaData> iterateTableMetaData() throws IOException {
2706 return new Iterator<TableMetaData>() {
2707 private final Iterator<Row> _iter =
2708 getTableNamesCursor().newIterable().setColumnNames(
2709 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS).iterator();
2710 private TableMetaData _next;
2711
2712 @Override
2713 public boolean hasNext() {
2714 if((_next == null) && _iter.hasNext()) {
2715 _next = nextTableMetaData(_iter);
2716 }
2717 return (_next != null);
2718 }
2719
2720 @Override
2721 public TableMetaData next() {
2722 if(!hasNext()) {
2723 throw new NoSuchElementException();
2724 }
2725
2726 TableMetaData next = _next;
2727 _next = null;
2728 return next;
2729 }
2730 };
2731 }
2732
2733 private TableMetaData nextTableMetaData(Iterator<Row> detailIter) {
2734
2735 while(detailIter.hasNext()) {
2736 Row row = detailIter.next();
2737
2738 Short type = row.getShort(CAT_COL_TYPE);
2739 if(!isTableType(type)) {
2740 continue;
2741 }
2742
2743 int parentId = row.getInt(CAT_COL_PARENT_ID);
2744 if(parentId != _tableParentId) {
2745 continue;
2746 }
2747
2748 String realName = row.getString(CAT_COL_NAME);
2749
2750 return createTableInfo(realName, type, row);
2751 }
2752
2753 return null;
2754 }
2755
2756 protected abstract Cursor findRow(Integer parentId, String name)
2757 throws IOException;
2758
2759 protected abstract Cursor findRow(Integer objectId)
2760 throws IOException;
2761
2762 protected abstract Cursor getTableNamesCursor() throws IOException;
2763
2764 public abstract TableInfo lookupTable(String tableName)
2765 throws IOException;
2766
2767 public abstract TableInfo lookupTable(String tableName, Integer parentId)
2768 throws IOException;
2769
2770 protected abstract int findMaxSyntheticId() throws IOException;
2771 }
2772
2773
2774
2775
2776 private final class DefaultTableFinder extends TableFinder
2777 {
2778 private final IndexCursor _systemCatalogCursor;
2779 private IndexCursor _systemCatalogIdCursor;
2780
2781 private DefaultTableFinder(IndexCursor systemCatalogCursor) {
2782 _systemCatalogCursor = systemCatalogCursor;
2783 }
2784
2785 private void initIdCursor() throws IOException {
2786 if(_systemCatalogIdCursor == null) {
2787 _systemCatalogIdCursor = _systemCatalog.newCursor()
2788 .setIndexByColumnNames(CAT_COL_ID)
2789 .toIndexCursor();
2790 }
2791 }
2792
2793 @Override
2794 protected Cursor findRow(Integer parentId, String name)
2795 throws IOException
2796 {
2797 return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
2798 _systemCatalogCursor : null);
2799 }
2800
2801 @Override
2802 protected Cursor findRow(Integer objectId) throws IOException
2803 {
2804 initIdCursor();
2805 return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
2806 _systemCatalogIdCursor : null);
2807 }
2808
2809 @Override
2810 public TableInfo lookupTable(String tableName) throws IOException {
2811 return lookupTable(tableName, _tableParentId);
2812 }
2813
2814 @Override
2815 public TableInfo lookupTable(String tableName, Integer parentId)
2816 throws IOException
2817 {
2818 if(findRow(parentId, tableName) == null) {
2819 return null;
2820 }
2821
2822 Row row = _systemCatalogCursor.getCurrentRow(
2823 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS);
2824 Short type = row.getShort(CAT_COL_TYPE);
2825
2826 if(!isTableType(type)) {
2827 return null;
2828 }
2829
2830 String realName = row.getString(CAT_COL_NAME);
2831
2832 return createTableInfo(realName, type, row);
2833 }
2834
2835 @Override
2836 protected Cursor getTableNamesCursor() throws IOException {
2837 return _systemCatalogCursor.getIndex().newCursor()
2838 .setStartEntry(_tableParentId, IndexData.MIN_VALUE)
2839 .setEndEntry(_tableParentId, IndexData.MAX_VALUE)
2840 .toIndexCursor();
2841 }
2842
2843 @Override
2844 protected int findMaxSyntheticId() throws IOException {
2845 initIdCursor();
2846 _systemCatalogIdCursor.reset();
2847
2848
2849
2850 _systemCatalogIdCursor.findClosestRowByEntry(0);
2851 if(!_systemCatalogIdCursor.moveToPreviousRow()) {
2852 return Integer.MIN_VALUE;
2853 }
2854 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2855 return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
2856 }
2857 }
2858
2859
2860
2861
2862 private final class FallbackTableFinder extends TableFinder
2863 {
2864 private final Cursor _systemCatalogCursor;
2865
2866 private FallbackTableFinder(Cursor systemCatalogCursor) {
2867 _systemCatalogCursor = systemCatalogCursor;
2868 }
2869
2870 @Override
2871 protected Cursor findRow(Integer parentId, String name)
2872 throws IOException
2873 {
2874 Map<String,Object> rowPat = new HashMap<String,Object>();
2875 rowPat.put(CAT_COL_PARENT_ID, parentId);
2876 rowPat.put(CAT_COL_NAME, name);
2877 return (_systemCatalogCursor.findFirstRow(rowPat) ?
2878 _systemCatalogCursor : null);
2879 }
2880
2881 @Override
2882 protected Cursor findRow(Integer objectId) throws IOException
2883 {
2884 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2885 return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
2886 _systemCatalogCursor : null);
2887 }
2888
2889 @Override
2890 public TableInfo lookupTable(String tableName) throws IOException {
2891 return lookupTable(tableName, _tableParentId);
2892 }
2893
2894
2895
2896
2897
2898
2899
2900 @Override
2901 public TableInfo lookupTable(String tableName, Integer parentId)
2902 throws IOException
2903 {
2904 for(Row row : _systemCatalogCursor.newIterable().setColumnNames(
2905 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS)) {
2906
2907 Short type = row.getShort(CAT_COL_TYPE);
2908 if(!isTableType(type)) {
2909 continue;
2910 }
2911
2912 int rowParentId = row.getInt(CAT_COL_PARENT_ID);
2913 if(parentId != null && rowParentId != parentId) {
2914 continue;
2915 }
2916
2917 String realName = row.getString(CAT_COL_NAME);
2918 if(!tableName.equalsIgnoreCase(realName)) {
2919 continue;
2920 }
2921
2922 return createTableInfo(realName, type, row);
2923 }
2924
2925 return null;
2926 }
2927
2928 @Override
2929 protected Cursor getTableNamesCursor() {
2930 return _systemCatalogCursor;
2931 }
2932
2933 @Override
2934 protected int findMaxSyntheticId() throws IOException {
2935
2936 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2937 _systemCatalogCursor.reset();
2938 int curMaxSynthId = Integer.MIN_VALUE;
2939 while(_systemCatalogCursor.moveToNextRow()) {
2940 int id = (Integer)_systemCatalogCursor.getCurrentRowValue(idCol);
2941 if((id > curMaxSynthId) && (id < 0)) {
2942 curMaxSynthId = id;
2943 }
2944 }
2945 return curMaxSynthId;
2946 }
2947 }
2948
2949
2950
2951
2952
2953 private static final class WeakTableReference extends WeakReference<TableImpl>
2954 {
2955 private final Integer _pageNumber;
2956
2957 private WeakTableReference(Integer pageNumber, TableImpl table,
2958 ReferenceQueue<TableImpl> queue) {
2959 super(table, queue);
2960 _pageNumber = pageNumber;
2961 }
2962
2963 public Integer getPageNumber() {
2964 return _pageNumber;
2965 }
2966 }
2967
2968
2969
2970
2971 private static final class TableCache
2972 {
2973 private final Map<Integer,WeakTableReference> _tables =
2974 new HashMap<Integer,WeakTableReference>();
2975 private final ReferenceQueue<TableImpl> _queue =
2976 new ReferenceQueue<TableImpl>();
2977
2978 public TableImpl get(Integer pageNumber) {
2979 WeakTableReference ref = _tables.get(pageNumber);
2980 return ((ref != null) ? ref.get() : null);
2981 }
2982
2983 public TableImplf="../../../../com/healthmarketscience/jackcess/impl/TableImpl.html#TableImpl">TableImpl put(TableImpl table) {
2984 purgeOldRefs();
2985
2986 Integer pageNumber = table.getTableDefPageNumber();
2987 WeakTableReference ref = new WeakTableReference(
2988 pageNumber, table, _queue);
2989 _tables.put(pageNumber, ref);
2990
2991 return table;
2992 }
2993
2994 private void purgeOldRefs() {
2995 WeakTableReference oldRef = null;
2996 while((oldRef = (WeakTableReference)_queue.poll()) != null) {
2997 _tables.remove(oldRef.getPageNumber());
2998 }
2999 }
3000 }
3001
3002
3003
3004
3005
3006 public static final class FileFormatDetails
3007 {
3008 private final String _emptyFile;
3009 private final JetFormat _format;
3010
3011 private FileFormatDetails(String emptyFile, JetFormat format) {
3012 _emptyFile = emptyFile;
3013 _format = format;
3014 }
3015
3016 public String getEmptyFilePath() {
3017 return _emptyFile;
3018 }
3019
3020 public JetFormat getFormat() {
3021 return _format;
3022 }
3023 }
3024 }