With LexerLang
use nardb — connection forms, credentials, how the NarQL chain reads in LexerLang, types, security.
NarDB is embedded in the LexerLang binary. A LexerLang application that writes use nardb opens the database without installing a server; the chain is NarQL itself.
use nardb, env_parser
$env = new env_parser(path.pwd + "/.env")
$db = nardb.connect('data/app.ndbc').credentials({user: 'root', pass: $env.require('NARDB_PASS')})
$members = $db.from('members').where({'age': {gte: 18}}).order({'name': 'asc'}).to_array()
$member = $db.from('members').where({'id': 123}).fetch_one().to_object()
$json = $db.from('members').fields('id', 'name').limit(10).to_json()NarQL → LexerLang#
Every step keeps its name. The only difference is the end: NarQL ends with .get();, in LexerLang you say which shape you want the result in.
| NarQL | LexerLang |
|---|---|
from('members', 'm') | $db.from('members', 'm') |
.where({'id': 1245, 'age': {gte: 18}}) | .where({'id': 1245, 'age': {gte: 18}}) |
.fields('id', 'name') | .fields('id', 'name') |
.fields(CAST('m.name', 'full_name'), FORMAT('birthday', 'yyyy-mm-dd')) | .fields(nardb.cast('m.name', 'full_name'), nardb.format('birthday', 'yyyy-mm-dd')) |
.order({'name': 'asc'}).limit(10).offset(20) | the same |
.get(); | .to_array() · .to_json() · .to_object() · .fetch_one() · .fetch_all() · .count() |
.insert_one({...}).get(); | .insert_one({...}) — returns the inserted row |
.insert_many([...]); | .insert_many([...]) |
.where({...}).update({...}).commit(); | .where({...}).update({...}) |
.where({...}).delete(); | .where({...}).delete() |
Writes run when called. .commit() may be written but, as in NarQL, does nothing extra — every statement is already one transaction.
Any NarQL script can also be run directly:
$r = $db.query("from('members').where({'email': $1}).fields('id').get();", $email)Connecting#
There are two modes; the chain is the same in both.
| Embedded | Server | |
|---|---|---|
| Data | a .ndbc folder next to the application | the data directory of nardb serve (~/.nardb/data) |
| Port | none | 7070 |
| When | one application, development | several processes; opening the shell while the app runs |
# embedded
$db = nardb.connect('data/app.ndbc').credentials({user: 'root', pass: $password})
$db = nardb.connect({path: 'data/app.ndbc', user: 'root', pass: $password})
# server
$db = nardb.connect('nardb://root@localhost:7070/shop').credentials({pass: $password})
$db = nardb.connect({host: 'localhost', port: 7070, user: 'root', pass: $password, db: 'shop'})| Option | Note |
|---|---|
path | path of the embedded database, relative to the app directory. Default data/app.ndbc |
create: false | fail if the embedded database is missing |
encryption, passphrase | encrypts a new embedded database |
host, port | the server; port defaults to 7070 |
db | the database's name on the server (shop → ~/.nardb/data/shop.ndbc); not a path |
user, pass | credentials; here or with .credentials() |
ca, cert, key | certificate files for a server on another machine |
Credentials are required#
A $db without credentials runs no queries:
nardb: once kimlik verin: $db.credentials({user: 'root', pass: ...})Every statement runs with the connected user's grants, in embedded mode too. root is admin on every schema; opening a narrower user for the application is a good habit:
nardb user add ./data/app.ndbc web
nardb grant ./data/app.ndbc web members write- First run: in a new embedded database without users, root is created with the password of the first
.credentials({user: 'root', pass: ...})call. To set it up beforehand:nardb create ./data/app.ndbc. - Speed: the password is verified with Argon2id once per process; later requests in the same process are compared against a salted hash in memory.
- No password in the address:
nardb://root:password@...and?pass=fail; addresses end up in logs and error messages. - Certificates: a server on the same machine needs no certificate files; the connection is still TLS. For another machine give
ca,cert,key(see Server).
Opened once per process#
An embedded database is opened once per process. LexerLang pages run again on every request; nardb.connect returns the same open database for the same path, and it is closed when the process stops. Because only one process can open a .ndbc at a time, NarDB Shell or nardb user ... cannot open it while the application runs; in that case serve it with nardb serve.
Types#
Results arrive typed (Result.Values in the Go API):
| Field | LexerLang |
|---|---|
| integers | int |
float32 / float64 | float |
bool | bool |
string, text, decimal | string |
date, datetime, time, timestamp | string, in the schema's format |
| empty field | null |
password_hash() field | "••••••" |
Security#
Values are not written into the NarQL text; they travel separately as $1, $2 parameters. Field names are quoted and escaped. A form value such as x'}).delete().all(); from('... stays a string to search for.
LexerLang-side details: lexer.gurerlabs.com — NarDB.