Checked spelling of REDME and ZiQL.md

This commit is contained in:
Adrien Bouvais 2024-10-14 00:15:18 +02:00
parent 7d2400b257
commit 8f1ce5462f
2 changed files with 92 additions and 97 deletions

View File

@ -2,34 +2,34 @@
# Introduction
ZipponDB is a relational database written entirely in Zig from scratch with 0 dependency.
ZipponDB is a relational database written entirely in Zig from scratch with 0 dependencies.
ZipponDB goal is to be ACID, light, simple and high performance. It aim small to medium application that don't need fancy features but a simple and reliable database.
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.
### Why Zippon ?
- Relational database (Soon)
- Simple and minimal query language
- Small, light, fast and implementable everywhere
- Small, light, fast, and implementable everywhere
# Quickstart
1. **Get a binary:** You can build the binary directly from the source code for any architecture (tuto is comming), or using the binary in the release (comming too).
2. **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. Then `database metrics` to see if it worked.
3. **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 and use this path,
this need to be the path to a directory with proper DATA, BACKUP and LOG directory.
4. **Attach a schema:** Once the database 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 running `schema describe`.
5. **Use the database:** You can now start using the database by sending query like that: `run "ADD User (name = 'Bob')"`.
1. **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).
2. **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. Then `db metrics` to see if it worked.
3. **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.
4. **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 running `schema describe`.
5. **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 use the current working directory as main directory so all path are a sub_path of it.***
***Note: For the moment, ZipponDB uses the current working directory as the main directory, so all paths are sub-paths of it.***
# Declare a schema
In ZipponDB you use structures, or struct for short, and not tables to organize how your data is store and manipulate. A struct have a name like `User` and members like `name` and `age`.
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 with inside a schema that describe all structs. Compared to SQL, you can see it as a file where you declare all table name, columns name, data type and relationship. All struct have an id of the type UUID by default.
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:
```lua
@ -42,7 +42,7 @@ User (
Note that the best friend is a link to another `User`.
Here a more advance example with multiple struct:
Here is a more advanced example with multiple structs:
```lua
User (
name: str,
@ -67,22 +67,21 @@ Comment (
)
```
Note: `[]` before the type mean a list/array of this type.
***Note: `[]` before the type means a list/array of this type.***
### Migration to a new schema - Not yet implemented
In the future, you will be able to update the schema like add a new member to a struct and update the database. For the moment, you can't change the schema once init.
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.
# ZipponQL
ZipponDB use it's own query language, ZipponQL or ZiQL for short. Here the keys point to remember:
- 4 actions available: `GRAB` `ADD` `UPDATE` `DELETE`
- All query start with an action then a struct name
- `{}` For filters
- `[]` For how much; what data
- `()` For new or updated data (Not already in file)
- `||` For additional options
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.***
@ -92,34 +91,34 @@ ZipponDB use it's own query language, ZipponQL or ZiQL for short. Here the keys
### GRAB
The main action is `GRAB`, this will parse files and return data.
The main action is `GRAB`, this will parse files and return data.
```js
GRAB User {name = 'Bob' AND (age > 30 OR age < 10)}
```
GRAB query return a list of JSON with the data inside, e.g:
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: [], }, ]
```
### ADD
The `ADD` action will add one entity into the database.
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.
```js
ADD User (name = 'Bob', age = 30, email = 'bob@email.com', scores = [1 100 44 82])
```
### DELETE
Similare to `GRAB` but delete all entity found using the filter.
Similar to `GRAB` but deletes all entities found using the filter and returns a list of deleted UUIDs.
```js
DELETE User {name = 'Bob'}
```
### UPDATE
A mix of `GRAB` and `ADD`. This take a filter first, then the new data.
Here we update the 5 first User named `bob` to add a capital and become `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 'adrien' to capitalize the name and become 'Adrien':
```js
UPDATE User [5] {name='bob'} TO (name = 'Bob')
```
@ -137,17 +136,17 @@ This include:
## Link query - Not yet implemented
You can also link query. Each query return a list of UUID of a specific struct. You can use it in the next query.
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`.
```js
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 have more than 2 queries. You can also just use one `=>` but the list of UUID is discard in that case.
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.
# Data types
Their is 5 data type for the moment:
There is 5 data types for the moment:
- `int`: 64 bit integer
- `float`: 64 bit float. Need to have a dot, `1.` is a float `1` is an integer.
- `bool`: Boolean, can be `true` or `false`
@ -159,19 +158,17 @@ Comming soon:
- `datetime`: A date time in yyyy/mm/dd/hh/mm/ss
- `time`: A time in hh/mm/ss
All data type can be an array of those type using [] in front of it. So []int is an array of integer.
All data types can be an array of those types using `[]` in front of it. So `[]int` is an array of integer.
All data type can also be `null`. Expect array that can only be empty.
All data types can also be `null`. Except arrays that can only be empty.
# Why I created it ?
Well the first reason is to learn, both zig and databases.
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 app on Fly.io but I often find myself struggelling to get a simple database.
I can either host it myself but I need to link my app and the db securely. Or use a cloud db service but that mean my db is far from my app.
All I want is to give to a Fly machine 10go of storage, do some backup on it and call it a day. But for that I need to include it to the Dockerfile of my app, what easier way than just a binary ?
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 my goal long term, to use it in my apps as a simple database that live WITH the app, sharing CPU and memory.
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.
# How does it work ?

110
ZiQL.md
View File

@ -1,21 +1,20 @@
# ZipponQL
ZipponDB use it's own query language, ZipponQL or ZiQL for short. Here the keys point to remember:
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
- By default, all members that are not links are returned
- To return links or only some members, specify them between []
- 4 actions available: `GRAB` `ADD` `UPDATE` `DELETE`
- All query start with an action then a struct name
- `{}` Are filters
- `[]` Are how much; what data
- `()` Are new or updated data (Not already in file)
- `||` Are additional options
- By default all member that are not link are return
- To return link or only some members, specify them between `[]`
***Disclaimer: A lot of features are still missing, and the language may change over time.***
***Disclaimer: Lot of stuff are still missing and the language may change over time.***
# Making errors
# Making erros
When you do an error writting ZiQL, you should see something like this to help you understand where you did a mistake:
When you make an error writing ZiQL, you should see something like this to help you understand where you made a mistake:
```
Error: Expected string
GRAB User {name = Bob}
@ -34,49 +33,49 @@ GRAB User {name = 'Bob' AND {age > 10}}
The main action is `GRAB`, this will parse files and return data.
Here how to return all `User` without any filtering:
Here's how to return all `User` entities without any filtering:
```js
GRAB User
```
To get all `User` above 18 years old:
To get all `User` entities above 18 years old:
```js
GRAB User {age > 18}
```
To only return the name of `User`:
To return only the `name` member of `User` entities:
```js
GRAB User [name] {age > 18}
```
To return the 10 first `User`:
To return the 10 first `User` entities:
```js
GRAB User [10] {age > 18}
```
You can use both:
You can combine these options:
```js
GRAB User [10; name] {age > 18}
```
Use multiple condition:
Use multiple conditions:
```js
GRAB User {name = 'Bob' AND (age > 30 OR age < 10)}
```
GRAB query return a list of JSON with the data inside, e.g:
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: [], }, ]
```
#### Not yet implemented
To order it using the name:
To order the results by `name`:
```js
GRAB User [10; name] {age > 10} |ASC name|
```
You can specify how much and what to return even for link inside struct. In this example I get 1 friend name for 10 `User`:
You can specify how much data to return and which members to include, even for links inside structs. In this example, I get 1 friend's name for 10 `User` entities:
```js
GRAB User [10; friends [1; name]]
```
@ -87,35 +86,35 @@ You can use the `IN` operator to check if something is in an array:
GRAB User { age > 10 AND name IN ['Adrien' 'Bob']}
```
This also work by using other filter. Here I get `User` that have a best friend named Adrien:
This also works by using other filters. Here I get `User` entities that have a best friend named Adrien:
```js
GRAB User { bestfriend IN { name = 'Adrien' } }
```
When using an array with IN, it will return all `User` that have at least ONE friend named Adrien:
When using an array with `IN`, it will return all User entities that have at least one friend named Adrien:
```js
GRAB User { friends IN { name = 'Adrien' } }
```
To get `User` with ALL friends named Adrien:
To get `User` entities with all friends named Adrien:
```js
GRAB User { friends ALLIN { name = 'Adrien' } }
```
You can use `IN` on itself. Here I get all `User` that liked a `Comment` that is from 2024. Both queries return the same thing:
You can use `IN` on itself. Here I get all `User` entities that liked a `Comment` from 2024. Both queries return the same result:
```js
GRAB User { IN Comment {at > '2024/01/01'}.like_by}
GRAB Comment.like_by { at > '2024/01/01'}
```
You can optain a similar result with this query but it will return a list of `Comment` with a member `liked_by` that is similar to `User` above. If you take all `liked_by` inside all `Comment`, it will be the same list but you can end up with duplicate as one `User` can like multiple `Comment`.
You can obtain a similar result with this query, but it will return a list of `Comment` entities with a `liked_by` member that is similar to the `User` entities above. If you take all `liked_by` members inside all `Comment` entities, it will be the same list, but you can end up with duplicates since one `User` can like multiple `Comment` entities.
```js
GRAB Comment [liked_by] {at > '2024/01/01'}
```
##### Return relationship
You can also return a relationship only. The filter will be done on `User` but will return `Comment`:
You can also return a relationship only. The filter will be applied to `User` entities, but will return `Comment` entities:
```js
GRAB User.comments {name = 'Bob'}
```
@ -125,35 +124,35 @@ You can do it as much as you like. This will return all `User` that liked commen
GRAB User.comments.like_by {name = 'Bob'}
```
This can also be use inside filter. Note that we need to specify `User` because it is a different struct that `Post`. Here I get all `Post` that have a comment from Bob:
This can also be used inside filters. Note that we need to specify `User` because it is a different struct than `Post`. Here, I get all `Post` entities that have a comment from Bob:
```js
GRAB Post {comments IN User{name = 'Bob'}.comments}
```
Can also do the same but only for the first Bob found:
You can also do the same but only for the first Bob found:
```js
GRAB Post {comments IN User [1] {name = 'Bob'}.comments}
```
Be carefull, this will return all `User` that liked a comment from 10 `User` named Bob:
Be careful; this will return all `User` entities that liked a comment from 10 `User` entities named Bob:
```js
GRAB User.comments.like_by [10] {name = 'Bob'}
```
To get 10 `User` that liked a comment from any `User` named Bob, you need to use:
To get 10 `User` entities that liked a comment from any `User` entity named Bob, you need to use:
```js
GRAB User.comments.like_by [comments [like_by [10]]] {name = 'Bob'}
```
##### Using !
You can use `!` to return the opposite. Use with `IN`, it check if it is NOT is the list. Use it with filters, it return entities that do not respect the filter.
You can use `!` to return the opposite. When used with `IN`, it checks if something is NOT in the list. When used with filters, it returns entities that do not match the filter.
This will return all `User` that didn't like a `Comment` in 2024:
This will return all `User` entities that didn't like a `Comment` in 2024:
```js
GRAB User { !IN Comment {at > '2024/01/01'}.like_by}
```
Be carefull because this do not return the same, it return all `User` that liked a `Comment` not in 2024:
Be careful because this does not return the same thing as above; it returns all `User` entities that liked a `Comment` not in 2024:
```js
GRAB Comment.like_by !{ at > '2024/01/01'}
```
@ -165,19 +164,18 @@ GRAB Comment.like_by { at < '2024/01/01'}
## ADD
The `ADD` action will add one entity into the database.
The synthax is similare but use `()`, this mean that the data is not yet in the database.
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.
Here an example:
Here's an example:
```js
ADD User (name = 'Bob', age = 30, email = 'bob@email.com', scores = [1 100 44 82])
```
You need to specify all member when adding an entity (default value are comming).
You need to specify all members when adding an entity (default values are coming soon).
#### Not yet implemented
ADD query return a list ids added, e.g:
The `ADD` query will return a list of added IDs, e.g.:
```
["1e170a80-84c9-429a-be25-ab4657894653", "1e170a80-84c9-429a-be25-ab4657894654", ]
```
@ -187,63 +185,63 @@ And you can also add them in batch
ADD User (name = 'Bob', age = 30, email = 'bob@email.com', scores = [1 100 44 82]) (name = 'Bob2', age = 33, email = 'bob2@email.com', scores = [])
```
You don't need to specify the member in the second entity as long as the order is respected.
You don't need to specify the members in the second entity as long as the order is respected:
```js
ADD User (name = 'Bob', age = 30, email = 'bob@email.com', scores = [1 100 44 82]) ('Bob2', 33, 'bob2@email.com', [])
```
## DELETE
Similare to `GRAB` but delete all entity found using the filter and return the list of UUID deleted.
Similar to `GRAB` but deletes all entities found using the filter and returns a list of deleted UUIDs.
```js
DELETE User {name = 'Bob'}
```
#### Not yet implemented
DELETE query return a list ids deleted, e.g:
The `DELETE` query will return a list of deleted IDs, e.g.:
```
["1e170a80-84c9-429a-be25-ab4657894653", "1e170a80-84c9-429a-be25-ab4657894654", ]
```
## UPDATE
A mix of `GRAB` and `ADD`. This take a filter first, then the new data.
Here we update the 5 first User named `adrien` to add a capital and become `Adrien`.
A mix of `GRAB` and `ADD`. It takes a filter first, then the new data.
Here, we update the first 5 `User` entities named 'adrien' to capitalize the name and become 'Adrien':
```js
UPDATE User [5] {name='adrien'} TO (name = 'Adrien')
```
Note that compared to `ADD`, you don't need to specify all member between `()`. Only the one specify will be updated.
Note that, compared to `ADD`, you don't need to specify all members between `()`. Only the ones specified will be updated.
#### Not yet implemented
UPDATE query return a list ids updated, e.g:
The `UPDATE` query will return a list of updated IDs, e.g.:
```
["1e170a80-84c9-429a-be25-ab4657894653", "1e170a80-84c9-429a-be25-ab4657894654", ]
```
You can use operations on itself too when updating:
You can use operations on values themselves when updating:
```js
UPDATE User {name = 'Bob'} TO (age += 1)
```
You can also manipulate array, like adding or removing values.
You can also manipulate arrays, like adding or removing values:
```js
UPDATE User {name='Bob'} TO (scores APPEND 45)
UPDATE User {name='Bob'} TO (scores APPEND [45 99])
UPDATE User {name='Bob'} TO (scores REMOVEAT [0 1 2])
```
For now there is 4 keywords to manipulate list:
- `APPEND`: Add value at the end of the list.
- `REMOVE`: Check the list and if the same value is found, delete it.
- `REMOVEAT`: Delete the value at a specific index.
- `CLEAR`: Remove all value in the array.
Currently, there will be four keywords for manipulating lists:
- `APPEND`: Adds a value to the end of the list.
- `REMOVE`: Checks the list, and if the same value is found, deletes it.
- `REMOVEAT`: Deletes the value at a specific index.
- `CLEAR`: Removes all values from the array.
Except `CLEAR` that take no value, each can use one value or an array of value, if chose an array it will perform the operation on all value in the array.
Except for `CLEAR`, which takes no value, each keyword can use one value or an array of values. If you choose an array, it will perform the operation on all values in the array.
For relationship, you can use filter on it:
For relationships, you can use filters:
```js
UPDATE User {name='Bob'} TO (comments APPEND {id = '000'})
UPDATE User {name='Bob'} TO (comments REMOVE { at < '2023/12/31'})