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