Basic ItemSpace Example

Here is a ‘hello world’ code sample to show the fast, efficient, simple, lowest-level API. There is also a higher-level ‘Map-based’ API, and all data can be printed and parsed as JSON.

/*
Copyright (c) 2019 Boiler Bay Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package com.infinitydb.examples;

import java.io.IOException;

import com.infinitydb.BinaryLongObjectInputStream;
import com.infinitydb.BinaryLongObjectOutputStream;
import com.infinitydb.Cu;
import com.infinitydb.InfinityDB;
import com.infinitydb.ItemSpace;
import com.infinitydb.ItemSubspace;

/**
 * Demonstrate the lowest-level InfinityDB operations, based on an 'ItemSpace'
 * data model. An ItemSpace is extremely simple, providing only a few simple
 * basic methods. The result of the simplicity is performance, space efficiency,
 * and the flexibility to represent a wide range of superimposed data models.
 * 
 * An ItemSpace is logically only a sorted set of 'Items', each Item being a
 * series of 1 to 1665 chars. The ItemSpace has no other state. An Item that is
 * a prefix of another comes first.
 * 
 * Items are stored, retrieved, and manipulated in a 'Cu' cursor, which contains
 * exactly one Item. It has no other state. (A Cu actually contains only a
 * private single char[] array 1665 chars long, with a current length from 0 to
 * 1665.)
 * 
 * At the next higher logical level, the Items represent a sequence of primitive
 * values of 12 types. All of the Java primitives such as a long, double, String
 * and so on have corresponding InfinityDB types, but there are some other
 * non-Java ones. Each primitive or other type in an Item is called a
 * 'component'. Each Item contains a variable number of components of any
 * mixture of types.
 * 
 * Each component type has a permanent standard efficient variable-length
 * self-delimiting representation in the Item. Applications never need to
 * understand the representations. Items are stored variable-length with common
 * prefixes between adjacent Items compressed out in storage.
 * 
 * Primitives include long, String, double, float, boolean, small byte[], small
 * char[], ByteString, EntityClass, Attribute, Index, and the Infinity constant.
 * The small arrays sort with length being most significant, but ByteStrings are
 * packed binary that sort like String. EntityClass and Attribute are for
 * optional 'metadata' in the Items to help them describe themselves. Index is
 * for huge arrays. The small arrays are used with Index to represent BLOBs and
 * CLOBs. Index plus Strings are good for representing texts.
 * 
 * There is also a Map-based API. Any ItemSpace can be wrapped with an
 * InfinityDBMap to provide an extended
 * java.util.concurrent.ConcurrentNavigableMap view. InfinityDB is actually more
 * than an ItemSpace, providing transactions and so on.
 */
public class BasicItemSpaceExample {

    // This is for the data blocks in memory.
    static long MAX_CACHE_SIZE = 100_000_000;

