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