|
Secondary key indices are used to access a store by a key other than the primary key. Recall that the Supplier Number field is the primary key of the Supplier store. In this section, the Supplier City field will be used as a secondary lookup key. Given a city value, we would like to be able to find the Suppliers in that city. Note that more than one Supplier may be in the same city.
Both stores and indices are database files containing key-value records. The key of an index record is the secondary key, and its value is the key of the associated record in the store. When lookups by secondary key are performed, the associated record in the store is transparently retrieved by its primary key and returned to the caller.
Secondary indices are maintained automatically when index key fields (the City field in this case) are added, modified or removed in the records of the store. However, the application must implement a KeyExtractor that extracts the index key from the store record.
It is useful to contrast opening an index with opening a store (as described earlier in Opening and closing database stores).
The SampleDatabase class is extended to open the Supplier-by-City secondary key index. The following additional imports and class members are needed.
import com.sleepycat.bdb.bind.KeyExtractor; import com.sleepycat.bdb.bind.serial.SerialSerialKeyExtractor; import com.sleepycat.bdb.DataIndex; ... public class SampleDatabase { ... private static final String SUPPLIER_CITY_INDEX = "supplier_city_index"; ... private DataIndex supplierByCityIndex; private SerialFormat cityKeyFormat; ... public SampleDatabase(String homeDirectory, boolean runRecovery) throws DbException, FileNotFoundException { ... int flags = Db.DB_CREATE | Db.DB_AUTO_COMMIT; ... cityKeyFormat = new SerialFormat(javaCatalog, String.class);KeyExtractor cityExtractor = new SupplierByCityExtractor( supplierKeyFormat, supplierValueFormat, cityKeyFormat); Db cityIndexDb = new Db(env, 0); cityIndexDb.setFlags(Db.DB_DUPSORT); cityIndexDb.open(null, SUPPLIER_CITY_INDEX, null, Db.DB_BTREE, flags, 0);
supplierByCityIndex = new DataIndex(supplierStore, cityIndexDb, cityKeyFormat, cityExtractor); } }
Opening a secondary key index requires creating a SerialFormat , a KeyExtractor , a Db and a DataIndex .
Like the formats created earlier for keys and values, the cityKeyFormat is a SerialFormat . Unlike the formats created earlier, it is an example of creating a format for a Java primitive class, String , instead of an application-defined class. Any serializable class may be used.
The cityExtractor is an instance of the application class SupplierByCityExtractor. This class implements the KeyExtractor interface and will be defined below. Recall that supplierKeyFormat and supplierValueFormat, two of the parameters to its constructor, were created earlier in Opening and closing database stores.
The cityIndexDb Db object is created and opened next. If you compare these statements for opening an index to the statements we used previously for opening a store, you'll notice only one difference: For an index the Db.setFlags method is called, while for a store it is not. The Db.DB_DUPSORT flag is specified to allow duplicate index keys. This is how more than one Supplier may be in the same City. If this flag is not specified, the default is that the index keys of all records must be unique.
For a store, duplicate keys are not normally used since a store with duplicate keys may not have any associated indices. If store keys are not unique, there is no way for a secondary key to reference a specific record in the store.
Note that Db.DB_DUPSORT and not Db.DB_DUP was specified. Sorted duplicates are always used for indices rather than unsorted duplicates, since sorting enables optimized equality joins.
Finally, the supplierByCityIndex DataIndex object is created from the supplier store, index Db, index key format, and key extractor. How to use the index to access records will be shown in a later section.
The application-defined SupplierByCityExtractor class is shown below. It was used above to create the cityExtractor object.
public class SampleDatabase { private static class SupplierByCityExtractor extends SerialSerialKeyExtractor { private SupplierByCityExtractor(SerialFormat primaryKeyFormat, SerialFormat valueFormat, SerialFormat indexKeyFormat) { super(primaryKeyFormat, valueFormat, indexKeyFormat); }public Object extractIndexKey(Object primaryKeyInput, Object valueInput) throws IOException { SupplierValue supplierValue = (SupplierValue) valueInput; return supplierValue.getCity(); }
public Object clearIndexKey(Object valueInputOutput) throws IOException { throw new UnsupportedOperationException(); } } }
In general, a key extractor class must implement the KeyExtractor interface. This interface has methods that operate on the record data as raw bytes. In practice, it is easiest to use an abstract base class that performs the conversion of record data to and from the format defined for the store's key and value. The base class implements the KeyExtractor interface and has abstract methods that must be implemented in turn by the application.
In this example the SerialSerialKeyExtractor base class is used because the store record uses the serial format for both its key and its value. The abstract methods of this class have key and value parameters of type Object which are automatically converted to and from the raw record data by the base class.
To perform the conversions properly, the key extractor must be aware of all three formats involved: the key format of the store record, the value format of the store record, and the key format of the index record. The SerialSerialKeyExtractor constructor is given these three formats as parameters.
The SerialSerialKeyExtractor.extractIndexKey method is given the key and value of the store record as parameters, and it returns the key of the index record. In this example, the index key is a field in the store record value. Since the record value is known to be a SupplierValue object, it is cast to that class and the city field is returned.
Note that the primaryKeyInput parameter is not used in the example. This parameter is needed only when an index key is derived from the key of the store record. Normally an index key is derived only from the store record value, but it may be derived from the key, value or both.
The SerialSerialKeyExtractor.clearIndexKey method is implemented only for foreign keys under certain conditions and will be described in the next section. In the SupplierByCityExtractor class this method always throws an exception since it should never be called.
The following getter methods return the index key format and index object for use by other classes in the example program. The format is used for creating bindings to the index key. The index object is used to create Java collections for accessing records via their secondary keys.
public class SampleDatabase { ... public final SerialFormat getCityKeyFormat() { return cityKeyFormat; }public final DataIndex getSupplierByCityIndex() { return supplierByCityIndex; } }
If you're wondering why this section does not include an example of how to close a DataIndex , that is because indices are closed automatically when their associated DataStore is closed. There is no way to close an index explicitly without closing the store.
Copyright (c) 1996-2003 Sleepycat Software, Inc. - All rights reserved.