ZipponDB is a relational database written entirely in Zig from scratch with 0 dependencies.
ZipponDB's goal is to be ACID, light, simple, and high-performance. It aims at small to medium applications that don't need fancy features but a simple and reliable database.
- Relational database (Soon)
- Simple and minimal query language
- Small, light, fast, and implementable everywhere
Note: ZipponDB is still in Alpha v0.1 and is missing a lot of features, see roadmap at the end of this README.
- Get a binary: You can build the binary directly from the source code for any architecture (tutorial is coming), or using the binary in the release (coming too).
- Create a database: You can then run the binary, this will start a Command Line Interface. The first thing to do is to create a new database. For that, run the command
db new path/to/directory
, it will create a ZipponDB directory. Thendb metrics
to see if it worked. - Select a database: You can select a database by using
db use path/to/ZipponDB
. You can also set the environment variable ZIPPONDB_PATH, and it will use this path, this needs to be the path to a directory with proper DATA, BACKUP, and LOG directories. - Attach a schema: Once the database is created, you need to attach a schema to it (see next section for how to define a schema). For that, you can run
schema init path/to/schema.txt
. This will create new directories and empty files used to store data. You can test the current db schema by runningschema describe
. - Use the database: ou can now start using the database by sending queries like that:
run "ADD User (name = 'Bob')"
.
Note: For the moment, ZipponDB uses the current working directory as the main directory, so all paths are sub-paths of it.
In ZipponDB, you use structures, or structs for short, and not tables to organize how your data is stored and manipulated. A struct has a name like User
and members like name
and age
.
Create a file that contains a schema that describes all structs. Compared to SQL, you can see it as a file where you declare all table names, column names, data types, and relationships. All structs have an id of the type UUID by default.
Here an example of a file:
User (
name: str,
email: str,
best_friend: User,
)
Note that the best friend is a link to another User
.
Here is a more advanced example with multiple structs:
User (
name: str,
email: str,
friends: []User,
posts: []Post,
comments: []Comment,
)
Post (
title: str,
image: str,
at: date,
like_by: []User,
comments: []Comment,
)
Comment (
content: str,
at: date,
like_by: []User,
)
Note: []
before the type means a list/array of this type.
In the future, you will be able to update the schema, such as adding a new member to a struct, and update the database. For the moment, you can't change the schema once it's initialized.
ZipponDB uses its own query language, ZipponQL or ZiQL for short. Here are the key points to remember:
- 4 actions available:
GRAB
,ADD
,UPDATE
,DELETE
- All queries start with an action followed by a struct name
{}
are filters[]
specify how much and what data()
contain new or updated data (not already in the file)||
are additional options
Disclaimer: Lot of stuff are still missing and the language may change over time.
For more information see ZiQL Introduction
The main action is GRAB
, this will parse files and return data.
GRAB User {name = 'Bob' AND (age > 30 OR age < 10)}
GRAB queries return a list of JSON objects with the data inside, e.g:
[{id:"1e170a80-84c9-429a-be25-ab4657894653", name: "Gwendolyn Ray", age: 70, email: "austin92@example.org", scores: [ 77 ], friends: [], }, ]
The ADD
action adds one entity to the database. The syntax is similar to GRAB
, but uses ()
. This signifies that the data is not yet in the database.
ADD User (name = 'Bob', age = 30, email = 'bob@email.com', scores = [1 100 44 82])
Similar to GRAB
but deletes all entities found using the filter and returns a list of deleted UUIDs.
DELETE User {name = 'Bob'}
A mix of GRAB
and ADD
. It takes a filter first, then the new data.
Here, we update the first 5 User
entities named 'bob' to capitalize the name and become 'Bob':
UPDATE User [5] {name='bob'} TO (name = 'Bob')
A lot of things are not yet implemented, you can find examples in the ZiQL Introduction.
This include:
- Relationship
- Ordering
- Batch
- Array manipulation
- And more...
You can also link query. Each query returns a list of UUID of a specific struct. You can use it in the next query.
Here an example where I create a new Comment
that I then append to the list of comment of one specific User
.
ADD Comment (content='Hello world', at=NOW, like_by=[]) => added_comment => UPDATE User {id = '000'} TO (comments APPEND added_comment)
The name between =>
is the variable name of the list of UUID used for the next queries, you can have multiple one if the link has more than 2 queries. You can also just use one =>
but the list of UUID is discarded in that case.
There is 5 data types for the moment:
int
: 64 bit integerfloat
: 64 bit float. Need to have a dot,1.
is a float1
is an integer.bool
: Boolean, can betrue
orfalse
string
: Character array between''
UUID
: Id in the UUID format, used for relationship, ect. All struct have an id member.
Comming soon:
date
: A date in yyyy/mm/dddatetime
: A date time in yyyy/mm/dd/hh/mm/sstime
: A time in hh/mm/ss
All data types can be an array of those types using []
in front of it. So []int
is an array of integer.
All data types can also be null
. Except arrays that can only be empty.
Well, the first reason is to learn both Zig and databases.
The second is to use it in my apps. I like to deploy Golang + HTMX apps on Fly.io, but I often find myself struggling to get a simple database. I can either host it myself, but then I need to link my app and the database securely. Or I can use a cloud database service, but that means my database is far from my app. All I want is to give a Fly machine 10GB of storage, do some backups on it, and call it a day. But for that, I need to include it in the Dockerfile of my app. What easier way than just a binary?
So that's my long-term goal: to use it in my apps as a simple database that lives with the app, sharing CPU and memory.
TODO: Create a tech doc of what is happening inside.
Note: This will probably evolve over time.
- UUID
- CLI
- Tokenizers
- ZiQL parser
- Schema engine
- File engine
- B+Tree
- Relationships
- Date
- Linked query
- Docker
- Schema migration
- Dump/Bump data
- Recovery
- Better CLI
- Logs
- Server
- Config file
- Python interface
- Go interface
- In memory option
- Cache
- Transaction
- Multi threading
- Lock manager
- Optimized data file
- Auth
- Metrics
- Durability
- Query optimizer
- ZiQL tuto
- Deployment tuto
- Code docs
- CLI help
- Query builder
- Tables
- Schema visualization
- Dashboard metrics
Let's see where it (or my brain) start explode ;)