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