LunaQL - initial draft

Back

Updated: Fri Jul 16 2021

Introduction

LunaQL or Luna Query, is a Query Language for MoanaDB (a NoSQL database). It is designed to handle document reads, writes, and deletes efficiently. It also allows MoanaDB (a NoSQL database) to be treated as relational database.

This initial draft will outline how queries are written and handled by MoanaDB.

Overview

LunaQL is a query language designed to work on both frontends and backends.

For example, lets say your frontend application has a "feed" view, you can have something like this:

query({
from({
/**
* Fetch the most recent 20 posts.
*
* @var post[]
*/
posts: {
sort: "desc";
with: {
/**
* Get the authors of the posts.
*
* @var user[]
*/
users: {
select: ["_id", "name"];
where: ["user_id", "=", "$._id"];
};
};
limit: 20;
do: "fetch";
};
});
});

Resuls

[
{
_id: 1,
user_id: 1,
body: "This is a post",
created_at: "2021-07-16 20:50:08",
updated_at: "2021-07-16 20:50:08",
users: [
{
_id: 1,
name: "Donald Pakkies"
}
]
},
...

This query will fetch the first 20 posts with the names of the users (authors) who created those posts from the database, and will also order the posts by most recent.

Now, if we need we need to make changes to our database. We can instruct our backend to make the changes we want:

query({
from({
/**
* Create a new post and assign it to user 1.
*/
posts: {
data: {
user_id: 1;
body: "This is a cool post";
};
do: "put";
};
});
});

Resuls

{
_id: 2,
user_id: 1,
body: "This is a cool post",
created_at: "2021-07-16 20:51:32",
updated_at: "2021-07-16 20:51:32",
}

This query will insert a new document in the posts collection.

Its important to note that, you will need to have a read-only MoanaDB key for your frontend and a full-access MoanaDB key for your backend. This will just ensure that your frontend can't make changes to your database.

Writing Queries

LunaQL queries are self-describing and self-documenting.

A simple query looks a lot like this:

query({
from({
users: {
do: "fetch";
};
});
});

This query tells MoanaDB to fetch users from the users collection.

MoanaDB treats do: "fetch" as an action.

Conditions

Now that we know how to write a simple query, we can start looking at how we can write conditional queries.

LunaQL provides, the where expression which allows us to write or provide our conditions:

query({
from({
users: {
where: [
[ "age", ">", 17 ]
];
do: "fetch";
};
});
});

Notice how we passed our condition inside an array. This is because we can provide multiple conditions:

query({
from({
users: {
where: [
[ "name", "like", "Donald" ],
[ "email", "like", "@gmail.com" ]
];
do: "fetch";
};
});
});

We can also group our conditions:

query({
from({
users: {
where: [
[ "name", "like", "Donald" ],
[ "email", "like", "@gmail.com" ]
];
orWhere: [
[ "name", "like", "Luna" ],
[ "email", "like", "@gmail.com" ]
];
do: "fetch";
};
});
});

This query will fetch gmail users who have a name that contain "Donald" or "Luna".

We can also limit our results:

query({
from({
users: {
where: [
[ "name", "like", "Donald" ],
[ "email", "like", "@gmail.com" ]
];
limit: 1;
do: "fetch";
};
});
});

This query will only fetch one user.

Supported operators

The where and orWhere expressions support the following operators:

Equals (case insensitive / type safe):

query({
from({
users: {
where: [
[ "age", "=", "20" ]
];
do: "fetch";
};
});
});

Equals (case sensitive / type unsafe):

query({
from({
users: {
where: [
[ "age", "===", 20 ]
];
do: "fetch";
};
});
});

Not Equals (case insensitive / type safe):

query({
from({
users: {
where: [
[ "age", "!=", "20" ]
];
do: "fetch";
};
});
});

Not Equals (case sensitive / type unsafe):

query({
from({
users: {
where: [
[ "age", "!==", "20" ]
];
do: "fetch";
};
});
});

Greater Than:

query({
from({
users: {
where: [
[ "age", ">", 20 ]
];
do: "fetch";
};
});
});

Greater Than or Equals:

query({
from({
users: {
where: [
[ "age", ">=", 20 ]
];
do: "fetch";
};
});
});

Less Than:

query({
from({
users: {
where: [
[ "age", "<", 20 ]
];
do: "fetch";
};
});
});

Less Than or Equals:

query({
from({
users: {
where: [
[ "age", "<=", 20 ]
];
do: "fetch";
};
});
});

Like:

query({
from({
users: {
where: [
[ "name", "like", "Luna" ]
];
do: "fetch";
};
});
});

Not Like:

query({
from({
users: {
where: [
[ "name", "not like", "Luna" ]
];
do: "fetch";
};
});
});

In Array:

query({
from({
users: {
where: [
[
"name", "in", [
"Donald,
"Luna"
]
]
];
do: "fetch";
};
});
});

Not In Array:

query({
from({
users: {
where: [
[
"name", "not in", [
"Donald,
"Luna"
]
]
];
do: "fetch";
};
});
});

Contains:

query({
from({
users: {
where: [
[ "name", "contains", "Luna" ]
];
do: "fetch";
};
});
});

Contains:

query({
from({
users: {
where: [
[ "name", "not contains", "Don" ]
];
do: "fetch";
};
});
});

Between:

query({
from({
users: {
where: [
[
"created_at", "between", [
2020,
2021
]
]
];
do: "fetch";
};
});
});

Not Between:

query({
from({
users: {
where: [
[
"created_at", "not between", [
2020,
2021
]
]
];
do: "fetch";
};
});
});

Actions

Here's a list of actions we can invoke:

Examples

Count documents:

query({
from({
posts: {
do: "count";
};
});
});

Delete documents:

query({
from({
posts: {
where: [
[ "_id:", "=", 1 ]
];
do: "delete";
};
});
});

Fetch documents:

query({
from({
posts: {
do: "fetch";
};
});
});

Create documents:

query({
from({
users: {
data: {
name: "Donald Pakkies;
email: "donaldpakkies@gmail.com";
};
do: "put";
};
});
});

Update documents:

query({
from({
users: {
where: [
[ "_id", "=", 1 ]
];
data: {
name: "Donald;
};
do: "update";
};
});
});

Relationships

LunaQL has the ability to join 2 documents from 2 different collections:

query({
from({
users: {
with: {
posts: {
where: [ "user_id", "=", "$._id" ];
};
};
do: "fetch";
};
});
})

$ represents the parent collection (users in this query).

This query will fetch all users with their posts. This is done by using the with expression which expects a where condition.

Thanks for reading, i will keep updating this.

© Donald Pakkies.
github