Schema and Entities

Discret uses a data model language to describe what kind of data can be inserted and queried.

Basic Syntax

Let's start with a basic example:

{
    Person {
        name: String,
        nickname: String nullable,
        parents:[Person]
    }
}

We defined an entity named Person that contains three fields:

Entity names are case sensitive, Person and person would define two different entities.

Field names are case sensitive, a field name will be different from Name

Scalar Fields

Discret defines the following scalar types to store your data:

Scalar types are case insensitive. for example, integer or iNteGer is valid.

By default scalar types are not nullable. but it is possible to :

You cannot combine nullable and default for the same field.

{   
    //with default values
    ScalarsDefault {
        name: String default "someone",
        age: Integer default 0,
        height: Float default 3.2,
        is_nice: Boolean default true,
        Is_Nice: boolean default True, //true is case insensitive
        profile: Json default "{}",                
        thumbnail: Base64 default ""
    }

    //with nullable values
    ScalarsNullable {
        name: String nullablE,
        age: Integer NULLABLE, //nullable is case insensitive
        height: Float nullable,
        is_nice: Boolean nullable,
        profile: Json nullable,                 
        thumbnail: Base64 Nullable
    }
}

Relation Field

Relation fields link entities together, allowing for complex data modelisation and query. There is two kinds of relation field:

Relation fields are nullable and it is not possible gives them a default value.

The syntax is the following.

{
    Person {
        name: String,
        pet: Pet,           //unique relationship
        parents:[Person]    //multiple relationship
    }

    Pet {
        name: String,
    }
}

Sadly, the Person defined by this entity can only have one Pet. But they can have has many parents as you want.

Namespace

Complex applications can have a large number of entities. It is possible to separate entity definition in namespace. This can greatly improve the modularity of your code and should be considered for large codebase.

mod_one {
    Person {
        name : String ,
        surname : String,
        child : [mod_one.Person] ,
        pets: [mod_one.Pet]
    }

    Pet {
        name : String,
    }
}
{
    Person {
        surname : String ,
        parents : [Person] ,
        pets: [mod_one.Pet]
    }
}

In this example two namespace are defined: mod_one and the default namespace without a name, we can notice that:

System fields

Every entity have a set of system fields, most of which being used internaly by Discret.

Those fields can be queried like regular fields, but only room_id and _binary can be modified directly.

Index

The base system already possesses indexes that should be enough for a lot of use cases. If an entity contains a very large number of tuples, and if a specific set of fields are queried a lot, it is possible to create an index to improve query performances. you should use this feature wisely, two many indexes can result in degraded insertion performances, .

Indexes can only be put on scalar and system fields.

This example creates an index on the (name, id) tuple:

{
    Person {
        name: String,
        nickname: String nullable,
        parents:[Person],
        index(name, id),
    }
}

Disabling full text indexing

By default, every String fields are indexed to allow full text search. It can be disabled using the no_full_text_index flag.

The index defined inside the entity will not be disabled.

my_data {
    Person( no_full_text_index) {
        name : String,
        index(name) //not disabled
    }
}

Updating the datamodel

During the lifetime of a software, the datamodel is likely to be modified many times. A set of rules must be respected to ensure that adding new fields and entities will not introduce breaking changes.

A data model modification must contains the full data model, not just the changes. Discret will compare the new model against the existing one to enforce the following rules:

Fields update and insertion must obey strict rules:

This set of constraints allows the datamodel to evolve without requiring versioning.

Let take an example, if we consider the following to be the old data model

{
    Person {
        name : String nullable,
        surname: String nullable,
    }
}

The following update will be valid

{
    Person {
        //changed to nullable
        name : String nullable, 

        //changed to not nullable with a default value        
        surname : String default "john",
        
        //new field at the end
        parents : [Person],
    }

    //new entity at the end of the namespace
    Pet {
        name: String
    }
}
//new namespace after the existing one
my_data {

}

But this one will be rejected

//error:: new namespace before the existing one
my_data {

}
{
    //Error: new entity before an existing one
    Pet {
        name: String
    }

    Person {
        //Error: 'name' field is removed

        //Error: new field before an existing one
        parents : [Person],

        //Error: changed to not nullable without a default value
        surname : String,
        
    }
}

Deprecation

We saw in the previous chapter that entities and fields cannot be deleted. It is however possible to mark them as 'deprecated' to indicate the developers of your application to stop using them. This is only a documentation flag, deprecated fields and entities can still be used and no warning or error will be thrown.

Deprecation uses the @deprecated keyword:

{
    @deprecated Person {
        name : String ,
    }

    Pet {
        name : String,
        @deprecated  age : Float NULLABLE,
        weight : Integer NULLABLE,
        is_vaccinated: Boolean NULLABLE,
        INDEX(weight),
    }
}

In this example, the Person entity and the Pet.age field are marked deprecated.