Skip to content
NarDBv0.2.0-dev

Validation

check rules, nullable, default and password fields.

Rules are applied on every insert and update. A value that breaks a rule is not written, and the error says which field was rejected and why.

check#

NarQL
create_schema('people')
    .fields({
        'email':  {type: string, max_length: 100, index: {unique: true},
                   check: regex('^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$')},
        'age':    {type: int8, check: {min: 0, max: 120}},
        'gender': {type: int8, check: enum({0: 'male', 1: 'female'})},
        'joined': {type: date, check: {min: '2020-01-01'}}
    })
    .commit();
RuleApplies to
{min: …, max: …}numbers, decimal, dates
regex('…')string, text (RE2 syntax: no lookaround, no backreferences)
enum({value: 'label'})integers and text
password_hash()string (max_length ≥ 128), text

Several rules go in a list: check: [regex('^[a-z]+$'), enum({'a': 'A', 'b': 'B'})].

NarQL
# error: age: 125 is above max 120
from('people').insert_one({email: 'a@b.co', age: 125});

int8 holds at most 127. With 130 the error would be "does not fit int8": the type's range is checked before the rules.

NarQL
# error: gender: 7 is not one of 0 (male), 1 (female)
from('people').insert_one({email: 'a@b.co', gender: 7});

nullable and default#

NarQL
create_schema('notes')
    .fields({
        'no':      {type: int64, autoincrement: true, primary_key: true},
        'title':   {type: string, max_length: 80, nullable: false},
        'status':  {type: string, max_length: 10, default: 'draft'},
        'created': {type: timestamp, default: now()}
    })
    .commit();

from('notes').insert_one({title: 'First note'});
NarQL
# error: title is required (not nullable)
from('notes').insert_one({status: 'published'});

A default must satisfy the field's own rules; a schema with a default that breaks them is rejected when it is created.

Password fields#

NarQL
create_schema('accounts')
    .fields({
        'username': {type: string, max_length: 40, primary_key: true},
        'password': {type: string, check: password_hash()}
    })
    .commit();

from('accounts').insert_one({username: 'osman', password: 'secret-password'});
from('accounts').fields('*').get();
  • The value is hashed with Argon2id before it is written; the plain password never reaches the disk.
  • Password fields appear as •••••• in query results.
  • A value that is already an Argon2id hash (imported from another system) is not hashed again.
  • password_hash can only be set when the field is created. Adding it to an existing field would leave the existing values unhashed.

Uniqueness#

A unique index stops the same value from being written twice:

NarQL
from('people').insert_one({email: 'osman@example.com'});
NarQL
# error: email: value osman@example.com already exists (unique index idx_email)
from('people').insert_one({email: 'osman@example.com'});