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 if(prop != null) {
2160 return Boolean.TRUE.toString().equalsIgnoreCase(prop);
2161 }
2162 return true;
2163 }
2164
2165
2166
2167
2168
2169
2170
2171 public static boolean getDefaultAllowAutoNumberInsert()
2172 {
2173 String prop = System.getProperty(ALLOW_AUTONUM_INSERT_PROPERTY);
2174 if(prop != null) {
2175 return Boolean.TRUE.toString().equalsIgnoreCase(prop);
2176 }
2177 return false;
2178 }
2179
2180
2181
2182
2183
2184
2185
2186 public static boolean getDefaultEvaluateExpressions()
2187 {
2188 String prop = System.getProperty(ENABLE_EXPRESSION_EVALUATION_PROPERTY);
2189 if(prop != null) {
2190 return Boolean.TRUE.toString().equalsIgnoreCase(prop);
2191 }
2192 return true;
2193 }
2194
2195
2196
2197
2198
2199
2200
2201 public static DateTimeType getDefaultDateTimeType() {
2202 return getEnumSystemProperty(DateTimeType.class, DATE_TIME_TYPE_PROPERTY,
2203 DateTimeType.LOCAL_DATE_TIME);
2204 }
2205
2206
2207
2208
2209
2210 protected static void transferDbFrom(FileChannel channel, InputStream in)
2211 throws IOException
2212 {
2213 ReadableByteChannel readChannel = Channels.newChannel(in);
2214 if(!BROKEN_NIO) {
2215
2216 channel.transferFrom(readChannel, 0, MAX_EMPTYDB_SIZE);
2217 } else {
2218
2219 ByteBuffer bb = ByteBuffer.allocate(8096);
2220 while(readChannel.read(bb) >= 0) {
2221 bb.flip();
2222 channel.write(bb);
2223 bb.clear();
2224 }
2225 }
2226 }
2227
2228
2229
2230
2231
2232
2233 public static byte[] getPasswordMask(ByteBuffer buffer, JetFormat format)
2234 {
2235
2236
2237 int pwdMaskPos = format.OFFSET_HEADER_DATE;
2238 if(pwdMaskPos < 0) {
2239 return null;
2240 }
2241
2242 buffer.position(pwdMaskPos);
2243 double dateVal = Double.longBitsToDouble(buffer.getLong());
2244
2245 byte[] pwdMask = new byte[4];
2246 PageChannel.wrap(pwdMask).putInt((int)dateVal);
2247
2248 return pwdMask;
2249 }
2250
2251 protected static InputStream getResourceAsStream(String resourceName)
2252 throws IOException
2253 {
2254 InputStream stream = DatabaseImpl.class.getClassLoader()
2255 .getResourceAsStream(resourceName);
2256
2257 if(stream == null) {
2258
2259 stream = Thread.currentThread().getContextClassLoader()
2260 .getResourceAsStream(resourceName);
2261
2262 if(stream == null) {
2263 throw new IOException("Could not load jackcess resource " +
2264 resourceName);
2265 }
2266 }
2267
2268 return stream;
2269 }
2270
2271 private static boolean isTableType(Short objType) {
2272 return(TYPE_TABLE.equals(objType) || isAnyLinkedTableType(objType));
2273 }
2274
2275 public static FileFormatDetails getFileFormatDetails(FileFormat fileFormat) {
2276 return FILE_FORMAT_DETAILS.get(fileFormat);
2277 }
2278
2279 private static void addFileFormatDetails(
2280 FileFormat fileFormat, String emptyFileName, JetFormat format)
2281 {
2282 String emptyFile =
2283 ((emptyFileName != null) ?
2284 RESOURCE_PATH + emptyFileName + fileFormat.getFileExtension() : null);
2285 FILE_FORMAT_DETAILS.put(fileFormat, new FileFormatDetails(emptyFile, format));
2286 }
2287
2288 private static String getName(Path file) {
2289 if(file == null) {
2290 return "<UNKNOWN.DB>";
2291 }
2292 return file.getFileName().toString();
2293 }
2294
2295 private String withErrorContext(String msg) {
2296 return withErrorContext(msg, getName());
2297 }
2298
2299 private static String withErrorContext(String msg, String dbName) {
2300 return msg + " (Db=" + dbName + ")";
2301 }
2302
2303 private static <E extends Enum<E>> E getEnumSystemProperty(
2304 Class<E> enumClass, String propName, E defaultValue)
2305 {
2306 String prop = System.getProperty(propName);
2307 if(prop != null) {
2308 prop = prop.trim().toUpperCase();
2309 if(!prop.isEmpty()) {
2310 return Enum.valueOf(enumClass, prop);
2311 }
2312 }
2313 return defaultValue;
2314 }
2315
2316 private static boolean isAnyLinkedTableType(Short type) {
2317 return (TYPE_LINKED_TABLE.equals(type) ||
2318 TYPE_LINKED_ODBC_TABLE.equals(type));
2319 }
2320
2321
2322
2323
2324 private static class TableInfo implements TableMetaData
2325 {
2326 public final Integer pageNumber;
2327 public final String tableName;
2328 public final int flags;
2329 public final Short tableType;
2330
2331 private TableInfo(Integer newPageNumber, String newTableName, int newFlags,
2332 Short newTableType) {
2333 pageNumber = newPageNumber;
2334 tableName = newTableName;
2335 flags = newFlags;
2336 tableType = newTableType;
2337 }
2338
2339 @Override
2340 public Type getType() {
2341 return Type.LOCAL;
2342 }
2343
2344 @Override
2345 public String getName() {
2346 return tableName;
2347 }
2348
2349 @Override
2350 public boolean isLinked() {
2351 return false;
2352 }
2353
2354 @Override
2355 public boolean isSystem() {
2356 return isSystemObject(flags);
2357 }
2358
2359 @Override
2360 public String getLinkedTableName() {
2361 return null;
2362 }
2363
2364 @Override
2365 public String getLinkedDbName() {
2366 return null;
2367 }
2368
2369 @Override
2370 public String getConnectionName() {
2371 return null;
2372 }
2373
2374 @Override
2375 public Table open(Database db) throws IOException {
2376 return ((DatabaseImpl)db).getTable(this, true);
2377 }
2378
2379 @Override
2380 public TableDefinition getTableDefinition(Database db) throws IOException {
2381 return null;
2382 }
2383
2384 @Override
2385 public String toString() {
2386 ToStringBuilder sb = CustomToStringStyle.valueBuilder("TableMetaData")
2387 .append("name", getName());
2388 if(isSystem()) {
2389 sb.append("isSystem", isSystem());
2390 }
2391 if(isLinked()) {
2392 sb.append("isLinked", isLinked())
2393 .append("linkedTableName", getLinkedTableName())
2394 .append("linkedDbName", getLinkedDbName())
2395 .append("connectionName", maskPassword(getConnectionName()));
2396 }
2397 return sb.toString();
2398 }
2399
2400 private static String maskPassword(String connectionName) {
2401 return ((connectionName != null) ?
2402 ODBC_PWD_PATTERN.matcher(connectionName).replaceAll("PWD=XXXXXX") :
2403 null);
2404 }
2405 }
2406
2407
2408
2409
2410 private static class LinkedTableInfo extends TableInfo
2411 {
2412 private final String _linkedDbName;
2413 private final String _linkedTableName;
2414
2415 private LinkedTableInfo(Integer newPageNumber, String newTableName,
2416 int newFlags, Short newTableType,
2417 String newLinkedDbName,
2418 String newLinkedTableName) {
2419 super(newPageNumber, newTableName, newFlags, newTableType);
2420 _linkedDbName = newLinkedDbName;
2421 _linkedTableName = newLinkedTableName;
2422 }
2423
2424 @Override
2425 public Type getType() {
2426 return Type.LINKED;
2427 }
2428
2429 @Override
2430 public boolean isLinked() {
2431 return true;
2432 }
2433
2434 @Override
2435 public String getLinkedTableName() {
2436 return _linkedTableName;
2437 }
2438
2439 @Override
2440 public String getLinkedDbName() {
2441 return _linkedDbName;
2442 }
2443 }
2444
2445
2446
2447
2448 private static class LinkedODBCTableInfo extends TableInfo
2449 {
2450 private final String _linkedTableName;
2451 private final String _connectionName;
2452
2453 private LinkedODBCTableInfo(Integer newPageNumber, String newTableName,
2454 int newFlags, Short newTableType,
2455 String connectName,
2456 String newLinkedTableName) {
2457 super(newPageNumber, newTableName, newFlags, newTableType);
2458 _linkedTableName = newLinkedTableName;
2459 _connectionName = connectName;
2460 }
2461
2462 @Override
2463 public Type getType() {
2464 return Type.LINKED_ODBC;
2465 }
2466
2467 @Override
2468 public boolean isLinked() {
2469 return true;
2470 }
2471
2472 @Override
2473 public String getLinkedTableName() {
2474 return _linkedTableName;
2475 }
2476
2477 @Override
2478 public String getConnectionName() {
2479 return _connectionName;
2480 }
2481
2482 @Override
2483 public Table open(Database db) throws IOException {
2484 return null;
2485 }
2486
2487 @Override
2488 public TableDefinition getTableDefinition(Database db) throws IOException {
2489 return (((pageNumber != null) && (pageNumber > 0)) ?
2490 ((DatabaseImpl)db).getTable(this, true) :
2491 null);
2492 }
2493 }
2494
2495
2496
2497
2498 private class TableIterator implements Iterator<Table>
2499 {
2500 private Iterator<String> _tableNameIter;
2501
2502 private TableIterator(Set<String> tableNames) {
2503 _tableNameIter = tableNames.iterator();
2504 }
2505
2506 @Override
2507 public boolean hasNext() {
2508 return _tableNameIter.hasNext();
2509 }
2510
2511 @Override
2512 public Table next() {
2513 if(!hasNext()) {
2514 throw new NoSuchElementException();
2515 }
2516 try {
2517 return getTable(_tableNameIter.next(), true);
2518 } catch(IOException e) {
2519 throw new RuntimeIOException(e);
2520 }
2521 }
2522 }
2523
2524
2525
2526
2527 private abstract class TableFinder
2528 {
2529 public Integer findObjectId(Integer parentId, String name)
2530 throws IOException
2531 {
2532 Cursor cur = findRow(parentId, name);
2533 if(cur == null) {
2534 return null;
2535 }
2536 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2537 return (Integer)cur.getCurrentRowValue(idCol);
2538 }
2539
2540 public Row getObjectRow(Integer parentId, String name,
2541 Collection<String> columns)
2542 throws IOException
2543 {
2544 Cursor cur = findRow(parentId, name);
2545 return ((cur != null) ? cur.getCurrentRow(columns) : null);
2546 }
2547
2548 public Row getObjectRow(
2549 Integer objectId, Collection<String> columns)
2550 throws IOException
2551 {
2552 Cursor cur = findRow(objectId);
2553 return ((cur != null) ? cur.getCurrentRow(columns) : null);
2554 }
2555
2556 public void getTableNames(Set<String> tableNames,
2557 boolean normalTables,
2558 boolean systemTables,
2559 boolean linkedTables)
2560 throws IOException
2561 {
2562 for(Row row : getTableNamesCursor().newIterable().setColumnNames(
2563 SYSTEM_CATALOG_COLUMNS)) {
2564
2565 String tableName = row.getString(CAT_COL_NAME);
2566 int flags = row.getInt(CAT_COL_FLAGS);
2567 Short type = row.getShort(CAT_COL_TYPE);
2568 int parentId = row.getInt(CAT_COL_PARENT_ID);
2569
2570 if(parentId != _tableParentId) {
2571 continue;
2572 }
2573
2574 if(TYPE_TABLE.equals(type)) {
2575 if(!isSystemObject(flags)) {
2576 if(normalTables) {
2577 tableNames.add(tableName);
2578 }
2579 } else if(systemTables) {
2580 tableNames.add(tableName);
2581 }
2582 } else if(linkedTables && isAnyLinkedTableType(type)) {
2583 tableNames.add(tableName);
2584 }
2585 }
2586 }
2587
2588 public boolean isLinkedTable(Table table) throws IOException
2589 {
2590 for(Row row : getTableNamesCursor().newIterable().setColumnNames(
2591 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS)) {
2592 Short type = row.getShort(CAT_COL_TYPE);
2593 String linkedDbName = row.getString(CAT_COL_DATABASE);
2594 String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME);
2595
2596 if(TYPE_LINKED_TABLE.equals(type) &&
2597 matchesLinkedTable(table, linkedTableName, linkedDbName)) {
2598 return true;
2599 }
2600 }
2601 return false;
2602 }
2603
2604 public int getNextFreeSyntheticId() throws IOException {
2605 int maxSynthId = findMaxSyntheticId();
2606 if(maxSynthId >= -1) {
2607
2608 throw new IllegalStateException(withErrorContext(
2609 "Too many database objects!"));
2610 }
2611 return maxSynthId + 1;
2612 }
2613
2614 public Iterator<TableMetaData> iterateTableMetaData() throws IOException {
2615 return new Iterator<TableMetaData>() {
2616 private final Iterator<Row> _iter =
2617 getTableNamesCursor().newIterable().setColumnNames(
2618 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS).iterator();
2619 private TableMetaData _next;
2620
2621 @Override
2622 public boolean hasNext() {
2623 if((_next == null) && _iter.hasNext()) {
2624 _next = nextTableMetaData(_iter);
2625 }
2626 return (_next != null);
2627 }
2628
2629 @Override
2630 public TableMetaData next() {
2631 if(!hasNext()) {
2632 throw new NoSuchElementException();
2633 }
2634
2635 TableMetaData next = _next;
2636 _next = null;
2637 return next;
2638 }
2639 };
2640 }
2641
2642 private TableMetaData nextTableMetaData(Iterator<Row> detailIter) {
2643
2644 while(detailIter.hasNext()) {
2645 Row row = detailIter.next();
2646
2647 Short type = row.getShort(CAT_COL_TYPE);
2648 if(!isTableType(type)) {
2649 continue;
2650 }
2651
2652 int parentId = row.getInt(CAT_COL_PARENT_ID);
2653 if(parentId != _tableParentId) {
2654 continue;
2655 }
2656
2657 String realName = row.getString(CAT_COL_NAME);
2658
2659 return createTableInfo(realName, type, row);
2660 }
2661
2662 return null;
2663 }
2664
2665 protected abstract Cursor findRow(Integer parentId, String name)
2666 throws IOException;
2667
2668 protected abstract Cursor findRow(Integer objectId)
2669 throws IOException;
2670
2671 protected abstract Cursor getTableNamesCursor() throws IOException;
2672
2673 public abstract TableInfo lookupTable(String tableName)
2674 throws IOException;
2675
2676 protected abstract int findMaxSyntheticId() throws IOException;
2677 }
2678
2679
2680
2681
2682 private final class DefaultTableFinder extends TableFinder
2683 {
2684 private final IndexCursor _systemCatalogCursor;
2685 private IndexCursor _systemCatalogIdCursor;
2686
2687 private DefaultTableFinder(IndexCursor systemCatalogCursor) {
2688 _systemCatalogCursor = systemCatalogCursor;
2689 }
2690
2691 private void initIdCursor() throws IOException {
2692 if(_systemCatalogIdCursor == null) {
2693 _systemCatalogIdCursor = _systemCatalog.newCursor()
2694 .setIndexByColumnNames(CAT_COL_ID)
2695 .toIndexCursor();
2696 }
2697 }
2698
2699 @Override
2700 protected Cursor findRow(Integer parentId, String name)
2701 throws IOException
2702 {
2703 return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
2704 _systemCatalogCursor : null);
2705 }
2706
2707 @Override
2708 protected Cursor findRow(Integer objectId) throws IOException
2709 {
2710 initIdCursor();
2711 return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
2712 _systemCatalogIdCursor : null);
2713 }
2714
2715 @Override
2716 public TableInfo lookupTable(String tableName) throws IOException {
2717
2718 if(findRow(_tableParentId, tableName) == null) {
2719 return null;
2720 }
2721
2722 Row row = _systemCatalogCursor.getCurrentRow(
2723 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS);
2724 Short type = row.getShort(CAT_COL_TYPE);
2725
2726 if(!isTableType(type)) {
2727 return null;
2728 }
2729
2730 String realName = row.getString(CAT_COL_NAME);
2731
2732 return createTableInfo(realName, type, row);
2733 }
2734
2735 @Override
2736 protected Cursor getTableNamesCursor() throws IOException {
2737 return _systemCatalogCursor.getIndex().newCursor()
2738 .setStartEntry(_tableParentId, IndexData.MIN_VALUE)
2739 .setEndEntry(_tableParentId, IndexData.MAX_VALUE)
2740 .toIndexCursor();
2741 }
2742
2743 @Override
2744 protected int findMaxSyntheticId() throws IOException {
2745 initIdCursor();
2746 _systemCatalogIdCursor.reset();
2747
2748
2749
2750 _systemCatalogIdCursor.findClosestRowByEntry(0);
2751 if(!_systemCatalogIdCursor.moveToPreviousRow()) {
2752 return Integer.MIN_VALUE;
2753 }
2754 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2755 return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
2756 }
2757 }
2758
2759
2760
2761
2762 private final class FallbackTableFinder extends TableFinder
2763 {
2764 private final Cursor _systemCatalogCursor;
2765
2766 private FallbackTableFinder(Cursor systemCatalogCursor) {
2767 _systemCatalogCursor = systemCatalogCursor;
2768 }
2769
2770 @Override
2771 protected Cursor findRow(Integer parentId, String name)
2772 throws IOException
2773 {
2774 Map<String,Object> rowPat = new HashMap<String,Object>();
2775 rowPat.put(CAT_COL_PARENT_ID, parentId);
2776 rowPat.put(CAT_COL_NAME, name);
2777 return (_systemCatalogCursor.findFirstRow(rowPat) ?
2778 _systemCatalogCursor : null);
2779 }
2780
2781 @Override
2782 protected Cursor findRow(Integer objectId) throws IOException
2783 {
2784 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2785 return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
2786 _systemCatalogCursor : null);
2787 }
2788
2789 @Override
2790 public TableInfo lookupTable(String tableName) throws IOException {
2791
2792 for(Row row : _systemCatalogCursor.newIterable().setColumnNames(
2793 SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS)) {
2794
2795 Short type = row.getShort(CAT_COL_TYPE);
2796 if(!isTableType(type)) {
2797 continue;
2798 }
2799
2800 int parentId = row.getInt(CAT_COL_PARENT_ID);
2801 if(parentId != _tableParentId) {
2802 continue;
2803 }
2804
2805 String realName = row.getString(CAT_COL_NAME);
2806 if(!tableName.equalsIgnoreCase(realName)) {
2807 continue;
2808 }
2809
2810 return createTableInfo(realName, type, row);
2811 }
2812
2813 return null;
2814 }
2815
2816 @Override
2817 protected Cursor getTableNamesCursor() throws IOException {
2818 return _systemCatalogCursor;
2819 }
2820
2821 @Override
2822 protected int findMaxSyntheticId() throws IOException {
2823
2824 ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
2825 _systemCatalogCursor.reset();
2826 int curMaxSynthId = Integer.MIN_VALUE;
2827 while(_systemCatalogCursor.moveToNextRow()) {
2828 int id = (Integer)_systemCatalogCursor.getCurrentRowValue(idCol);
2829 if((id > curMaxSynthId) && (id < 0)) {
2830 curMaxSynthId = id;
2831 }
2832 }
2833 return curMaxSynthId;
2834 }
2835 }
2836
2837
2838
2839
2840
2841 private static final class WeakTableReference extends WeakReference<TableImpl>
2842 {
2843 private final Integer _pageNumber;
2844
2845 private WeakTableReference(Integer pageNumber, TableImpl table,
2846 ReferenceQueue<TableImpl> queue) {
2847 super(table, queue);
2848 _pageNumber = pageNumber;
2849 }
2850
2851 public Integer getPageNumber() {
2852 return _pageNumber;
2853 }
2854 }
2855
2856
2857
2858
2859 private static final class TableCache
2860 {
2861 private final Map<Integer,WeakTableReference> _tables =
2862 new HashMap<Integer,WeakTableReference>();
2863 private final ReferenceQueue<TableImpl> _queue =
2864 new ReferenceQueue<TableImpl>();
2865
2866 public TableImpl get(Integer pageNumber) {
2867 WeakTableReference ref = _tables.get(pageNumber);
2868 return ((ref != null) ? ref.get() : null);
2869 }
2870
2871 public TableImplf="../../../../com/healthmarketscience/jackcess/impl/TableImpl.html#TableImpl">TableImpl put(TableImpl table) {
2872 purgeOldRefs();
2873
2874 Integer pageNumber = table.getTableDefPageNumber();
2875 WeakTableReference ref = new WeakTableReference(
2876 pageNumber, table, _queue);
2877 _tables.put(pageNumber, ref);
2878
2879 return table;
2880 }
2881
2882 private void purgeOldRefs() {
2883 WeakTableReference oldRef = null;
2884 while((oldRef = (WeakTableReference)_queue.poll()) != null) {
2885 _tables.remove(oldRef.getPageNumber());
2886 }
2887 }
2888 }
2889
2890
2891
2892
2893
2894 public static final class FileFormatDetails
2895 {
2896 private final String _emptyFile;
2897 private final JetFormat _format;
2898
2899 private FileFormatDetails(String emptyFile, JetFormat format) {
2900 _emptyFile = emptyFile;
2901 _format = format;
2902 }
2903
2904 public String getEmptyFilePath() {
2905 return _emptyFile;
2906 }
2907
2908 public JetFormat getFormat() {
2909 return _format;
2910 }
2911 }
2912 }