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