Berkeley DB Reference Guide:
Java API Tutorial - Index

PrevRefNext

Opening foreign key indices

This section builds on the prior section describing secondary key indices to show how to open foreign key indices. A foreign key index is a secondary key index that also provides integrity constraints. When the primary key of a record in one store is embedded in the value of a record in another store, integrity constraints ensure that the record in the first store exists, i.e, that there are no "dangling pointers". In this example the Shipment's PartNumber and SupplierNumber fields will be used as foreign keys.

When a foreign key index is defined, an "on delete" parameter is specified. This parameter determines what action is taken by the Java API when the record is deleted to which a foreign key refers. For example, consider what happens to a Shipment record when a Part or Supplier record that is referred to by that Shipment is deleted. There are three possibilities.


The SampleDatabase class is extended to open the Shipment-by-Part and Shipment-by-Supplier secondary key indices. 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.ForeignKeyIndex;
...
public class SampleDatabase
{
    ...
    private static final String SHIPMENT_PART_INDEX = "shipment_part_index";
    private static final String SHIPMENT_SUPPLIER_INDEX = "shipment_supplier_index";
    ...
    private ForeignKeyIndex shipmentByPartIndex;
    private ForeignKeyIndex shipmentBySupplierIndex;
    ...
    public SampleDatabase(String homeDirectory, boolean runRecovery)
        throws DbException, FileNotFoundException
    {
        ...
        int flags = Db.DB_CREATE | Db.DB_AUTO_COMMIT;
        ...
        KeyExtractor partExtractor = new ShipmentByPartExtractor(
                                                    shipmentKeyFormat,
                                                    shipmentValueFormat,
                                                    partKeyFormat);
        Db partIndexDb = new Db(env, 0);
        partIndexDb.setFlags(Db.DB_DUPSORT);
        partIndexDb.open(null, SHIPMENT_PART_INDEX, null,
                         Db.DB_BTREE, flags, 0);

shipmentByPartIndex = new ForeignKeyIndex(shipmentStore, partIndexDb, partExtractor, partStore, ForeignKeyIndex.ON_DELETE_CASCADE);

KeyExtractor supplierExtractor = new ShipmentBySupplierExtractor( shipmentKeyFormat, shipmentValueFormat, supplierKeyFormat); Db supplierIndexDb = new Db(env, 0); supplierIndexDb.setFlags(Db.DB_DUPSORT); supplierIndexDb.open(null, SHIPMENT_SUPPLIER_INDEX, null, Db.DB_BTREE, flags, 0);

shipmentBySupplierIndex = new ForeignKeyIndex(shipmentStore, supplierIndexDb, supplierExtractor, supplierStore, ForeignKeyIndex.ON_DELETE_CASCADE); } }

Opening a foreign key index requires creating a SerialFormat , a KeyExtractor , a Db and a ForeignKeyIndex . In the example two ForeignKeyIndex objects are created, shipmentByPartIndex and shipmentBySupplierIndex.

If you compare these statements for opening foreign key indices to the statements used in the previous section for opening a secondary index, you'll notice that the only significant differences are the parameters of the ForeignKeyIndex constructor, as compared to the DataIndex constructor. The first three parameters are the same. The last two ForeignKeyIndex parameters are used for enforcing integrity constraints. The fourth parameter is the foreign store that contains the records to which the foreign keys refer. The fifth and last parameter is the "on delete" action.


The application-defined ShipmentByPartExtractor and ShipmentBySupplierExtractor classes are shown below. They were used above to create the partExtractor and supplierExtractor objects.

public class SampleDatabase
{
    private static class ShipmentByPartExtractor
        extends SerialSerialKeyExtractor
    {
        private ShipmentByPartExtractor(SerialFormat primaryKeyFormat,
                                        SerialFormat valueFormat,
                                        SerialFormat indexKeyFormat)
        {
            super(primaryKeyFormat, valueFormat, indexKeyFormat);
        }

public Object extractIndexKey(Object primaryKeyInput, Object valueInput) throws IOException { ShipmentKey shipmentKey = (ShipmentKey) primaryKeyInput; return new PartKey(shipmentKey.getPartNumber()); }

public Object clearIndexKey(Object valueInputOutput) throws IOException { throw new UnsupportedOperationException(); } }

private static class ShipmentBySupplierExtractor extends SerialSerialKeyExtractor { private ShipmentBySupplierExtractor(SerialFormat primaryKeyFormat, SerialFormat valueFormat, SerialFormat indexKeyFormat) { super(primaryKeyFormat, valueFormat, indexKeyFormat); }

public Object extractIndexKey(Object primaryKeyInput, Object valueInput) throws IOException { ShipmentKey shipmentKey = (ShipmentKey) primaryKeyInput; return new SupplierKey(shipmentKey.getSupplierNumber()); }

public Object clearIndexKey(Object valueInputOutput) throws IOException { throw new UnsupportedOperationException(); } } }

The key extractor classes above are almost identical to the one defined in the previous section for use with a secondary index. The index key fields are different, of course, but the interesting difference is that the index keys are extracted from the key, not the value, of the Shipment record. This illustrates that an index key may be derived from the store record key, value, or both.

Note that the SerialSerialKeyExtractor.clearIndexKey methods above always throw an exception, just like in the key extractor class in the previous section. This is because ForeignKeyIndex.ON_DELETE_CLEAR was not used when creating the ForeignKeyIndex objects. If it were used, these methods would need to set the part number and supplier number to null in the Shipment key. But record keys cannot be changed! And in fact, the Shipment key is not passed to the SerialSerialKeyExtractor.clearIndexKey method, only the Shipment value is passed. Therefore, if a foreign index key is derived from the store record key, ForeignKeyIndex.ON_DELETE_CLEAR may not be used.


The following getter methods return the index objects for use by other classes in the example program. The index objects are used to create Java collections for accessing records via their foreign keys.

public class SampleDatabase
{
    ...
    public final ForeignKeyIndex getShipmentByPartIndex()
    {
        return shipmentByPartIndex;
    }

public final ForeignKeyIndex getShipmentBySupplierIndex() { return shipmentBySupplierIndex; } }


ForeignKeyIndex objects, just like DataIndex objects, are closed automatically when their associated DataStore is closed. There is no way to close an index explicitly without closing the store.


PrevRefNext

Copyright (c) 1996-2003 Sleepycat Software, Inc. - All rights reserved.