As explained in the Indexes and Cursors page, eXtremeDB supports a variety of index types. The following sections give C API implementation details for managing each of these index types: B-Tree, Patricia, R-Tree, KD-Tree, HNSW, Hash, OID, Autoid, User-defined.
An eXtremeDB class cannot be accessed without an index, so a class must have at least one index. If no index is explicitly defined, and the class has no dynamic-size fields (such as strings, vectors, optional structs, etc.), then a list index will be created for this class using the fixedrec allocator. Via the fixedrec allocator, the location of all the database object instances is well known, which permits access to these objects through a chain of objects instead of a B-tree for this list index implementation. This index implementation has a minimal performance cost compared to a normal B-tree index.
However, note that the implicit creation of a list index happens only for classes with fixed size. If the class contains one or more dynamic fields, the fixedrec allocator cannot be used to generate the "chain of objects". So when dynamic field(s) are present, the application must explicitly define a list or some other type of index.
C-Specific Features
C applications can optimize B-Tree index performance using the
inclusivedeclaration. (Please refer to the Optimizing Tree Indexes section below.)Another additional feature available to C applications is the ability to use the
voluntaryqualifier in schema definitions for an index to indicate that the index can be created or dropped at runtime. In other words,voluntaryindexes are not built until the application explicitly calls the generated function<classname_indexname>_create(). In the same fashion, the application can remove avoluntaryindex by calling the generated function<classname_indexname>_drop().Also, the eXtremeDB specialized Object ID (
oid) index is possible in C applications. (Please refer to the OID Indexes section below.)Another significant feature available for C developers is the ability to define custom indexes. (Please refer to the User-defined Indexes section below.)
Persistent Databases
For persistent databases there are optimization features that pertain particularly to B-Tree indexes. Please refer to the Persistent Database Index Optimization in C page for further details.
B-Tree Indexes
As explained in the B-Tree Indexes page,
treeindexes can be used for ordered (sorted) retrieval, range retrieval and pattern matching. A B-Tree index is specified in the schema file with thetreedeclaration and optional modifiersuniqueornonunique. If no modifier is specified, the default isnonunique.For example:
class anObject { uint4 value; tree<value> Idx; };The mcocomp schema compiler generates cursor functions and search functions for
treeindexes.To obtain a
cursorfor atreeindex:MCO_RET classname_indexname_index_cursor(/*IN*/ mco_trans_h t, /*OUT*/ mco_cursor_h c);The
_search()functions generated for alltreeindexes are of the following form:MCO_RET classname_indexname_search( /*IN*/ mco_trans_h trans, /*INOUT*/ mco_cursor_h cursor, /*IN*/ MCO_OPCODE op, /*IN*/ [const] <type> [*]param1, [[/*IN*/ uint2 len1,] [/*IN*/ [const] <type> [*]param2, [/*IN*/ uint2 len2,] …]);Here
MCO_OPCODErepresents a compare operation as defined in cursor operator codes.To obtain the object handle from the current cursor position:
MCO_RET classname_from_cursor( /*IN*/ mco_trans_h t, /*IN*/ mco_cursor_h c, /*OUT*/ classname *object);Note that the cursor must be properly initialized with a cursor positioning function (see the following section) to retrieve a valid database object handle.
The
_locate()function is used to position atreeindex cursor based on an object reference. The cursor must have been previously instantiated using the_index_cursor()function. (Note that the_locate()function applies only totree-based cursors, not tolistorhashcursors):MCO_RET classname_indexname_locate( /*IN*/ mco_trans_h t, /*INOUT*/ mco_cursor_h c, /*IN*/ classname *handle);Cursors and Searches
The standard cursor positioning functions
mco_cursor_first(),mco_cursor_last(),mco_cursor_next()andmco_cursor_prev()are used to initialize the cursor to the first or last and iterate through the result sets of searches.Searches are performed using the generated
_search()function. Please refer to the Searches page for implementation details. Also, as explained in the Searches page,treeindexes can be used for pattern matching, range retrieval and ordered (sorted) retrieval.Optimizing Tree Indexes
Normally, the B-Tree implementation doesn't store key values in the index. However, for in-memory databases, sometimes it is beneficial to keep the key value on the index pages.
As explained in the Inclusive and Covering Indexes page, B-Tree index performance can be optimized by declaring the index
inclusive.A "key-value-inclusive" index for a class is defined in a C API schema through the
inclusivekeyword, for example:class Tab { string name; int4 code; string body; inclusive tree<name,code> pk; };If it is desired to make all B-Tree indexes for transient classes "key-value-inclusive", the
MCO_DB_INCLUSIVE_BTREEoption can be specified in thedb_paramsargument to C APImco_db_open_dev(), for example:MCO_RET rc; mco_db_h db = 0; mco_device_t dev[N_DEVICES]; mco_db_params_t db_params; ... db_params.mode_mask |= MCO_DB_INCLUSIVE_BTREE; ... rc = mco_db_open_dev(db_name, mydb_get_dictionary(), dev, N_DEVICES, &db_params);In addition, a "covering" index (which contains all of the fields of interest in a class) can be also declared inclusive to leverage CPU cache usage, for example:
class Tab { string name; int4 code; string body; inclusive tree<*> pk; };(Note that a "key-value-inclusive" or "covering" index can only be a B-Tree index, so the
inclusivedeclaration can only precede thetreekeyword.)Patricia Trie Indexes
As explained in the Patricia Indexes page, the eXtremeDB
patriciaindex is particularly useful for network and telecommunications applications. Apatriciaindex can be declared over scalar andbooleandata types as well as arrays and vectors of those types. In fact, thebooleandata type allows bit arrays to be used to store IP addresses. Apatriciaindex can also be declaredunique; in the absence of theuniquekeyword it defaults to allowing duplicates. Unlike other eXtremeDB indexes, thepatriciaindex cannot be compound; it is always declared for a single field.The following schema illustrates some possible declarations:
class xyz { boolean b1[32]; vector<boolean> b2; uint4 b3; char<10> b4[10]; vector<string> b5; patricia <b1> Ib1; patricia <b2> Ib2; patricia <b3> Ib3; patricia <b4> Ib4; patricia <b5> Ib5; unique patricia <b1> Ib1U; unique patricia <b2> Ib2U; unique patricia <b3> Ib3U; unique patricia <b4> Ib4U; unique patricia <b5> Ib5U; }Note that
booleanfieldsb1,b2are used as bit arrays forpatriciaindexesIb1UandIb2U. Thebooleandata type is treated in a special way by themcocompschema compiler and requires some explanation in the following section. Also, in addition to the standard tree index generated functions, the following section discusses functions that are generated for eachpatriciaindex.Boolean Data Type
The
booleandata type can be used to define a single bit field, a fixed size array of bits, or a variable length array of bits. The only index type possible for thebooleandata type is thepatriciaindex. A single-bit field cannot be indexed, nor is it advisable to index a short array of bits.For example:
class xyz { boolean b1; // a bit field boolean b2[32]; // fixed-size array of 32 bits vector<boolean> b3; // variable-length bit array };For C applications, the following functions are generated for fields of type
boolean:classname_fieldname_get(classname *handle, /*OUT*/ uint1 *result); classname_fieldname_put(classname *handle, uint1 value);For a fixed-size bit array the following interfaces are also generated:
// Reads the specified bit out of the array. // Note: It's OK to read one bit, but in order // to read several bits it's better to read the // entire array and mask it. classname_fieldname_at(classname* handle, uint2 index, /*OUT*/ uint1 *result); // Read range of bits. // Note: the start_index bit number must be a power of 8 classname_fieldname_get_range(classname* handle, uint2 start_index, uint2 num, /*OUT*/ uint1 *dest); // Again, the bit-by-bit _put is not the most effective // way of writing the array. Better to mask the entire // array and use _put_range classname_fieldname_put(classname* handle, uint2 index, uint1 value); // Write range of bits. // Note: the start_index bit number must be a power of 8 classname_fieldname_put_range(classname* handle, uint2 start_index, uint2 num, const uint1 *src); // Read the entire field classname_fieldname_get(classname *handle, /*OUT*/ uint1 *result);For variable-size arrays (
vectors), the following interfaces are also generated:// Vector size (in bits) classname_fieldname_size(classname* handle, /*OUT*/ uint2 *result); // Allocate a vector (size in bits) classname_fieldname_alloc(classname* handle, uint2 size); // Read an element at a specified position classname_fieldname_at(classname* handle, uint2 index, /*OUT*/ uint1 *result); // Read range of bits. // Note: the start_index bit number must be a power of 8 classname_fieldname_get_range(classname* handle, uint2 start_index, uint2 num, /*OUT*/ uint1 *dest); // Write range of bits. // Note: the start_index bit number must be a power of 8 classname_fieldname_put_range(classname* handle, uint2 start_index, uint2 num, const uint1 *src); // Write an element (not very efficient) classname_fieldname_put(classname* handle, uint2 index, uint1 value);Generated Functions for Patricia Indexes
The generated functions applicable only to
patriciaindexes are_longest_match(),_exact_match(),_prefix_match()and_next_match(). These will have slightly different forms depending on the type of field being indexed.A
patriciaindex created over a scalar field will cause the following functions to be generated:classname_indexname_next_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type mask, int number_of_bits); classname_indexname_prefix_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type mask, int number_of_bits); classname_indexname_longest_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type mask, int number_of_bits); classname_indexname_exact_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type mask, int number_of_bits);The
typeargument is the scalar type (for exampleuint4) andmaskis the key value to match. If the indexed field is a fixed-length array or avectorof scalars these functions will be of the form:classname_indexname_next_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type *mask, int number_of_bits); classname_indexname_prefix_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type *mask, int number_of_bits); classname_indexname_longest_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type *mask, int number_of_bits); classname_indexname_exact_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, type *mask, int number_of_bits);Here
typeis the type of each element of the array orvector(for exampleuint4) andmaskis the key value to match. If the indexed field is an array ofbooleanthese functions will be of the form:classname_indexname_next_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, char* mask, int number_of_bits); classname_indexname_prefix_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, char* mask, int number_of_bits); classname_indexname_longest_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, char* mask, int number_of_bits); classname_indexname_exact_match(mco_trans_h t, /*INOUT*/ mco_cursor_h c, char* mask, int number_of_bits);Here
maskis a key value that is a packed bit array (each byte contains 8 bits).Cursors and Searches
The standard cursor positioning functions
mco_cursor_first(),mco_cursor_last(),mco_cursor_next()andmco_cursor_prev()are used to iterate through the result sets ofpatriciaindex searches. But instead of a_search()function, searches are performed using one of the generated_match()functions. Initially a cursor's position is not defined and needs to be set with a lookup or a call ofmco_cursor_first()ormco_cursor_last().Please refer to the Searches page for implementation details.
R-Tree Indexes
As explained in the R-Tree Index page, an
rtreeindex is commonly used to speed spatial searches. Anrtreeindex is typically defined for an array field containing the number of coordinates required to describe a "rectangle". For example:class rtree_class { int2 square[4]; rtree <square> ridx; };The
rtreeindex-related functions generated for this class will be as follows:MCO_RET rtree_class_ridx_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET rtree_class_ridx_search( mco_trans_h t_, /*IN*/ MCO_OPCODE op_, /*INOUT*/ mco_cursor_h c_, const int2* square);Cursors and Searches
As explained in the R-Tree Index page,
rtreesearches are performed using the generated_search()function with one of the four search operation opcodes:MCO_EQUAL,MCO_CONTAIN,MCO_OVERLAPorMCO_NEIGHBORHOOD. Please refer to the Searches page for implementation details.KD-Tree Indexes
As explained in the KD-Tree Index page,
kdtreeindexes are ideal for multi-dimensional key value searches. Thekdtreeindex is defined in the schema using thekdtreedeclaration. For example:class Car { string vendor; string model; string color; uint4 year; uint4 mileage; boolean automatic; boolean ac; uint4 price; char<3> state; string description; kdtree <year, mileage, color, model, vendor, automatic, ac, price> index; };The
kdtreeindex-related functions generated for this class will be as follows:MCO_RET Car_index_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET Car_index_search( mco_trans_h t, /*INOUT*/ mco_cursor_h c, /*IN*/ Car *range_start, /*IN*/ Car *range_end); MCO_RET Car_index_locate( mco_trans_h t, /*OUT*/ mco_cursor_h c, Car *handle);Cursors and Searches
As explained in the KD-Tree Index page, searches with
kdtreeindexes are performed using the Query-By-Example approach. Once a selection of database objects are found by the search operation, the standard cursor positioning functionsmco_cursor_first(),mco_cursor_last(),mco_cursor_next()andmco_cursor_prev()are used to iterate through the result sets. Please refer to the Searches page for implementation details.Trigram Indexes
As explained in the Trigram Index page,
trigramindexes are ideal for text searches when the exact spelling of the target object is not precisely known. Thetrigramindex is defined in the schema using thetrigramdeclaration. For example:class anObject { uint4 id; string text; trigram<text> trigramIdx; };The
trigramindex-related functions generated for this class will be as follows:MCO_RET anObject_trigramIdx_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET anObject_trigramIdx_search( mco_trans_h t, /*INOUT*/ mco_cursor_h c, MCO_OPCODE op_, const char *text_key_, uint2 sizeof_text_key_); MCO_RET anObject_trigramIdx_locate( mco_trans_h t, /*OUT*/ mco_cursor_h c, anObject *handle);Cursors and Searches
As explained in the Trigram Index page, searches with
trigramindexes are performed using the generated_search()function. Once a selection of database objects are found by the search operation, the standard cursor positioning functionsmco_cursor_first(),mco_cursor_last(),mco_cursor_next()andmco_cursor_prev()are used to iterate through the result sets. Please refer to the Searches page for implementation details.HNSW Indexes
As explained in the HNSW Indexes page,
hnswindexes are ideal for fast approximate nearest neighbor search.The
hnswindex is defined in the schema using thehnswdeclaration. For example:#define key_t unsigned<4> #define coord_t float #define DIM 3 declare database hnswdb; class Embeddings { key_t pk; coord_t coord[DIM]; tree<pk> pk_idx; hnsw<coord> hnsw_idx[16]; };The DIM parameter specifies the vector dimensionality and must strictly match the input data, as it directly determines the memory footprint for coordinate storage and the computational cost of distance calculations. The value in square brackets "
[16]" corresponds to the M parameter, which defines the maximum number of connections per node at each graph level: higher values improve search accuracy and routing robustness but increase memory usage and indexing time, while lower values produce a more compact index at the potential cost of recall.Additional parameters, including ef_search, ef_construction, the distance function, and an override for M, can be configured after runtime initialization as follows:
const int ef_construction = 100; const int ef_search = 100; const int M = 10; int dist_func; ... CHECK(mco_runtime_start()); ... mco_runtime_setoption(MCO_RT_OPTION_HNSW_EF_SEARCH, ef_search); mco_runtime_setoption(MCO_RT_OPTION_HNSW_EF_CONSTRUCTION, ef_construction); mco_runtime_setoption(MCO_RT_OPTION_HNSW_M, M); dist_func = DIST_COSINE; mco_runtime_setoption(MCO_RT_OPTION_HNSW_DIST_FUNC, dist_func);The available distance metrics are defined in the
mco.hheader file as follows:typedef enum { DIST_L2, DIST_COSINE, DIST_IP, DIST_MANHATTAN } mco_hnsw_dist_func_t;By default, these parameters are initialized to:
- ef_search = 128
- ef_construction = 64
- dist_func = DIST_L2
- M = 16
Cursors and Searches
Search operations on this index are performed using the generated
_searchfunction. Please refer to the Searches page for implementation details.Note: Cursor-based search returns up to N neighboring nodes, where N cannot exceed the total number of embeddings in the collection. Due to the approximate nature of the algorithm, the results do not guarantee strictly monotonically increasing distances between consecutive items, which is expected behavior for ANN search.
The following example demonstrates a typical usage scenario:
#define DIM 3 typedef float coord_t; static coord_t embeddings[][DIM] = { {0,0,0}, {1,2,3}, {1,1,1}, {1,2,4} }; ... void fill_database(mco_db_h db) { MCO_RET rc; mco_trans_h t; Embeddings e; mco_uint4 i; /* Store embeddings */ printf("\n\tFilling database with embeddings...\n" ); for (i = 0; i < sizeof(embeddings) / sizeof(embeddings[0]); ++i) { rc = mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t); if ( MCO_S_OK == rc ) { CHECK(Embeddings_new(t, &e)); CHECK(Embeddings_pk_put(&e, i)); CHECK(Embeddings_coord_put_range(&e,0, DIM, embeddings[i])); rc = mco_trans_commit(t); ... } ... } ... /* Search for nearest neighbors */ printf("\n\tSearching for nearest neighbors...\n" ); mco_trans_h t; mco_cursor_t cursor; MCO_RET rc; rc = mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t); if (MCO_S_OK == rc) { const coord_t pattern[DIM] = { 3, 3, 3 }; CHECK(Embeddings_hnsw_idx_index_cursor(t, &cursor)); for (rc = Embeddings_hnsw_idx_search(t, &cursor, pattern); rc == MCO_S_OK; rc = mco_cursor_next(t, &cursor)) { Embeddings e; mco_uint4 pk; coord_t coord[DIM]; CHECK(Embeddings_from_cursor(t, &cursor, &e)); CHECK(Embeddings_pk_get(&e, &pk)); CHECK(Embeddings_coord_get_range(&e, 0, DIM, coord)); printf("Embeddings %d [%f,%f,%f]\n", pk, coord[0], coord[1], coord[2]); } CHECK(mco_cursor_close(t, &cursor)); CHECK(mco_trans_rollback(t)); } ...Vamana(DiskANN) Indexes
Due to its architectural similarity to HNSW, the Vamana index follows the same creation and initialization workflow. The only distinction is the
ALPHAparameter: assigning it any non-zero value enables the Vamana variant in place of the standard HNSW algorithm. The following code snippet demonstrates its usage:const int ALPHA = 10; /* 10 = 0.10, 100 = 1.00, etc. */ ... mco_runtime_setoption(MCO_RT_OPTION_VAMANA_BUILD_METHOD, VAMANA_BUILD_INCREMENTAL); mco_runtime_setoption(MCO_RT_OPTION_VAMANA_ALPHA, ALPHA);Note: In the Vamana algorithm implementation, the
ALPHAparameter is specified as an integer value representing hundredths of the actual floating-point value used internally. For example:The optimal
- A parameter value of 10 corresponds to an internal ALPHA = 0.10
- A parameter value of 80 corresponds to an internal ALPHA = 0.80
ALPHAvalue is not fixed and should be tuned based on your dataset and the desired trade-off between recall (search accuracy) and query latency (search speed). Higher values generally improve recall at the cost of increased search time.The
MCO_RT_OPTION_VAMANA_BUILD_METHODoption controls how the starting point (root) is chosen during Vamana graph construction. The following values are defined in themco.hheader file:typedef enum { VAMANA_BUILD_INCREMENTAL, VAMANA_BUILD_MEDOID, VAMANA_BUILD_CENTROID } mco_vamana_build_method_t;
VAMANA_BUILD_INCREMENTAL— The first inserted point becomes the root. This is the default behavior.VAMANA_BUILD_MEDOID— The root point is selected via a two-pass process based on the existing values in the table.VAMANA_BUILD_CENTROID— An artificial central point, computed as the mean of all coordinates, is created and used as the anchor vertex for graph construction.Hash Indexes
As explained in the Hash and Autoid Indexes page,
hashindexes are ideal for quick lookup of individual database objects.Hashindexes require an extra parameter, expected-number-of-entries, following the index name. It is an integer number that the runtime uses to allocate the initial hash table for the index. It must be specified but is not required to be exact. For example:class Record { uint4 iIdx; /* Index */ uint4 iSeries; /* Series of measurement */ hash <iIdx> I_Index[10000]; nonunique hash <iSeries> I_Series[10000]; };Note that the
[10000]specification for indexesI_IndexandI_Seriescause the runtime to allocate initial hash tables with space for10000key values.The index-related functions generated for the (unique)
hash I_Indexin this class will be as follows:MCO_RET Record_I_Index_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET Record_I_Index_compare( mco_trans_h t, mco_cursor_h c, uint4 iIdx_key_, /*OUT*/ int *result_); MCO_RET Record_I_Index_find( mco_trans_h t, uint4 iIdx_key_, /*OUT*/ Record *handle_);The index-related functions generated for the
nonunique hash I_Seriesin this class will be as follows:MCO_RET Record_I_Series_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET Record_I_Series_compare( mco_trans_h t, mco_cursor_h c, uint4 iSeries_key_, /*OUT*/ int *result_); MCO_RET Record_I_Series_search( mco_trans_h t, /*INOUT*/ mco_cursor_h c, uint4 iSeries_key_);Note that for
unique hashindexes the exact match_find()function is generated, but fornonunique hashindexes the_search()function is generated.Dynamic Hash Table Allocation
An eXtremeDB C API feature called dynamic hash can be enabled to automatically re-allocate the hash table as needed. This will avoid (a) long collision chains if the estimated number of objects is too small, or (b) wasting memory in the case of an overly cautious large estimate.
Note that when dynamic hash is enabled it applies to all
hashindexes in the database as well as to thehashtable maintained foroidandautoidindexes.The
dynamic_hashparameter (a boolean value) passed to mco_db_open_dev() in themco_db_params_tstructure determines if dynamic hash table extending is enabled or not (it is enabled by default). The initial hash table is allocated using two values: the estimated number of objects specified for this class in the database schema, and thehash_load_factorparameter (a percentage value, 100% by default), also part of themco_db_params_tstructure. The initial hash table is estimatednumber of objects * 100 / hash_load_factor. (The value of zero forhash_load_factorsignifies the default value of 100%.)So if
hash_load_factoris 100 (i.e., 100%), then the initial size of the hash table is the specified estimated number of objects. Ifhash_load_factoris 50, then initial size of hash table is twice the estimated number of objects. And ifhash_load_factoris 200, then the initial size of the hash table is half of the estimated number of objects.When dynamic hash is enabled the
hash_load_factorparameter is used to determine when to extend (reallocate) the hash table. For example, if the initial hash table size is 1000 and thehash_load_factoris 50, then the hash table will be extended when the 501st object is inserted; if thehash_load_factoris 150, then the hash table will be extended when the 1501st object is inserted.If it is important to impose a definite limit on the size of the hash table, the application can disable dynamic hash by setting the
dynamic_hashparameter to false. This will have the consequence that more hash conflicts can occur, so the application must be willing to pay that performance cost.Cursors and Searches
As explained in the Hash and Autoid Indexes page, "exact match" searches with
unique hashindexes are performed using the_find()function. Fornonunique hashindexes the_search()function is used and once a selection of database objects are found by the search operation, the standard cursor positioning functionsmco_cursor_first(),mco_cursor_last(),mco_cursor_next()andmco_cursor_prev()are used to iterate through the result sets. Please refer to the Searches page for implementation details.OID Indexes
Whether an
oidis provided by an external source or retrieved with an object as a reference to another object in the database, in C applications, anoidcan be used to quickly retrieve the object it identifies. Uniqueness is enforced during object creation by the eXtremeDB runtime and anoidindex can be used to establish relationships between classes in the database.For example, to establish a relationship between an Order and Execution using
oids, consider the following schema:struct OrderId { uint4 Id; }; declare oid OrderId[1000]; // 1000 is the number of expected values (database-wide) class Order { ... vector <ref> Executions; ... }; class Execution { float quantity; oid; ... };Note that the
declarestatement is used to identify a unique object identifier with the expected number of objects that will be stored with anoid. The expected-number-of-entries is used by the runtime to calculate the initial hash table size for the index. (See the Hash Indexes section for an explanation of how to specify static and dynamic hash tables and how they are managed by the eXtremeDB runtime.)Once the
oidstructure is declared, classes with this unique identifier can then be declared (as in the Execution class above). The runtime maintains an internal index referencing all objects of such classes. Objects can reference each other byoidusing therefdata type (as in the Order class above).The
oidmust be a user-defined structure, even if theoidhas a single field. Eachoidvalue must be unique for the entire database and only oneoiddeclaration is allowed within a database schema. And only oneoidstatement is allowed per class.Oidsmust be assigned a value that is unique in the entire database (not just in the class, as is the case for hash and unique tree indexes).In this example, the class Order contains a variable length array (a
vector)Executionsof references tooidsof the class Execution. Each element of thevectoris theoidof an instance of the class Execution and can be used to quickly reference (locate) the associated Execution object.(For another example of creating
oidreferences please refer to the Class Relationships page.)OID Structures and Functions
If an
oidis declared for the database, themcocompschema compiler generates a C structure corresponding to the structure defining theoid. For instance, for a schema containing anoiddeclaration like the following:declare database my_db; struct structname { uint4 num_in; }; declare oid structname[10000];the following definitions and functions will be generated:
typedef struct my_db_oid__ { uint4 num_in; } my_db_oid; static const uint2 my_db_oid_size = sizeof(my_db_oid); MCO_RET my_db_delete_object( /*IN*/ mco_trans_h t, /*IN*/ const my_db_oid *oid); MCO_RET my_db_get_class_code( /*IN*/ mco_trans_h t, /*IN*/ const my_db_oid *oid, /*OUT*/ uint2 *classcode);Note the prepended "my_db" in these definitions. This evidences the fact that only one
oidcan be defined for a given database and uniqueness for all values of thatoidwill be enforced by the eXtremeDB runtime. The_delete_object()function deletes an object based on itsoid. The_get_class_code()function returns an integer that identifies the class of the object referenced by a specifiedoidvalue.For classes containing an
oid, the following functions are generated to create an object, locate an object based on itsoidand to extract theoidof an object:MCO_RET classname_new( /*IN*/ mco_trans_h t, /*IN*/ const my_db_oid *id, /*OUT*/ classname *handle); MCO_RET classname_oid_find( /*IN*/ mco_trans_h t, /*IN*/ const my_db_oid *id, /*OUT*/ classname *handle); MCO_RET classname_oid_get( /*IN*/ classname *handle, /*OUT*/ my_db_oid *id);Cursors and Searches
Only "exact match" searches are possible for
oidwhich are performed using the_oid_find()function. Please refer to the Searches page for implementation details.Autoid Indexes
The
autoidis a guaranteed unique value generated by the eXtremeDB runtime. For C applications, theautoidis declared within the DDL class definition with a specified number of estimated objects of that class. Anautoidindex can be used to establish relationships between classes in the database. (For an example of creatingautoidreferences please refer to the Class Relationships page.)When a class is declared to have an
autoid, the schema compiler will generate the following two functions for the class:MCO_RET classname_autoid_find( /*IN*/ mco_trans_h t, /*IN*/ autoid_t id, /*OUT*/ classname *handle); MCO_RET classname_autoid_get( /*IN*/ classname *handle, /*OUT*/ autoid_t *id);When an object of a class with an
autoidis created, the runtime generates and inserts a uniqueautoidvalue for the object into an internally maintainedhashindex. This value can be retrieved with the generated_autoid_get()function for the given class. Anautoidvalue, whether stored in a program variable or a field of another database object as a reference, can be used in the_autoid_find()function to locate the referenced object as demonstrated in the following code snippet:Schema snippet: class referenced { ... autoid[4000]; ... }; class referencing { ... autoid_t refd_object; ... }; Application code snippets: autoid_t id; mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t); // Create new object, autoid assigned by system referenced_new(t, &refd_obj); // Get the autoid value referenced_autoid_get(&refd_obj, &id); rc = referencing_new(t, &refg_obj); // Create new object // Store ref'd autoid in it referencing_refd_object_put(&refg_obj, id); rc = mco_trans_commit(t); /* Object retrieval */ rc = mco_trans_start(db, MCO_READ_ONLY, MCO_TRANS_FOREGROUND, &t); /* First locate a 'referencing' object by some method (not shown) */ // Get the autoid of the referenced obj referencing_refd_object_get(&refg_obj, &id); // Locate the referenced object rc = referenced_autoid_find(t, id, &refd_obj); rc = mco_trans_commit(t);This snippet shows the process of creating a new object of a class that has an
autoiddefined, retrieving the system-assignedautoidvalue, and storing that value in a field of an object of the class that references it. Later, theautoidvalue is extracted from the referencing object and used to locate the referenced object through its_autoid_find()function.Note that the internal nature of the
autoidvalue is not defined. The only defined property of anautoidis that it has size of 8 bytes and it is unique. An application should not rely on or expect numeric values of theautoidfield.Cursors and Searches
Only "exact match" searches are possible for
autoidwhich are performed using the_autoid_find()function. Please refer to the Searches page for implementation details.User-defined Indexes
For C applications,
treeandhashindexes can be declared in the schema definition asuserdef, in which case the application must provide custom compare functions that are used by the database runtime when building the index and during lookup. In addition, forhashindexes, the application must provide the custom hash functions. For example, consider the following sample schema:declare database mydb; class Obj { unsigned<4> first_part1; unsigned<2> first_part2; unsigned<4> second_part1; signed<2> second_part2; string data; userdef hash <first_part1, first_part2> first[1000]; userdef tree <second_part1, second_part2> second; };The following index-related functions are generated for
userdef hashindexfirst:MCO_RET Obj_first_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET Obj_first_compare( mco_trans_h t, mco_cursor_h c, uint4 first_part1_key_, uint2 first_part2_key_, /*OUT*/ int *result_); MCO_RET Obj_first_find( mco_trans_h t, uint4 first_part1_key_, uint2 first_part2_key_, /*OUT*/ Obj *handle_);And for
userdef treeindexsecond:MCO_RET Obj_second_index_cursor( mco_trans_h t, /*OUT*/ mco_cursor_h c); MCO_RET Obj_second_search( mco_trans_h t, /*INOUT*/ mco_cursor_h c, MCO_OPCODE op_, uint4 second_part1_key_, int2 second_part2_key_); MCO_RET Obj_second_compare( mco_trans_h t, mco_cursor_h c, uint4 second_part1_key_, int2 second_part2_key_, /*OUT*/ int *result_); MCO_RET Obj_second_locate( mco_trans_h t, /*OUT*/ mco_cursor_h c, Obj *handle);For the
treeindexsecondit is necessary to implement two functions: the object-to-object and object-to-key compare functions. These functions return a negative, zero or a positive value depending on whether the first parameter is less than, equal to, or greater than the second parameter. Forhashindexes, it is necessary to implement<classname_indexname>_hash_obj()and<classname_indexname>_hash_ext()functions in addition to the compare functions, which in this case return zero if the first and the second parameters are equal and non-zero otherwise. The function prototypes are generated by the schema compiler and are placed into a file nameddbname_udf.c(wheredbnameis the database name in thedeclare databasestatement).For example, for the above sample schema the following
mydb_udf.cfile would be generated:#include "mydb.h" #include "mcowrap.h" /* * API for the user-defined index "first" */ /* object-to-object user-defined compare function */ int2 Obj_first_compare_obj(Obj *handle1, Obj *handle2) { /* TODO: add your implementation here */ return 0; } /* object-to-key user-defined compare function */ int2 Obj_first_compare_ext(Obj *handle, void **key) { /* TODO: add your implementation here */ return 0; } /* user-defined object hash function */ uint4 Obj_first_hash_obj(Obj *handle) { /* TODO: add your implementation here */ return 0; } /* user-defined key hash function */ uint4 Obj_first_hash_ext(void **key) { /* TODO: add your implementation here */ return 0; } /* * API for the user-defined index "second" */ /* object-to-object user-defined compare function */ int2 Obj_second_compare_obj(Obj *handle1, Obj *handle2) { /* TODO: add your implementation here */ return 0; } /* object-to-key user-defined compare function */ int2 Obj_second_compare_ext(Obj *handle, void **key) { /* TODO: add your implementation here */ return 0; }If a file
dbname_udf.calready exists (for example if the schema was previously compiled, then modified and compiled again), the DDL compiler will generate a filedbname_udf.c.newand display a warning. In this case, it is the responsibility of the programmer to decide which file should be included in the project. If the user-defined indexes have been changed, then the.newfile should be renamed to the.cfile.The custom user-defined compare functions are always called within the context of a transaction. So it is okay to access the object fields using the generated
_get()functions. To access key fields within the index structure, supplementary macro definitions are generated in thedbname.hfile. For the example schema above these would be as follows:#define Obj_first_extkey_first_part1(k) #define Obj_first_extkey_first_part2(k) #define Obj_second_extkey_second_part1(ek) #define Obj_second_extkey_second_part2(ek)A sample implementation for the custom compare and hash functions could be as follows:
/* object-to-object user-defined compare function */ int2 Obj_first_compare_obj(Obj *handle1, Obj *handle2) { uint4 o1_first_part1, o2_first_part1; uint2 o1_first_part2, o2_first_part2; Obj_first_part1_get(handle1, &o1_first_part1); Obj_first_part1_get(handle2, &o2_first_part1); if (o1_first_part1 != o2_first_part1) return 1; Obj_first_part2_get(handle1, &o1_first_part2); Obj_first_part2_get(handle2, &o2_first_part2); if (o1_first_part2 != o2_first_part2) return 1; return 0; } /* object-to-key user-defined compare function */ int2 Obj_first_compare_ext(Obj *handle, void **key) { uint4 o_first_part1; uint2 o_first_part2; Obj_first_part1_get(handle, &o_first_part1); if (o_first_part1 != Obj_first_extkey_first_part1(key)) return 1; Obj_first_part2_get(handle, &o_first_part2); if (o_first_part2 != Obj_first_extkey_first_part2(key)) return 1; return 0; } /* user-defined object hash function */ uint4 Obj_first_hash_obj(Obj *handle) { uint4 o_first_part1; uint2 o_first_part2; uint4 hash; Obj_first_part1_get(handle, &o_first_part1); Obj_first_part2_get(handle, &o_first_part2); hash = (o_first_part1*1000+o_first_part2) / 1000; return hash; } /* user-defined key hash function */ uint4 Obj_first_hash_ext(void **key) { uint4 hash; hash = (Obj_first_extkey_first_part1(key)*1000 + Obj_first_extkey_first_part2(key)) / 1000; return hash; } /* * API for the user-defined index "second" */ /* object-to-object user-defined compare function */ int2 Obj_second_compare_obj(Obj *handle1, Obj *handle2) { uint4 o1_second_part1, o2_second_part1; uint2 o1_second_part2, o2_second_part2; Obj_second_part1_get(handle1, &o1_second_part1); Obj_second_part1_get(handle2, &o2_second_part1); Obj_second_part2_get(handle1, &o1_second_part2); Obj_second_part2_get(handle2, &o2_second_part2); if (o1_second_part1 < o2_second_part1) return -1; if (o1_second_part1 > o2_second_part1) return 1; if (o1_second_part2 < o2_second_part2) return -1; if (o1_second_part2 > o2_second_part2) return 1; return 0; } /* object-to-key user-defined compare function */ int2 Obj_second_compare_ext(Obj *handle, void **key) { uint4 o_second_part1; uint2 o_second_part2; Obj_second_part1_get(handle, &o_second_part1); Obj_second_part2_get(handle, &o_second_part2); if (o_second_part1 < Obj_second_extkey_second_part1(key)) return -1; if (o_second_part1 > Obj_second_extkey_second_part1(key)) return 1; if (o_second_part2 < Obj_second_extkey_second_part2(key)) return -1; if (o_second_part2 > Obj_second_extkey_second_part2(key)) return 1; return 0; }Cursors and Searches
As seen above, for
userdef hashindexes, only a_find()function is generated. But fortreeindexes, both a_find()and a_search()function are generated.The standard cursor positioning functions
mco_cursor_first(),mco_cursor_last(),mco_cursor_next()andmco_cursor_prev()are used to iterate through the result sets. Please refer to the Searches page for implementation details.