Skip to content

Writing Data#

Concourse provides several methods for writing data to records. All write operations are atomic by default — when operating outside of an explicit transaction, each individual write is automatically committed.

Adding Values#

The add method appends a value to a field if and only if that value does not already exist in the field. A field in Concourse can hold multiple distinct values simultaneously, so add is the primary way to build up multi-valued fields.

Add to a New Record#

To add a value and automatically create a new record, omit the record parameter. Concourse assigns a unique record ID and returns it.

1
2
// Java
long record = concourse.add("name", "Jeff Nelson");
1
2
// CaSH
add "name", "Jeff Nelson"

Add to an Existing Record#

To add a value to a specific record, pass the record ID. The method returns true if the value was added, or false if it already exists.

1
2
// Java
boolean added = concourse.add("name", "Jeff Nelson", 1);
1
2
// CaSH
add "name", "Jeff Nelson", 1

Add to Multiple Records#

You can add a value across several records at once. The method returns a Map associating each record ID to a boolean indicating whether the add succeeded for that record.

1
2
3
4
// Java
Map<Long, Boolean> result = concourse.add(
    "department", "Engineering",
    Lists.newArrayList(1L, 2L, 3L));

Setting Values#

The set method atomically removes all existing values for a key in a record and then adds a new value. Use set when a field should contain exactly one value.

1
2
// Java
concourse.set("email", "jeff@cinchapi.com", 1);
1
2
// CaSH
set "email", "jeff@cinchapi.com", 1

Set Across Multiple Records#

You can set a value for a key in several records at once.

1
2
3
// Java
concourse.set("status", "active",
    Lists.newArrayList(1L, 2L, 3L));

Removing Values#

The remove method removes a specific value from a field if it currently exists. Unlike clear, which removes all values, remove targets a single value.

1
2
// Java
boolean removed = concourse.remove("tag", "beta", 1);
1
2
// CaSH
remove "tag", "beta", 1

Remove from Multiple Records#

1
2
3
// Java
Map<Long, Boolean> result = concourse.remove(
    "tag", "beta", Lists.newArrayList(1L, 2L, 3L));

Clearing Values#

The clear method atomically removes all values stored for one or more keys in one or more records. Use this to completely wipe a field or an entire record.

Clear a Single Key#

1
2
// Java
concourse.clear("name", 1);
1
2
// CaSH
clear "name", 1

Clear Multiple Keys#

1
2
3
// Java
concourse.clear(
    Lists.newArrayList("name", "email"), 1);

Clear an Entire Record#

Omit the key parameter to remove all values for every key in the record.

1
2
// Java
concourse.clear(1);

Clear Across Multiple Records#

All clear variants support collections of records as well.

1
2
3
4
// Java
concourse.clear("name",
    Lists.newArrayList(1L, 2L, 3L));
concourse.clear(Lists.newArrayList(1L, 2L, 3L));

Inserting Data#

The insert method atomically writes a collection of key/value associations into one or more records. This is the most efficient way to load structured data into Concourse.

Insert from JSON#

Pass a JSON string containing a top-level object to insert a single record, or a top-level array of objects to insert multiple records. The method returns the set of record IDs where the data was inserted.

1
2
3
4
5
// Java
Set<Long> records = concourse.insert(
    "{\"name\": \"Jeff Nelson\", "
    + "\"company\": \"Cinchapi\", "
    + "\"age\": 30}");
1
2
// CaSH
insert({"name": "Jeff Nelson", "company": "Cinchapi", "age": 30})

To insert multiple records at once, pass a JSON array:

1
2
3
// Java
Set<Long> records = concourse.insert(
    "[{\"name\": \"Alice\"}, {\"name\": \"Bob\"}]");

Insert from a Map#

1
2
3
4
5
// Java
Map<String, Object> data = Maps.newLinkedHashMap();
data.put("name", "Jeff Nelson");
data.put("age", 30);
long record = concourse.insert(data);

Insert from a Multimap#

Use a Multimap when a key should have multiple values.

1
2
3
4
5
6
7
// Java
Multimap<String, Object> data =
    LinkedHashMultimap.create();
data.put("name", "Jeff Nelson");
data.put("tag", "founder");
data.put("tag", "engineer");
long record = concourse.insert(data);

Insert into Existing Records#

You can insert data into specific existing records. The insert will fail for a given record if any of the key/value associations already exist in that record.

