Core Concepts of TCStore
TCStore organizes its data into collections, so-called datasets.
Each Dataset is comprised of zero or more records.
Each Record has a key, unique within the dataset, and zero or more cells.
Each Cell has a name, unique within the record; a declared type; and a non-null value.
While records within a dataset must have a uniform key type, records are not required to have uniform content — each record may be comprised of cells having different names and/or different types.
Each record and each cell is self-describing and is understood by the storage engine.
TCStore Data Storage Model - typed data.
Since records and cells are self-describing, records and cells can be manipulated efficiently:
retrieved: no need to retrieve the entire
Record, when all you want is a single
Cell;
mutated: cell values can be directly changed such as incrementing an
Integer cell;
searched: nearly, every cell type is searchable. Indexes may be added to improve search patterns;
computed: computation, mutative or not, to be executed against the records of a dataset;
Using popular/industry definitions, TCStore is an "Aggregate oriented, Key-Value, wide-column NoSQL store". As noted above, the individual records stored within TCStore contain cells with type information enabling the store to make use of the data it holds. However, like other NoSQL stores, TCStore is schema-less in its core design, allowing individual records to contain identical sets of cells, a subset of common cells, or a completely different sets of cells.
As such, and like other NoSQL stores, TCStore is not intended for usage patterns that are traditional to tabular data or RDBMSs. Data contained within TCStore are not and cannot be directly relational, and care should be taken to use modeling techniques (such as de-normalization of data) other than those commonly used with RDBMSs.
Type System
Fundamental to TCStore is the type system used in the data model.
The supported data types are:
Type | Description/Mapping to | Associated with ... |
BOOL | A boolean value (either true or false), mapping to java.lang.Boolean | cells of type Cell<Boolean> cell definitions of type BoolCellDefinition and CellDefinition<Boolean> |
BYTES | An array of bytes, signed 8-bit each, mapping to byte[] | cells of type Cell<byte[]> cell definitions of type BytesCellDefinition and CellDefinition<byte[]> |
CHAR | A single UTF-16 character, 16-bit unsigned, mapping to java.lang.Character | cells of type Cell<Character> cell definitions of type CharCellDefinition and CellDefinition<Character> |
DOUBLE | A 64-bit floating point value, mapping to java.lang.Double | cells of type Cell<Double> cell definitions of type DoubleCellDefinition and CellDefinition<Double> |
INT | A signed 32-bit integer value, mapping to java.lang.Integer | cells of type Cell<Integer> cell definitions of type IntCellDefinition and CellDefinition<Integer> |
LONG | A signed 64-bit integer value, mapping to java.lang.Long | cells of type Cell<Long> cell definitions of type LongCellDefinition and CellDefinition<Long> |
STRING | A variable length sequence of CHAR, mapping to java.lang.String | cells of type Cell<String> cell definitions of type StringCellDefinition and CellDefinition<String> |
The key of a Record may be an instance of any of the above types except BYTES. The value of a Cell may be an instance of any one of the above types.
Datasets
A Dataset is a collection of Record instances. Each Record instance is uniquely identified by a key within the Dataset. The key type is declared when the Dataset is created. Aside from the Record key type, a Dataset has no predefined schema.
Records
A Record is a key plus an unordered set of "name to (typed) value" pairs representing a natural aggregate of your domain model. Each Record within a Dataset can hold completely different sets of cells, as there is no schema to conform to. Record instances held within a given Dataset are immutable. Changing one or multiple values on a Record creates a new instance of that Record which replaces the old instance.
Record represents the only atomically alterable type in TCStore. You can mutate as many Cells of a given Record as you wish as an atomic action, but not across multiple Record instances. This includes adding and removing cells at the same time as changing the values of existing cells.
Cell Definitions and Values
A Record contains zero or more Cell instances, each derived from a CellDefinition. A CellDefinition is a "type/name" pair (e.g. String firstName). From a CellDefinition you can create a Cell (e.g. firstName = "Alex", where "Alex" is of type String) to store in a Record. The name of the Cell is the name from the CellDefinition used to create the cell; the value of the Cell is of the type specified in the CellDefinition used to create the cell.
Cell instances cannot contain null values. However, the API will let you test a Record for the absence of a cell.
Note: The Cell instances within a Record are unordered.
Creating a CellDefinition Instance
There are multiple ways of providing a CellDefinition describing cells used in a dataset. The following example shows various ways of specifying a CellDefinition for a cell holding a String value.
StringCellDefinition NAME =
CellDefinition.defineString("nameCell"); // <1>
CellDefinition<String> ALT_NAME =
CellDefinition.defineString("altNameCell"); // <2>
CellDefinition<String> BASIC_NAME =
CellDefinition.define("basicNameCell", Type.STRING); // <3>
1. A CellDefinition supporting a value type of String can be using the CellDefinition.defineString() method.
2. A StringCellDefinition is also a CellDefinition<String>.
3. In addition to the CellDefinition.defineString() method, the CellDefinition.define() may be used to create a CellDefinition for a String.
Cell definitions for other supported types (see
Type system) are handled similarly.
The following are the CellDefinition instances used in several examples shown in this document:
Counter Demo Cells
LongCellDefinition counterCell = CellDefinition.defineLong("longCounter");
BoolCellDefinition stoppedCell = CellDefinition.defineBool("stopped");
StringCellDefinition stoppedByCell = CellDefinition.defineString("stoppedBy");
In this group, three cells are defined
counterCell holds the value of the counter
stoppedCell indicates if the counter has been stopped or not
stoppedByCell holds the name of the stopping user if the counter is stopped
Creating a Cell Instance
Creating a Cell instance is typically done from a CellDefinition instance using the CellDefinition.newCell() method.
For applications using well-structured data, statically declaring the cell definitions and using newCell can aid code clarity.
For applications with more fluid requirements where data is not well structured, it is possible to create the needed CellDefinition instances implicitly when creating Cell instances.
Cell<String> nameCell = NAME.newCell("Alex"); // <1>
Cell<String> mollyCell = nameCell.definition().newCell("Molly"); // <2>
Cell<String> aliasCell = Cell.cell("aliasCell", "Tattoo"); // <3>
CellSet cells = CellSet.of( // <4>
cell("inlineNameCell", "Alex"),
cell("inlineWeightCell", 118.5D),
cell("inlineCountCell", 2L),
cell("inlineCategoryCell", '®'),
cell("inlineAgeCell", 42),
cell("inlineEmployedCell", true),
cell("inlineVaultCell",
new byte[] {(byte) 0xCA,(byte) 0xFE,(byte) 0xBA,(byte) 0xBE}));
1. Creates a new instance of a nameCell having the value "Alex". When using pre-defined cell definitions, this is the most common way to create a new Cell instance, the CellDefinition.newCell method. In this example, NAME is a statically-declared CellDefinition.
2. A new Cell instance can also be created from an existing Cell through the original cell’s CellDefinition. A Cell instance reveals its CellDefinition through the Cell.definition() method.
3. For applications with more fluid data needs, a CellDefinition may be implicitly defined in conjunction with creating a cell using the Cell.cell() method. When the Cell.cell() method is used, the value type for the CellDefinition is determined from the value supplied. The supported value types are limited to the supported types identified in the type system. In this example, a the CellDefinition created is equivalent to CellDefinition.define("aliasCell", Type.String). The Cell instance is equivalent to that created by CellDefinition.define("aliasCell", Type.String).newCell("Tattoo").
4. When paired with a static import for com.terracottatech.store.Cell.cell, creation of Cell instances inline becomes somewhat "cleaner". In this example, several cells, with their implicit cell definitions, inline with the creation of the CellSet to contain them.
Note: Caution is advised when using Cell.cell. A Record (or CellSet) can contain no more than one Cell of a given name regardless of type.
Note that neither CellSet nor Record guarantees an order of the Cell instances contained within. If cell ordering is required, the ordering must be applied by application code.