    public static void main(String... args) {

        // Allocate a cursor to contain one Item.
        try (Cu cu = Cu.alloc()) {

            // Create an ItemSpace. Many kinds are possible, some 'wrapping'
            // others. Also a 'Map' API can be used.
            InfinityDB db = InfinityDB.createTemporary(MAX_CACHE_SIZE);

            helloWorld(db, cu);
            twoComponentItems(db, cu);
            setsOfItemsSharingAPrefix(db, cu);
            itemSubspace(db, cu);
            blob(db, cu);

            // Optionally make all changes durable, or rollBack().
            db.commit();

        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /*
     * Minimal ItemSpace usage. An InfinityDB instance is an example of an
     * ItemSpace.
     */
    static void helloWorld(ItemSpace db, Cu cu) throws IOException {

        // Mutations

        // Create a minimal Item in cu. This is a single String component.
        cu.clear().append("hello world");

        // Put the Item in the database
        db.insert(cu);
        // Remove from the database.
        db.delete(cu);
        // restore Item to the database
        db.insert(cu);
        // Not shown: 'deleteSubspace' and 'update'.

        // Retrievals.
        // You can use >, <, >=, or <= for retrievals.

        // Restore the cu to emptiness. This is before the first Item.
        cu.clear();
        // The nearest greater Item following the zero-length Item is the
        // one we put in.
        db.next(cu);
        System.out.println("Nearest greater Item = " + cu);
        // Go backwards from after the last possible Item. This is '<'. cu.clear().appendInfinity(); // The Item before the infinite Item is the one we put in. db.previous(cu); System.out.println("Nearest lesser Item = " + cu); // Use '>=' with cu already holding the same component - nothing
        // happens because it is equal.
        db.first(cu);
        System.out.println("Nearest greater or equal Item = " + cu);
        // Use '<=' with cu already holding the same component - nothing
        // happens because it is equal
        db.last(cu);
        System.out.println("Nearest lesser or equal Item = " + cu);

        // Show the Item's standard encoded form
        dumpItemInHex(cu);
    }

    static void twoComponentItems(ItemSpace db, Cu cu) throws IOException {

        // Empty the database
        db.clear();

        // The Cu methods chain.
        cu.clear().append("hello world").append(1);
        // Now the Cu contains two components: a string and a long
        // It prints like "hello world" 1.
        System.out.println("two component Item = " + cu);
        db.insert(cu);

        // Get the offset in the Item after the "hello world"
        int off = cu.skipComponent(0);
        // Off is the length of the "hello world" component.
        // Truncate cu after that so we can show retrieving the 1.
        cu.setLength(off);
        // The off is a 'protected prefix length'. The cu before that is not
        // modified
        if (db.next(cu, off)) {
            // true means we found a greater Item having that prefix.
            System.out.println("two component Item retrieved = " + cu);
            // Print the 1 by itself. We get it as a 'component' without knowing
            // its type,
            // so it comes out as a Long.
            System.out.println(
                    "the retrieved long by itself = " + cu.componentAt(off));
            // If we expect a long, this will fail if not
            System.out.println(
                    "the retrieved long by itself = " + cu.longAt(off));
        }
    }

    static void setsOfItemsSharingAPrefix(ItemSpace db, Cu cu)
            throws IOException {

        // Create a database with two Items with a common prefix

        db.clear();
        cu.clear().append("hello world").append("suffix one");
        db.insert(cu);
        cu.clear().append("hello world").append("suffix two");
        db.insert(cu);

        // Iterate the suffixes

        // Get the length of the prefix over which to iterate. Often this is
        // already available.
        int off = cu.skipComponent(0);
        // Truncate to the prefix over which to iterate. Often this is already
        // available.
        cu.setLength(off);
        // The off is a 'protected prefix', so the "hello world" prefix doesn't
        // change.
        // This prints "suffix one", then "suffix two"
        while (db.next(cu, off)) {
            // suffixes are in sorted order
            System.out.println("Two suffixes = " + cu.componentAt(off));
        }

        // Add an Item
        cu.clear().append("hello world").append("suffix three");
        db.insert(cu);

        // Three suffixes are printed
        for (cu.setLength(off); db.next(cu, off);) {
            System.out.println("Three suffixes = " + cu.componentAt(off));
        }

        // Iterate starting after "suffix one"
        cu.setLength(off).append("suffix one");
        while (db.next(cu, off)) {
            System.out.println("After suffix one = " + cu.componentAt(off));
        }

        // Delete all of the suffixes of the "hello world" component at once,
        // quickly
        cu.setLength(off);
        db.deleteSubspace(cu);
        // Show results. None are printed
        // Iterate the suffixes. The off is a 'protected prefix', so the
        // "hello world" prefix doesn't change
        for (cu.setLength(off); db.next(cu, off);) {
            System.out.println("No suffixes = " + cu.componentAt(off));
        }
    }

    /**
     * An example of an ItemSpace that 'wraps' another. There are also
     * DeltaItemSpace, VolatileItemSpace, IndirectItemSpace and more plus
     * ItemSpaceScanner.
     */
    static void itemSubspace(ItemSpace db, Cu cu) throws IOException {

        // Inserting Object[] allocates a Cu and appends the elements as
        // components like a 'tuple'
        db.clear();
        db.insert(new Object[] { "locations", "Santa Cruz", "CA", "USA" });
        db.insert(new Object[] { "locations", "San Jose", "CA", "USA" });
        db.insert(new Object[] { "locations", "San Francisco", "CA", "USA" });

        // Wrap the given ItemSpace such that only some suffixes are visible
        ItemSpace locationsDb = new ItemSubspace(db, "locations");
        // Print the suffixes alone. Inserting or deleting prepends the
        // prefixes.
        printItems(locationsDb, "locations = ");
    }

    // Just iterate an ItemSpace and print it.
    static void printItems(ItemSpace db, String message) throws IOException {
        try (Cu cu = Cu.alloc()) {
            while (db.next(cu)) {
                System.out.println(message + cu);
            }
        }
    }

    /**
     * A Binary Long OBject or 'BLOB' or a character long object or 'CLOB' can
     * be put under any prefix. There is no size limit. It is stored using an
     * 'Index' component and a byte[] component after that in each Item. An
     * 'Index' component is basically a special kind of long.
     */
    static void blob(ItemSpace db, Cu cu) throws IOException {

        db.clear();
        // We can put a blob anywhere at all with a suitable prefix.
        cu.clear().append("the blob's prefix");
        BinaryLongObjectOutputStream blobOut =
                new BinaryLongObjectOutputStream(db, cu);
        blobOut.write(42);
        blobOut.close();
        BinaryLongObjectInputStream blobIn =
                new BinaryLongObjectInputStream(db, cu);
        System.out.println("blob result = " + blobIn.read());
    }

    /**
     * Show the Item in its permanent standard encoded form. This is never
     * needed in practice. The component encodings are variable-length,
     * self-typed, and self-delimiting. Byte, short, int and long all are stored
     * as longs, but small longs are compressed.
     */
    static void dumpItemInHex(Cu cu) {
        System.out
                .println("The Item in internal encoded form (never needed) = ");
        // The cu.charAt(i) is special - it has no primitive type but instead
        // prints
        // the char at the offset. Use Strings instead for storing chars as
        // primitives.
        for (int i = 0; i < cu.length(); i++) {
            System.out.print(Integer.toHexString(cu.charAt(i)) + " ");
        }
        System.out.println();
    }
}