1
2
3
4
5
6
7
// Java
boolean success = concourse.insert(
    "{\"department\": \"Engineering\"}", 1);

Map<Long, Boolean> results = concourse.insert(
    "{\"department\": \"Engineering\"}",
    Lists.newArrayList(1L, 2L));

When inserting JSON or Map data, you can include resolvable link instructions that automatically create links to all records matching a criteria. Use Link.toWhere(criteria) to generate the instruction.

1
2
3
4
5
6
// Java
Map<String, Object> data = Maps.newLinkedHashMap();
data.put("name", "Engineering");
data.put("members",
    Link.toWhere("department = Engineering"));
long record = concourse.insert(data);

Do not add resolvable links directly

Resolvable links should only be used within insert operations (JSON, Map, or Multimap). Do not use add to write resolvable links because the evaluation and linking would not be atomic.

Linking Records#

The link method creates a directed pointer from one record to another. Links are a special data type that enables Concourse’s graph features.

1
2
3
// Java
// Link record 1 to record 2 via the "friends" key
boolean linked = concourse.link("friends", 2, 1);
1
2
// CaSH
link "friends", 2, 1
1
2
3
// Java
Map<Long, Boolean> result = concourse.link(
    "friends", Lists.newArrayList(2L, 3L, 4L), 1);

Use unlink to remove a link between two records.

1
2
// Java
boolean unlinked = concourse.unlink("friends", 2, 1);
1
2
// CaSH
unlink "friends", 2, 1

Atomic Write Operations#

Concourse provides several compound write operations that execute atomically without requiring an explicit transaction.

verifyAndSwap#

Atomically replace a value if and only if the expected value is currently stored in the field. This is the classic compare-and-swap (CAS) operation.

1
2
3
4
// Java
boolean swapped = concourse.verifyAndSwap(
    "balance", 100, 1, 90);
// Only succeeds if "balance" in record 1 is currently 100
1
2
// CaSH
verifyAndSwap "balance", 100, 1, 90

verifyOrSet#

Atomically verify that a key contains exactly one specific value in a record, or set it as such. After this method returns, the field is guaranteed to contain only the specified value.

Unlike set, verifyOrSet does not create new revisions unless necessary. If the field already contains exactly the specified value and nothing else, no changes are made.

1
2
// Java
concourse.verifyOrSet("status", "active", 1);
1
2
// CaSH
verifyOrSet "status", "active", 1

reconcile#

Make the necessary changes so that a field contains exactly the specified collection of values. Concourse computes the minimal diff and only applies the adds and removes needed.

1
2
3
// Java
concourse.reconcile("tags", 1,
    Lists.newArrayList("v1", "stable", "release"));

Simulating a Unique Index#

findOrAdd#

Atomically find the unique record where a key equals a value, or add the key/value pair to a new record if none exists. Throws a DuplicateEntryException if more than one record matches.

1
2
3
4
// Java
long record = concourse.findOrAdd("email",
    "jeff@cinchapi.com");
// Returns the existing record, or creates a new one

findOrInsert#

Atomically find the unique record matching a criteria, or insert data into a new record if none exists. This is the most flexible way to implement upsert semantics.

1
2
3
4
5
// Java
long record = concourse.findOrInsert(
    "email = jeff@cinchapi.com",
    "{\"email\": \"jeff@cinchapi.com\", "
    + "\"name\": \"Jeff Nelson\"}");

You can also use the Criteria builder:

1
2
3
4
5
6
7
// Java
long record = concourse.findOrInsert(
    Criteria.where().key("email")
        .operator(Operator.EQUALS)
        .value("jeff@cinchapi.com"),
    "{\"email\": \"jeff@cinchapi.com\", "
    + "\"name\": \"Jeff Nelson\"}");

Like insert, the data parameter can be a JSON string, Map, or Multimap.

DuplicateEntryException

Both findOrAdd and findOrInsert throw a DuplicateEntryException if more than one record matches the condition. This ensures the uniqueness guarantee is enforced. Design your criteria to match at most one record.

Consolidating Records#

The consolidate method merges the data from two or more records into a single record. After consolidation, the surviving record contains the union of all values from the merged records, and the other records are cleared.

1
2
3
// Java
concourse.consolidate(1, 2, 3);
// All data from records 2 and 3 is merged into record 1