Composite Keys

BTreeMap can have composite key; an key composed from multiple components. Range query can get all sub-components associated with primary component.

Here is an example; lets associate persons name (composed of surname and firstname) with age. We than find all persons with surname Smith (primary key component) .

    //create new map
    BTreeMap<Tuple2, Integer> persons = db
      .treeMap("persons", Tuple2.class, Integer.class)
      .createOrOpen();

    //insert three persons into map
    persons.put(new Tuple2("Smith","John"), 45);
    persons.put(new Tuple2("Smith","Peter"), 37);
    persons.put(new Tuple2("Doe","John"), 70);

    //now lets get map which contains all Smiths
    NavigableMap<Tuple2,Integer> smiths =
      persons.prefixSubMap(
new Tuple2("Smith", null)  //null indicates wildcard for range query
      );

Example above can be more strongly-typed with wrapper classes and generics. In here we use Surname and Firstname classes.

    //create new map
    BTreeMap<Tuple2<Surname, Firstname>, Integer> persons = db
      .treeMap("persons")
      .keySerializer(new Tuple2Serializer()) //specialized tuple serializer
      .valueSerializer(Serializer.INTEGER)
      .createOrOpen();

    //insert three person into map
    persons.put(new Tuple2(new Surname("Smith"),new Firstname("John")), 45);
    persons.put(new Tuple2(new Surname("Smith"),new Firstname("Peter")), 37);
    persons.put(new Tuple2(new Surname("Doe"),new Firstname("John")), 70);

    //now lets get map which contains all Smiths
    NavigableMap<Tuple2<Surname, Firstname>,Integer> smiths =
      persons.prefixSubMap(
new Tuple2(new Surname("Smith"), null)  //null indicates
      );

Tuples use Comparable interface, all key components (Person and Firstname) should implement it. Other option is to use composite serializer with comparator method. For example to have Tuple2<byte[], byte[]> key we create tuple serializer following way: new Tuple2Serializer(Serializer.BYTE_ARRAY, Serializer.BYTE_ARRAY). Complete example:

    //create new map
    BTreeMap<Tuple2<byte[], byte[]>, Integer> persons = db
      .treeMap("persons")
      .keySerializer(new Tuple2Serializer(Serializer.BYTE_ARRAY, Serializer.BYTE_ARRAY))
      .valueSerializer(Serializer.INTEGER)
      .createOrOpen();

    persons.put(new Tuple2("Smith".getBytes(),"John".getBytes()), 45);

    NavigableMap<Tuple2,Integer> smiths =
      persons.prefixSubMap(
new Tuple2("Smith".getBytes(), null)
      );

Range query

In examples above we used prefixSubMap(new Tuple2("surname", null)) method. It performs range query where second component is replaced by minimal and maximal value. This method BTreeMap class and is not standard Map method, there is NavigableMap.subMap equivalent:

    NavigableMap<Tuple2,Integer> smiths =
      persons.prefixSubMap(new Tuple2("Smith", null));

    // is equivalent to
    smiths = persons.subMap(
      new Tuple2("Smith", Integer.MIN_VALUE), true,
      new Tuple2("Smith", Integer.MAX_VALUE), true
    );

In example above we use Integer because it provides minimal and maximal values. To make this easier TupleSerializer introduces special values for negative and positive infinity, those are even smaller/greater than min/max values. null corresponds to negative infinity, Tuple.HI is positibe infinity.

Those two values are not serializable and can not be stored in Map. But can be used for range query:

    persons.subMap(
      new Tuple2("Smith", null),
      new Tuple2("Smith", Tuple.HI)
    );

Submap returns only single range. It means that we can only query left most components. Common mistake is to put infinity in middle, and expect right components to be included. Tuple in example bellow has three components (surname, firstname, age). But we can not just query Surname and Age, because age is left most and it will be overriden by infinity component before it:

    //WRONG!! null is in middle position
    persons.prefixSubMap(new Tuple3("Smith",null,11));

    //same but submap
    //WRONG!! infinity is in middle
    persons.subMap(
      new Tuple3("Smith", null,     11),
      new Tuple3("Smith", Tuple.HI, 11)
    );

Fixed size array tuples

Tuples can be replaced by array. In this case we do not have generics and will have to do lot of casting. Here is an example with surname/firstname. For key serializer we use new SerializerArrayTuple(tupleSize). null and Tuple.HI will not work, but we can use shorter array for prefix:

    //create new map
    BTreeMap<Object[], Integer> persons = db
      .treeMap("persons", new SerializerArrayTuple(2), Serializer.INTEGER)
      .createOrOpen();

    //insert three person into map
    persons.put(new Object[]{"Smith", "John"}, 45);
    persons.put(new Object[]{"Smith", "Peter"}, 37);
    persons.put(new Object[]{"Doe", "John"}, 70);

    //now lets get map which contains all Smiths
    NavigableMap<Object[],Integer> smiths =
      persons.prefixSubMap(
new Object[]{"Smith"}
      );

//TODO null is positive infinity, Tuple.HI does not exist

Variable size array tuples

MapDB also has generic array serializer which can be used for tuples. In this case prefixSubmap will not work. But we can use submap:

    //create new map
    BTreeMap<Object[], Integer> persons = db
      .treeMap("persons", new SerializerArrayDelta(), Serializer.INTEGER)
      .createOrOpen();

    //insert three persons into map
    persons.put(new Object[]{"Smith", "John"}, 45);
    persons.put(new Object[]{"Smith", "Peter"}, 37);
    persons.put(new Object[]{"Doe", "John"}, 70);


    NavigableMap<Object[],Integer> smiths = persons.subMap(
      new Object[]{"Smith"}, //lower bound
      new Object[]{"Smith", null} //upper bound, null is positive infinity in this serializer
    );

Delta compression

All three tuples type use delta compression.

TODO delta compression

results matching ""

    No results matching ""