1 /*- 2 * Copyright (c) 2002, 2020 Oracle and/or its affiliates. All rights reserved. 3 * 4 * See the file LICENSE for license information. 5 * 6 * $Id$ 7 */ 8 9 package com.sleepycat.db; 10 11 import com.sleepycat.db.internal.Db; 12 import com.sleepycat.db.internal.DbConstants; 13 import com.sleepycat.db.internal.DbEnv; 14 import com.sleepycat.db.internal.DbTxn; 15 16 /** 17 The configuration properties of a <code>SecondaryDatabase</code> extend 18 those of a primary <code>Database</code>. 19 The secondary database configuration is specified when calling {@link 20 Environment#openSecondaryDatabase Environment.openSecondaryDatabase}. 21 <p> 22 To create a configuration object with default attributes: 23 <pre> 24 SecondaryConfig config = new SecondaryConfig(); 25 </pre> 26 To set custom attributes: 27 <pre> 28 SecondaryConfig config = new SecondaryConfig(); 29 config.setAllowCreate(true); 30 config.setSortedDuplicates(true); 31 config.setKeyCreator(new MyKeyCreator()); 32 </pre> 33 <hr> 34 <p> 35 NOTE: There are two situations where the use of secondary databases without 36 transactions requires special consideration. When using a transactional 37 database or when doing read operations only, this note does not apply. 38 <ul> 39 <li>If secondary is configured to not allow duplicates, when the secondary 40 is being updated it is possible that an error will occur when the secondary 41 key value in a record being added is already present in the database. A 42 <code>DatabaseException</code> will be thrown in this situation.</li> 43 <li>If a foreign key constraint is configured with the delete action 44 <code>ABORT</code> (the default setting), a <code>DatabaseException</code> 45 will be thrown if an attempt is made to delete a referenced foreign 46 key.</li> 47 </ul> 48 In both cases, the operation will be partially complete because the primary 49 database record will have already been updated or deleted. In the presence 50 of transactions, the exception will cause the transaction to abort. Without 51 transactions, it is the responsibility of the caller to handle the results 52 of the incomplete update or to take steps to prevent this situation from 53 happening in the first place. 54 <hr> 55 <p> 56 @see Environment#openSecondaryDatabase Environment.openSecondaryDatabase 57 @see SecondaryDatabase 58 */ 59 public class SecondaryConfig extends DatabaseConfig implements Cloneable { 60 /* 61 * For internal use, to allow null as a valid value for 62 * the config parameter. 63 */ 64 public static final SecondaryConfig DEFAULT = new SecondaryConfig(); 65 66 /* package */ checkNull(SecondaryConfig config)67 static SecondaryConfig checkNull(SecondaryConfig config) { 68 return (config == null) ? DEFAULT : config; 69 } 70 71 private boolean allowPopulate; 72 private boolean immutableSecondaryKey; 73 private Db foreign; 74 private ForeignKeyDeleteAction fkDelAction; 75 private ForeignKeyNullifier keyNullifier; 76 private ForeignMultiKeyNullifier multiKeyNullifier; 77 private SecondaryKeyCreator keyCreator; 78 private SecondaryMultiKeyCreator multiKeyCreator; 79 80 /** 81 Creates an instance with the system's default settings. 82 */ SecondaryConfig()83 public SecondaryConfig() { 84 } 85 86 /** 87 Specifies whether automatic population of the secondary is allowed. 88 <p> 89 If automatic population is allowed, when the secondary database is 90 opened it is checked to see if it is empty. If it is empty, the 91 primary database is read in its entirety and keys are added to the 92 secondary database using the information read from the primary. 93 <p> 94 If this property is set to true and the database is transactional, the 95 population of the secondary will be done within the explicit or auto-commit 96 transaction that is used to open the database. 97 <p> 98 @param allowPopulate whether automatic population of the secondary is 99 allowed. 100 */ setAllowPopulate(final boolean allowPopulate)101 public void setAllowPopulate(final boolean allowPopulate) { 102 this.allowPopulate = allowPopulate; 103 } 104 105 /** 106 Returns whether automatic population of the secondary is allowed. If 107 {@link #setAllowPopulate} has not been called, this method returns 108 false. 109 <p> 110 @return whether automatic population of the secondary is allowed. 111 @see #setAllowPopulate 112 */ getAllowPopulate()113 public boolean getAllowPopulate() { 114 return allowPopulate; 115 } 116 117 /** 118 Specifies whether the secondary key is immutable. 119 <p> 120 Specifying that a secondary key is immutable can be used to optimize 121 updates when the secondary key in a primary record will never be changed 122 after that primary record is inserted. For immutable secondary keys, a 123 best effort is made to avoid calling 124 <code>SecondaryKeyCreator.createSecondaryKey</code> when a primary record 125 is updated. This optimization may reduce the overhead of an update 126 operation significantly if the <code>createSecondaryKey</code> operation is 127 expensive. 128 <p> 129 Be sure to set this property to true only if the secondary key in the 130 primary record is never changed. If this rule is violated, the secondary 131 index will become corrupted, that is, it will become out of sync with the 132 primary. 133 <p> 134 @param immutableSecondaryKey whether the secondary key is immutable. 135 */ setImmutableSecondaryKey(final boolean immutableSecondaryKey)136 public void setImmutableSecondaryKey(final boolean immutableSecondaryKey) { 137 this.immutableSecondaryKey = immutableSecondaryKey; 138 } 139 140 /** 141 Returns whether the secondary key is immutable. If 142 {@link #setImmutableSecondaryKey} has not been called, this method returns 143 false. 144 <p> 145 @return whether the secondary key is immutable. 146 @see #setImmutableSecondaryKey 147 */ getImmutableSecondaryKey()148 public boolean getImmutableSecondaryKey() { 149 return immutableSecondaryKey; 150 } 151 152 /** 153 Specifies the user-supplied object used for creating single-valued 154 secondary keys. 155 <p> 156 Unless the primary database is read-only, a key creator is required 157 when opening a secondary database. Either a KeyCreator or MultiKeyCreator 158 must be specified, but both may not be specified. 159 <p> 160 Unless the primary database is read-only, a key creator is required 161 when opening a secondary database. 162 <p> 163 @param keyCreator the user-supplied object used for creating single-valued 164 secondary keys. 165 */ setKeyCreator(final SecondaryKeyCreator keyCreator)166 public void setKeyCreator(final SecondaryKeyCreator keyCreator) { 167 this.keyCreator = keyCreator; 168 } 169 170 /** 171 Returns the user-supplied object used for creating single-valued secondary 172 keys. 173 <p> 174 @return the user-supplied object used for creating single-valued secondary 175 keys. 176 @see #setKeyCreator 177 */ getKeyCreator()178 public SecondaryKeyCreator getKeyCreator() { 179 return keyCreator; 180 } 181 182 /** 183 Specifies the user-supplied object used for creating multi-valued 184 secondary keys. 185 <p> 186 Unless the primary database is read-only, a key creator is required 187 when opening a secondary database. Either a KeyCreator or MultiKeyCreator 188 must be specified, but both may not be specified. 189 <p> 190 @param multiKeyCreator the user-supplied object used for creating multi-valued 191 secondary keys. 192 */ setMultiKeyCreator(final SecondaryMultiKeyCreator multiKeyCreator)193 public void setMultiKeyCreator(final SecondaryMultiKeyCreator multiKeyCreator) { 194 this.multiKeyCreator = multiKeyCreator; 195 } 196 197 /** 198 Returns the user-supplied object used for creating multi-valued secondary 199 keys. 200 <p> 201 @return the user-supplied object used for creating multi-valued secondary 202 keys. 203 @see #setKeyCreator 204 */ getMultiKeyCreator()205 public SecondaryMultiKeyCreator getMultiKeyCreator() { 206 return multiKeyCreator; 207 } 208 209 /** 210 * Defines a foreign key integrity constraint for a given foreign key 211 * database. 212 * 213 * <p>If this property is non-null, a record must be present in the 214 * specified foreign database for every record in the secondary database, 215 * where the secondary key value is equal to the foreign database key 216 * value. Whenever a record is to be added to the secondary database, the 217 * secondary key is used as a lookup key in the foreign database. 218 * 219 * <p>The foreign database must not have duplicates allowed.</p> 220 * 221 * @param foreignDb the database used to check the foreign key 222 * integrity constraint, or null if no foreign key constraint should be 223 * checked. 224 */ setForeignKeyDatabase(Database foreignDb)225 public void setForeignKeyDatabase(Database foreignDb){ 226 this.foreign = foreignDb.db; 227 } 228 229 /** 230 * Returns the database used to check the foreign key integrity constraint, 231 * or null if no foreign key constraint will be checked. 232 * 233 * @return the foreign key database, or null. 234 * 235 * @see #setForeignKeyDatabase 236 */ getForeignKeyDatabase()237 public Db getForeignKeyDatabase(){ 238 return foreign; 239 } 240 241 /** 242 * Specifies the action taken when a referenced record in the foreign key 243 * database is deleted. 244 * 245 * <p>This property is ignored if the foreign key database property is 246 * null.</p> 247 * 248 * @param action the action taken when a referenced record 249 * in the foreign key database is deleted. 250 * 251 * @see ForeignKeyDeleteAction @see #setForeignKeyDatabase 252 */ setForeignKeyDeleteAction(ForeignKeyDeleteAction action)253 public void setForeignKeyDeleteAction(ForeignKeyDeleteAction action){ 254 this.fkDelAction = action; 255 } 256 257 /** 258 * Returns the action taken when a referenced record in the foreign key 259 * database is deleted. 260 * 261 * @return the action taken when a referenced record in the foreign key 262 * database is deleted. 263 * 264 * @see #setForeignKeyDeleteAction 265 */ getForeignKeyDeleteAction()266 public ForeignKeyDeleteAction getForeignKeyDeleteAction(){ 267 return fkDelAction; 268 } 269 270 /** 271 * Specifies the user-supplied object used for setting single-valued 272 * foreign keys to null. 273 * 274 * <p>This method may <em>not</em> be used along with {@link 275 * #setMultiKeyCreator}. When using a multi-key creator, use {@link 276 * #setForeignMultiKeyNullifier} instead.</p> 277 * 278 * <p>If the foreign key database property is non-null and the foreign key 279 * delete action is <code>NULLIFY</code>, this property is required to be 280 * non-null; otherwise, this property is ignored.</p> 281 * 282 * <p><em>WARNING:</em> Key nullifier instances are shared by multiple 283 * threads and key nullifier methods are called without any special 284 * synchronization. Therefore, key creators must be thread safe. In 285 * general no shared state should be used and any caching of computed 286 * values must be done with proper synchronization.</p> 287 * 288 * @param keyNullifier the user-supplied object used for setting 289 * single-valued foreign keys to null. 290 * 291 * @see ForeignKeyNullifier @see ForeignKeyDeleteAction#NULLIFY @see 292 * #setForeignKeyDatabase 293 */ setForeignKeyNullifier(ForeignKeyNullifier keyNullifier)294 public void setForeignKeyNullifier(ForeignKeyNullifier keyNullifier){ 295 this.keyNullifier = keyNullifier; 296 } 297 298 /** 299 * Returns the user-supplied object used for setting single-valued foreign 300 * keys to null. 301 * 302 * @return the user-supplied object used for setting single-valued foreign 303 * keys to null. 304 * 305 * @see #setForeignKeyNullifier 306 */ getForeignKeyNullifier()307 public ForeignKeyNullifier getForeignKeyNullifier(){ 308 return keyNullifier; 309 } 310 311 /** 312 * Specifies the user-supplied object used for setting multi-valued foreign 313 * keys to null. 314 * 315 * <p>If the foreign key database property is non-null and the foreign key 316 * delete action is <code>NULLIFY</code>, this property is required to be 317 * non-null; otherwise, this property is ignored.</p> 318 * 319 * <p><em>WARNING:</em> Key nullifier instances are shared by multiple 320 * threads and key nullifier methods are called without any special 321 * synchronization. Therefore, key creators must be thread safe. In 322 * general no shared state should be used and any caching of computed 323 * values must be done with proper synchronization.</p> 324 * 325 * @param multiKeyNullifier the user-supplied object used for 326 * setting multi-valued foreign keys to null. 327 * 328 * @see ForeignMultiKeyNullifier @see ForeignKeyDeleteAction#NULLIFY @see 329 * #setForeignKeyDatabase 330 */ setForeignMultiKeyNullifier(ForeignMultiKeyNullifier multiKeyNullifier)331 public void setForeignMultiKeyNullifier(ForeignMultiKeyNullifier multiKeyNullifier){ 332 this.multiKeyNullifier = multiKeyNullifier; 333 } 334 335 /** 336 * Returns the user-supplied object used for setting multi-valued foreign 337 * keys to null. 338 * 339 * @return the user-supplied object used for setting multi-valued foreign 340 * keys to null. 341 * 342 * @see #setForeignMultiKeyNullifier 343 */ getForeignMultiKeyNullifier()344 public ForeignMultiKeyNullifier getForeignMultiKeyNullifier(){ 345 return multiKeyNullifier; 346 } 347 348 /* package */ openSecondaryDatabase(final DbEnv dbenv, final DbTxn txn, final String fileName, final String databaseName, final Db primary)349 Db openSecondaryDatabase(final DbEnv dbenv, 350 final DbTxn txn, 351 final String fileName, 352 final String databaseName, 353 final Db primary) 354 throws DatabaseException, java.io.FileNotFoundException { 355 int associateFlags = 0; 356 int foreignFlags = 0; 357 associateFlags |= allowPopulate ? DbConstants.DB_CREATE : 0; 358 if (getTransactional() && txn == null) 359 associateFlags |= DbConstants.DB_AUTO_COMMIT; 360 if (immutableSecondaryKey) 361 associateFlags |= DbConstants.DB_IMMUTABLE_KEY; 362 363 final Db db = super.openDatabase(dbenv, txn, fileName, databaseName); 364 boolean succeeded = false; 365 try { 366 /* 367 * The multi-key creator must be set before the call to associate 368 * so that we can work out whether the C API callback should be 369 * set or not. 370 */ 371 db.set_secmultikey_create(multiKeyCreator); 372 primary.associate(txn, db, keyCreator, associateFlags); 373 if (foreign != null){ 374 db.set_foreignmultikey_nullifier(multiKeyNullifier); 375 foreign.associate_foreign(db, keyNullifier, foreignFlags | fkDelAction.getId()); 376 } 377 succeeded = true; 378 return db; 379 } finally { 380 if (!succeeded) 381 try { 382 db.close(0); 383 } catch (Throwable t) { 384 // Ignore it -- there is already an exception in flight. 385 } 386 } 387 } 388 389 /* package */ SecondaryConfig(final Db db)390 SecondaryConfig(final Db db) 391 throws DatabaseException { 392 393 super(db); 394 395 final int assocFlags = db.get_assoc_flags(); 396 allowPopulate = (assocFlags & DbConstants.DB_ASSOC_CREATE) != 0; 397 immutableSecondaryKey = (assocFlags & DbConstants.DB_ASSOC_IMMUTABLE_KEY) != 0; 398 keyCreator = db.get_seckey_create(); 399 multiKeyCreator = db.get_secmultikey_create(); 400 } 401 } 402 403