1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: SecondaryConfig.java,v 12.12 2008/03/10 13:22:16 mjc Exp $
7 */
8
9package com.sleepycat.db;
10
11import com.sleepycat.db.internal.Db;
12import com.sleepycat.db.internal.DbConstants;
13import com.sleepycat.db.internal.DbEnv;
14import com.sleepycat.db.internal.DbTxn;
15
16/**
17The configuration properties of a <code>SecondaryDatabase</code> extend
18those of a primary <code>Database</code>.
19The secondary database configuration is specified when calling {@link
20Environment#openSecondaryDatabase Environment.openSecondaryDatabase}.
21<p>
22To create a configuration object with default attributes:
23<pre>
24    SecondaryConfig config = new SecondaryConfig();
25</pre>
26To 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<p>
34<hr>
35<p>
36NOTE: There are two situations where the use of secondary databases without
37transactions requires special consideration.  When using a transactional
38database or when doing read operations only, this note does not apply.
39<ul>
40<li>If secondary is configured to not allow duplicates, when the secondary
41is being updated it is possible that an error will occur when the secondary
42key value in a record being added is already present in the database.  A
43<code>DatabaseException</code> will be thrown in this situation.</li>
44<li>If a foreign key constraint is configured with the delete action
45<code>ABORT</code> (the default setting), a <code>DatabaseException</code>
46will be thrown if an attempt is made to delete a referenced foreign
47key.</li>
48</ul>
49In both cases, the operation will be partially complete because the primary
50database record will have already been updated or deleted.  In the presence
51of transactions, the exception will cause the transaction to abort.  Without
52transactions, it is the responsibility of the caller to handle the results
53of the incomplete update or to take steps to prevent this situation from
54happening in the first place.
55<p>
56</hr>
57<p>
58@see Environment#openSecondaryDatabase Environment.openSecondaryDatabase
59@see SecondaryDatabase
60*/
61public class SecondaryConfig extends DatabaseConfig implements Cloneable {
62    /*
63     * For internal use, to allow null as a valid value for
64     * the config parameter.
65     */
66    public static final SecondaryConfig DEFAULT = new SecondaryConfig();
67
68    /* package */
69    static SecondaryConfig checkNull(SecondaryConfig config) {
70        return (config == null) ? DEFAULT : config;
71    }
72
73    private boolean allowPopulate;
74    private boolean immutableSecondaryKey;
75    private Db foreign;
76    private ForeignKeyDeleteAction fkDelAction;
77    private ForeignKeyNullifier keyNullifier;
78    private ForeignMultiKeyNullifier multiKeyNullifier;
79    private SecondaryKeyCreator keyCreator;
80    private SecondaryMultiKeyCreator multiKeyCreator;
81
82    /**
83    Creates an instance with the system's default settings.
84    */
85    public SecondaryConfig() {
86    }
87
88    /**
89    Specifies whether automatic population of the secondary is allowed.
90    <p>
91    If automatic population is allowed, when the secondary database is
92    opened it is checked to see if it is empty.  If it is empty, the
93    primary database is read in its entirety and keys are added to the
94    secondary database using the information read from the primary.
95    <p>
96    If this property is set to true and the database is transactional, the
97    population of the secondary will be done within the explicit or auto-commit
98    transaction that is used to open the database.
99    <p>
100    @param allowPopulate whether automatic population of the secondary is
101    allowed.
102    */
103    public void setAllowPopulate(final boolean allowPopulate) {
104        this.allowPopulate = allowPopulate;
105    }
106
107    /**
108    Returns whether automatic population of the secondary is allowed.  If
109    {@link #setAllowPopulate} has not been called, this method returns
110    false.
111    <p>
112    @return whether automatic population of the secondary is allowed.
113    <p>
114    @see #setAllowPopulate
115    */
116    public boolean getAllowPopulate() {
117        return allowPopulate;
118    }
119
120    /**
121    Specifies whether the secondary key is immutable.
122    <p>
123    Specifying that a secondary key is immutable can be used to optimize
124    updates when the secondary key in a primary record will never be changed
125    after that primary record is inserted.  For immutable secondary keys, a
126    best effort is made to avoid calling
127    <code>SecondaryKeyCreator.createSecondaryKey</code> when a primary record
128    is updated.  This optimization may reduce the overhead of an update
129    operation significantly if the <code>createSecondaryKey</code> operation is
130    expensive.
131    <p>
132    Be sure to set this property to true only if the secondary key in the
133    primary record is never changed.  If this rule is violated, the secondary
134    index will become corrupted, that is, it will become out of sync with the
135    primary.
136    <p>
137    @param immutableSecondaryKey whether the secondary key is immutable.
138    */
139    public void setImmutableSecondaryKey(final boolean immutableSecondaryKey) {
140        this.immutableSecondaryKey = immutableSecondaryKey;
141    }
142
143    /**
144    Returns whether the secondary key is immutable.  If
145    {@link #setImmutableSecondaryKey} has not been called, this method returns
146    false.
147    <p>
148    @return whether the secondary key is immutable.
149    <p>
150    @see #setImmutableSecondaryKey
151    */
152    public boolean getImmutableSecondaryKey() {
153        return immutableSecondaryKey;
154    }
155
156    /**
157    Specifies the user-supplied object used for creating single-valued
158    secondary keys.
159    <p>
160    Unless the primary database is read-only, a key creator is required
161    when opening a secondary database.  Either a KeyCreator or MultiKeyCreator
162    must be specified, but both may not be specified.
163    <p>
164    Unless the primary database is read-only, a key creator is required
165    when opening a secondary database.
166    <p>
167    @param keyCreator the user-supplied object used for creating single-valued
168    secondary keys.
169    */
170    public void setKeyCreator(final SecondaryKeyCreator keyCreator) {
171        this.keyCreator = keyCreator;
172    }
173
174    /**
175    Returns the user-supplied object used for creating single-valued secondary
176    keys.
177    <p>
178    @return the user-supplied object used for creating single-valued secondary
179    keys.
180    <p>
181    @see #setKeyCreator
182    */
183    public SecondaryKeyCreator getKeyCreator() {
184        return keyCreator;
185    }
186
187    /**
188    Specifies the user-supplied object used for creating multi-valued
189    secondary keys.
190    <p>
191    Unless the primary database is read-only, a key creator is required
192    when opening a secondary database.  Either a KeyCreator or MultiKeyCreator
193    must be specified, but both may not be specified.
194    <p>
195    @param multiKeyCreator the user-supplied object used for creating multi-valued
196    secondary keys.
197    */
198    public void setMultiKeyCreator(final SecondaryMultiKeyCreator multiKeyCreator) {
199        this.multiKeyCreator = multiKeyCreator;
200    }
201
202    /**
203    Returns the user-supplied object used for creating multi-valued secondary
204    keys.
205    <p>
206    @return the user-supplied object used for creating multi-valued secondary
207    keys.
208    <p>
209    @see #setKeyCreator
210    */
211    public SecondaryMultiKeyCreator getMultiKeyCreator() {
212        return multiKeyCreator;
213    }
214
215    public void setForeignKeyDatabase(Database foreignDb){
216	this.foreign = foreignDb.db;
217    }
218
219    public Db getForeignKeyDatabase(){
220	return foreign;
221    }
222
223    public void setForeignKeyDeleteAction(ForeignKeyDeleteAction action){
224	this.fkDelAction = action;
225    }
226
227    public ForeignKeyDeleteAction getForeignKeyDeleteAction(){
228	return fkDelAction;
229    }
230
231    public void setForeignKeyNullifier(ForeignKeyNullifier keyNullifier){
232	this.keyNullifier = keyNullifier;
233    }
234
235    public ForeignKeyNullifier getForeignKeyNullifier(){
236	return keyNullifier;
237    }
238
239    public void setForeignMultiKeyNullifier(ForeignMultiKeyNullifier multiKeyNullifier){
240	this.multiKeyNullifier = multiKeyNullifier;
241    }
242
243    public ForeignMultiKeyNullifier getForeignMultiKeyNullifier(){
244	return multiKeyNullifier;
245    }
246
247    /* package */
248    Db openSecondaryDatabase(final DbEnv dbenv,
249                             final DbTxn txn,
250                             final String fileName,
251                             final String databaseName,
252                             final Db primary)
253        throws DatabaseException, java.io.FileNotFoundException {
254	int associateFlags = 0;
255	int foreignFlags = 0;
256        associateFlags |= allowPopulate ? DbConstants.DB_CREATE : 0;
257        if (getTransactional() && txn == null)
258            associateFlags |= DbConstants.DB_AUTO_COMMIT;
259        if (immutableSecondaryKey)
260            associateFlags |= DbConstants.DB_IMMUTABLE_KEY;
261
262        final Db db = super.openDatabase(dbenv, txn, fileName, databaseName);
263        boolean succeeded = false;
264        try {
265            /*
266             * The multi-key creator must be set before the call to associate
267             * so that we can work out whether the C API callback should be
268             * set or not.
269             */
270            db.set_secmultikey_create(multiKeyCreator);
271            primary.associate(txn, db, keyCreator, associateFlags);
272 	    if (foreign != null){
273		db.set_foreignmultikey_nullifier(multiKeyNullifier);
274		foreign.associate_foreign(db, keyNullifier, foreignFlags | fkDelAction.getId());
275	    }
276            succeeded = true;
277            return db;
278        } finally {
279            if (!succeeded)
280                try {
281                    db.close(0);
282                } catch (Throwable t) {
283                    // Ignore it -- there is already an exception in flight.
284                }
285        }
286    }
287
288    /* package */
289    SecondaryConfig(final Db db)
290        throws DatabaseException {
291
292        super(db);
293
294        // XXX: There is no way to find out what flags were passed to associate.
295        allowPopulate = false;
296        immutableSecondaryKey = false;
297        keyCreator = db.get_seckey_create();
298        multiKeyCreator = db.get_secmultikey_create();
299    }
300}
301
302