Merge branch 'engine/v2' into next
commit
2b9a3ca6c1
@ -1,29 +0,0 @@
|
||||
name: Update benches (release)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
request-bench:
|
||||
name: Add request for updating bench
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone perf repo
|
||||
run: git clone https://github.com/skytable/perf.git
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.name "Glydr"
|
||||
git config --global user.email "${{ secrets.GLYDR_MAIL }}"
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Add request and push
|
||||
env:
|
||||
REL: ${{ env.RELEASE_VERSION }}
|
||||
run: |
|
||||
cd perf
|
||||
printf "\ntarget/release/skyreport update release $REL" >> requests.txt
|
||||
git add .
|
||||
git commit -m "Add update request for release ``$REL``"
|
||||
git push https://glydr:${{ secrets.GLYDR }}@github.com/skytable/perf.git --all
|
@ -1,34 +0,0 @@
|
||||
name: Rebuild docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
env:
|
||||
IS_ACTIONS_DOC: "false"
|
||||
|
||||
jobs:
|
||||
rebuild-docs:
|
||||
name: Build new actiondoc
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
chmod +x ci/setvars.sh
|
||||
ci/setvars.sh
|
||||
|
||||
- name: Publish docs
|
||||
env:
|
||||
BOT_USER: ${{ secrets.BOT_INIT_USER }}
|
||||
BOT_MAIL: ${{ secrets.BOT_INIT_MAIL }}
|
||||
BOT_API: ${{ secrets.BOT_API_CALL }}
|
||||
GIT_SHA: ${{ env.GITHUB_SHA }}
|
||||
run: |
|
||||
chmod +x ci/doc.sh
|
||||
ci/doc.sh
|
||||
if: env.IS_ACTIONS_DOC == 'true'
|
@ -0,0 +1,18 @@
|
||||
----
|
||||
Notes for v0.8.0
|
||||
----
|
||||
|
||||
I'd like to thank a lot of the people that have very indirectly had some influence on Skytable's design. Here's a little list (in no
|
||||
particular order):
|
||||
|
||||
- First of all I'd like to thank Raymond F. Boyce and Donald D. Chamberlin for their work on SQL. While I'm not fortunate enough to have
|
||||
any connection to them, a lot of their work have laid out guiding principles for my work.
|
||||
- I'd also like to thank several people from the Rust community (listed in no particular order):
|
||||
- Aaron Turon: Aaron's work on concurrency libraries have greatly helped in parts of "design thinking"
|
||||
- Carl Lerche (@carllerche): For the immense amount of work Carl done on Tokio and related systems.
|
||||
- Michael Vaner (@vorner): For their work on designing concurrency primitives. I'm a great admirer of Michael's work but
|
||||
unfortunately haven't had the opportunity to directly talk.
|
||||
- Amanieu d'Antras (@Amanieu): Amanieu's work on parking_lot and hashbrown have been eye openers for several things that I've designed
|
||||
and implemented, both in and out of Skytable
|
||||
|
||||
-- Sayan N. (Dec, 2023 <ohsayan@outlook.com>)
|
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,16 @@
|
||||
#
|
||||
# The Dockerfile for the Skytable server sdb
|
||||
#
|
||||
|
||||
FROM debian:stable
|
||||
|
||||
# Copy the necessary binaries
|
||||
COPY target/release/skyd /usr/local/bin
|
||||
COPY target/release/skysh /usr/local/bin
|
||||
RUN mkdir /etc/skytable
|
||||
# Create necessary directories
|
||||
RUN mkdir /var/lib/skytable
|
||||
COPY examples/config-files/docker.toml /etc/skytable/skyd.toml
|
||||
COPY examples/config-files/dpkg/config.yaml /var/lib/skytable/config.yaml
|
||||
COPY pkg/docker/start-server.sh /usr/local/bin/start-server.sh
|
||||
WORKDIR /var/lib/skytable
|
||||
CMD ["skyd", "-c", "/etc/skytable/skyd.toml"]
|
||||
# Install uuidgen for generating a random password
|
||||
RUN apt-get update && apt-get install -y uuid-runtime
|
||||
RUN chmod +x /usr/local/bin/start-server.sh
|
||||
ENTRYPOINT ["/usr/local/bin/start-server.sh"]
|
||||
EXPOSE 2003/tcp
|
||||
|
@ -1,82 +1,125 @@
|
||||
<html>
|
||||
<div align="center">
|
||||
<img src="assets/logo.jpg" height=64 width=64>
|
||||
<h1>Skytable</h1><h3>Your next NoSQL database</h3>
|
||||
<br/><p align="center">
|
||||
<img width="150" src="assets/logo.jpg">
|
||||
</p>
|
||||
<h2 align = "center">
|
||||
Skytable — A modern database for building powerful experiences
|
||||
</h2>
|
||||
<h3 align="center">
|
||||
Performance, scalability and flexibility. Choose three.
|
||||
</h3>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/skytable/skytable/releases"><img src="https://img.shields.io/github/v/release/skytable/skytable?style=flat" alt="GitHub release (with filter)"></a> <a href="https://github.com/skytable/skytable/actions"><img src="https://img.shields.io/github/actions/workflow/status/skytable/skytable/test-push.yml?style=flat" alt="GitHub Workflow Status (with event)"></a> <a href="https://discord.gg/QptWFdx"><img src="https://img.shields.io/discord/729378001023926282?logo=discord&style=flat" alt="Discord"></a> <a href="https://docs.skytable.io"><img src="https://img.shields.io/badge/read%20the%20docs-here-blue?style=flat" alt="Docs"></a> <a href="https://github.com/skytable/skytable/discussions?style=flat"><img src="https://img.shields.io/badge/discuss-here-8A3324?style=flat&logo=github&labelColor=C34723" alt="Static Badge"></a>
|
||||
</p>
|
||||
|
||||
![GitHub Workflow Status](<https://img.shields.io/github/actions/workflow/status/skytable/skytable/test-push.yml?branch=next>) ![Development](https://img.shields.io/badge/development-regular-32CD32?style=flat-square) ![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/skytable/skytable?include_prereleases&sort=semver&style=flat-square)
|
||||
[![Docs](https://img.shields.io/badge/readthedocs-here-blueviolet?style=flat-square)](https://docs.skytable.io) [![Contribute Now](https://img.shields.io/badge/%F0%9F%8C%9Fcontribute-now-a94064)](https://ohsayan.github.io/skythanks) [![Discord](https://img.shields.io/badge/talk-on%20discord-7289DA?logo=discord&style=flat-square")](https://discord.gg/QptWFdx)
|
||||
## 💡 What is Skytable?
|
||||
|
||||
</div>
|
||||
</html>
|
||||
Skytable is a **modern NoSQL database** that focuses on **performance, flexibility and scalability**. Our goal is to deliver **a rock-solid database** that you can use as the foundation for your next application — **minus the gimmicks**.
|
||||
|
||||
## What is Skytable?
|
||||
Skytable makes every attempt to **remove the gotchas from SQL-systems**. For example, nonempty `model`s and `space`s cannot be dropped and **BlueQL is designed to greatly deter SQL injection vulnerabilities** with a new **mandatory parameterization** design and several other **secure query language design principles**.
|
||||
|
||||
Skytable is a free and open-source NoSQL database that aims to provide flexible data modeling at
|
||||
scale. For us simplicity, performance and flexibility are our guiding design principles.
|
||||
We were previously known as TerrabaseDB or Skybase and are nicknamed Sky, SDB or STable by the community.
|
||||
Every component in Skytable has been **engineered from the ground up** to meet our design goals. **Skytable uses BlueQL<sup>TM</sup>** which is our own **new in-house query language** designed from the ground up for a **clean, powerful, modern and secure querying experience** and is **generally more secure than SQL**.
|
||||
|
||||
Features like keyspaces, tables, data types, authn+authz, snapshots and more are ready for you to use while we're working on [several new data models and features](https://github.com/skytable/skytable/issues/203). Skytable's key/value store is performant, secure and ready for you to deploy.
|
||||
Skytable works with **structured and semi-structured data**. We're currently working on supporting unstructured data.
|
||||
|
||||
## Getting started 🚀
|
||||
> **You can read more about Skytable's architecture, including information on the clustering and HA implementation that we're currently working on, and limitations [on this page](https://docs.skytable.io/architecture).**
|
||||
|
||||
1. Download a bundle for your platform from [here ⬇️ ](https://github.com/skytable/skytable/releases)
|
||||
2. Unzip the bundle
|
||||
3. Make the files executable (run `chmod +x skyd skysh` on \*nix systems)
|
||||
4. First run `skyd` to start the database server and then run `skysh` to start the interactive shell
|
||||
5. Run commands like: `SET foo bar` , `GET bar` , `UPDATE cat mitten` or `DEL proprietary` on `skysh`!
|
||||
## 🎨 Features
|
||||
|
||||
You can learn more about installation [here](https://docs.skytable.io/getting-started)
|
||||
- **Spaces, models and more**: For flexible data definition
|
||||
- **Powerful querying with BlueQL**: A modern query language, designed for the 21<sup>st</sup> century
|
||||
- **Rich data modeling**: Use `model`s to define data with complex types, collections and more
|
||||
- **Performant, in and out of the box**: Heavily multithreaded and optimized
|
||||
- **Secure, query in and response out**: BlueQL is designed to strongly deter query injection pathways
|
||||
- **SQL minus the gotchas**: Ever done a `DROP TABLE users` and lost all data? **That won't happen in Skytable**.
|
||||
- **Designed to scale by enforcing best practices**: If you're building with Skytable today, the practices you'll learn here will let you easily take on the job of building large scale systems
|
||||
|
||||
## Features
|
||||
> Learn more about [Skytable's features here](https://docs.skytable.io).
|
||||
|
||||
- **Insanely fast**: Scale to millions of queries per second per node. See [benchmarks here](https://github.com/ohsayan/sky-benches).
|
||||
- **Multiple keyspaces/tables**: Seamlessly integrates with actions to provide a SQL-like experience
|
||||
- **Key/value store**: `GET` , `SET` , `UPDATE` and [all that stuff](https://docs.skytable.io/all-actions). With the `str` and `binstr` types.
|
||||
- **Authn/Authz**: Simple and secure authentication/authorization
|
||||
- **Volatile tables**: For all the caching you need
|
||||
- **Snapshots**: Automated (and tunable) snapshots for stress-free backups
|
||||
- **Secure**: Secure connections are built into Skytable with SSL/TLS
|
||||
- **Multithreaded**: Designed to exploit all CPU cores
|
||||
- **Resource friendly**: The database server doesn't need more than 1MB to run
|
||||
- **Convenient**: Without the setup hassle and system-specific dependencies
|
||||
## 🚀 Getting started
|
||||
|
||||
**🛣️ There's a lot more coming! View our [roadmap](https://github.com/skytable/skytable/issues/203)**
|
||||
1. **Set up Skytable on your machine**: You'll need to download a bundled release file [from the releases page](https://github.com/skytable/skytable/releases). Unzip the files and you're ready to go.
|
||||
2. Start the database server: `./skyd --auth-root-password <password>` with your choice of a password for the `root` account. The `root` account is just like a `root` account on Unix based systems that has control over everything.
|
||||
3. Start the interactive client REPL: `./skysh` and then enter your password.
|
||||
4. You're ready to run queries!
|
||||
|
||||
## Clients 🔌
|
||||
> **For a more detailed guide on installation and deployment, [follow the guide here.](https://docs.skytable.io/installation)**
|
||||
|
||||
The project currently maintains an official [Rust driver](https://github.com/skytable/client-rust) and we have plans
|
||||
to support more languages along the way!
|
||||
We also maintain a list of [community supported drivers here](https://github.com/skytable/skytable/wiki/Drivers).
|
||||
|
||||
If you want to use a different language, for now you'll just need to implement the simple and performant [Skyhash Protocol](https://docs.skytable.io/protocol/skyhash).
|
||||
## ⚡ Using Skytable
|
||||
|
||||
## Community 👐
|
||||
Skytable has `SPACE`s instead of `DATABASE`s due to signficant operational differences (and because `SPACE`s store a lot more than tabular data).
|
||||
|
||||
A project which is powered by the community believes in the power of community! If you get stuck anywhere - here are your options!
|
||||
**With the REPL started, follow this guide**:
|
||||
|
||||
<html>
|
||||
<a href="https://gitter.im/skytable/community"><img src="https://img.shields.io/badge/chat%20on-gitter-ed1965?logo=gitter&style=flat-square"></img>
|
||||
</a><a href="https://discord.gg/QptWFdx"><img src="https://img.shields.io/badge/talk-on%20discord-7289DA?logo=discord&style=flat-square"></img></a>
|
||||
</html>
|
||||
1. Create a `space` and switch to it:
|
||||
```sql
|
||||
CREATE SPACE myspace
|
||||
USE myspace
|
||||
```
|
||||
2. Create a `model`:
|
||||
```sql
|
||||
CREATE MODEL myspace.mymodel(username: string, password: string, notes: list { type: string })
|
||||
```
|
||||
The rough representation for this in Rust would be:
|
||||
```rust
|
||||
pub struct MyModel {
|
||||
username: String,
|
||||
password: Strin,
|
||||
notes: Vec<String>,
|
||||
}
|
||||
```
|
||||
3. `INSERT` some data:
|
||||
```sql
|
||||
INSERT INTO mymodel('sayan', 'pass123', [])
|
||||
```
|
||||
4. `UPDATE` some data:
|
||||
```sql
|
||||
UPDATE mymodel SET notes += "my first note" WHERE username = 'sayan'
|
||||
```
|
||||
5. `SELECT` some data
|
||||
```sql
|
||||
SELECT * FROM mymodel WHERE username = 'sayan'
|
||||
```
|
||||
6. Poke around! **And then make sure you [read the documentation learn BlueQL](https://docs.skytable.io/blueql/overview).**
|
||||
|
||||
## Platforms 💻
|
||||
> **For a complete guide on Skytable, it's architecture, BlueQL, queries and more we strongly recommend you to [read the documentation here.](https://docs.skytable.io)**
|
||||
>
|
||||
> While you're seeing strings and other values being used here, this is so because the REPL client smartly parameterizes queries behind the scenes. **BlueQL has mandatory parameterization**. (See below to see how the Rust client handles this)
|
||||
|
||||
![Linux supported](https://img.shields.io/badge/Linux%2032--bit%2F64--bit-Supported%20✓-%23228B22?logo=linux) ![macOS supported](https://img.shields.io/badge/macOS%20x86__64%2Farm64-supported%20✓-228B22?style=flat-square&logo=apple) ![Windows supported](https://img.shields.io/badge/Windows%2032--bit%2F64--bit-supported%20✓-228B22?style=flat-square&logo=windows)
|
||||
## 🧩 Find a client driver
|
||||
|
||||
## Versioning
|
||||
You need a client driver to use Skytable in your programs. Officially, we maintain a regularly updated [Rust client driver](https://github.com/skytable/client-rust) which is liberally license under the Apache-2.0 license so that you can use it anywhere.
|
||||
|
||||
This project strictly follows semver, however, since this project is currently in the development phase (0.x.y), the API may change unpredictably
|
||||
Using the Rust client driver, it's very straightforward to run queries thanks to Rust's powerful type system and macros:
|
||||
|
||||
## Contributing
|
||||
```rust
|
||||
use skytable::{Config, query};
|
||||
|
||||
fn main() {
|
||||
let mut db = Config::new_default("username", "password").connect().unwrap();
|
||||
let query = query!("select username, password from myspace.mymodel where username = ?", "sayan");
|
||||
let (username, password): (String, Vec<u8>) = db.query_parse(&query).unwrap();
|
||||
// do something with it!
|
||||
}
|
||||
```
|
||||
|
||||
> **You can find more information on client drivers on [this page](https://docs.skytable.io/libraries). If you want to help write a client driver for your language of choice, *we're here to support your work*. Please reach out to: hey@skytable.io or leave a message on our Discord server!**
|
||||
|
||||
## 🙋 Getting help
|
||||
|
||||
[![Contribute Now](https://img.shields.io/badge/%F0%9F%8C%9Fcontribute-now-a94064?style=for-the-badge)](https://ohsayan.github.io/skythanks)
|
||||
We exclusively use [Discord](https://discord.gg/QptWFdx) for most real-time communications — you can chat with developers, maintainers, and our amazing users! Outside that, we recommend that you use our [GitHub Discussions page](https://github.com/skytable/skytable/discussions) for any questions or open a new issue if you think you've found a bug.
|
||||
|
||||
*We're here to help!*
|
||||
|
||||
## Contributing
|
||||
|
||||
You are welcome to contribute to Skytable! Beginner friendly issues are marked with the [<img src=https://img.shields.io/badge/L--easy-C71585>](https://github.com/skytable/skytable/labels/L-easy) label. Read the guide [here](./CONTRIBUTING.md).
|
||||
Please read the [contributing guide here](./CONTRIBUTING.md).
|
||||
|
||||
## Contributors
|
||||
## Acknowledgements
|
||||
|
||||
You can see a full list of contributors [here](https://ohsayan.github.io/skythanks)
|
||||
Please read the [acknowledgements](./ACKNOWLEDGEMENTS.txt) document.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [AGPL-3.0 License](./LICENSE).
|
||||
Skytable is distributed under the [AGPL-3.0 License](./LICENSE). **You may not use Skytable's logo for other projects.**
|
||||
|
@ -1,380 +0,0 @@
|
||||
#
|
||||
# Created on Thu Aug 27 2020
|
||||
#
|
||||
# This file is a part of Skytable
|
||||
# Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
# NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
# vision to provide flexibility in data modelling without compromising
|
||||
# on performance, queryability or scalability.
|
||||
#
|
||||
# Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
#
|
||||
# This file is used by Skytable's documentation website for automatically
|
||||
# generating documentation for the actions. It will also be used by the Skytable
|
||||
# server in the future
|
||||
|
||||
# the docbuilder expects:
|
||||
# 'name': str, 'complexity': str, 'accept': [str]
|
||||
# 'return': [str], 'syntax': [str], 'desc': str
|
||||
|
||||
global:
|
||||
- name: HEYA
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [HEYA, HEYA <message>]
|
||||
desc: |
|
||||
Either returns a "HEY!" or returns the provided argument as an `str`
|
||||
return: [String]
|
||||
- name: DBSIZE
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [DBSIZE, DBSIZE <entity>]
|
||||
desc: Check the number of entries stored in the current table or in the provided entity
|
||||
return: [Integer]
|
||||
- name: MKSNAP
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [MKSNAP, MKSNAP <SNAPNAME>]
|
||||
desc: |
|
||||
This action can be used to create a snapshot. Do note that this action **requires
|
||||
snapshotting to be enabled on the server side**, before it can create snapshots.
|
||||
If you want to create snapshots **without** snapshots being enabled on the server-side,
|
||||
pass a second argument `<SNAPNAME>` to specify a snapshot name and a snapshot will
|
||||
be create in a folder called `rsnap` under your data directory. For more
|
||||
information on snapshots, read [this document](/snapshots)
|
||||
return: [Rcode 0, err-snapshot-disabled, err-snapshot-busy]
|
||||
- name: FLUSHDB
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [FLUSHDB, FLUSHDB <entity>]
|
||||
desc: Removes all entries stored in the current table or in the provided entity
|
||||
return: [Rcode 0, Rcode 5]
|
||||
- name: WHEREAMI
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [WHEREAMI]
|
||||
desc: |
|
||||
Returns an array with either the name of the current keyspace as the first element or if a default table
|
||||
is set, then it returns the keyspace name as the first element and the table name as the second element
|
||||
return: [Non-null array]
|
||||
- name: AUTH
|
||||
desc: Change global authn/authz settings
|
||||
subactions:
|
||||
- name: LOGIN
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH LOGIN <username> <token>]
|
||||
desc: Attempts to log in using the provided credentials
|
||||
return: [Rcode 0, Rcode 10]
|
||||
- name: CLAIM
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH CLAIM <origin key>]
|
||||
desc: Attempts to claim the root account using the origin key
|
||||
return: [String, Rcode 10]
|
||||
- name: LOGOUT
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH LOGOUT]
|
||||
desc: Attempts to log out the currently logged in user
|
||||
return: [Rcode 0, Rcode 10]
|
||||
- name: ADDUSER
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH ADDUSER <username>]
|
||||
desc: Attempts to create a new user with the provided username, returning the token
|
||||
return: [String, Rcode 11]
|
||||
- name: DELUSER
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH DELUSER <username>]
|
||||
desc: Attempts to delete the user with the provided username
|
||||
return: [Rcode 0, Rcode 10, Rcode 11]
|
||||
- name: RESTORE
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH RESTORE <username>, AUTH RESTORE <origin-key> <username>]
|
||||
desc: |
|
||||
Attempts to restore the password for the provided user. This will regenerate the token
|
||||
and return the newly issued token. However, if you aren't a root account, that is, you
|
||||
lost your root password, then you'll need to run `AUTH RESTORE <origin-key> root`.
|
||||
return: [String, Rcode 10, Rcode 11]
|
||||
- name: LISTUSER
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH LISTUSER]
|
||||
desc: |
|
||||
Attempts to return a list of users for the current database instance
|
||||
return: [Non-null array]
|
||||
- name: WHOAMI
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [AUTH WHOAMI]
|
||||
desc: |
|
||||
Returns a string with the AuthID of the currently logged in user or errors if the user
|
||||
is not logged in
|
||||
return: [String]
|
||||
- name: SYS
|
||||
desc: |
|
||||
Get system information and metrics
|
||||
subactions:
|
||||
- name: INFO
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [sys info <property>]
|
||||
return: [String, Float]
|
||||
desc: |
|
||||
Returns static properties of the system, i.e properties that do not change during runtime.
|
||||
The following properties are available:
|
||||
- `version`: Returns the server version (String)
|
||||
- `protocol`: Returns the protocol version string (String)
|
||||
- `protover`: Returns the protocol version (float)
|
||||
- name: METRIC
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [sys metric <metric>]
|
||||
return: [String, Float]
|
||||
desc: |
|
||||
Returns dynamic properties of the system, i.e metrics are properties that can change during
|
||||
runtime. The following metrics are available:
|
||||
- `health`: Returns "good" or "critical" depending on the system state (String)
|
||||
- `storage`: Returns bytes used for on-disk storage (uint64)
|
||||
|
||||
keyvalue:
|
||||
generic:
|
||||
- name: DEL
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [DEL <key1> <key2> ...]
|
||||
desc: |
|
||||
Delete 'n' keys from the current table. This will return the number of keys that were deleted
|
||||
as an unsigned integer
|
||||
return: [Integer, Rcode 5]
|
||||
- name: EXISTS
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [EXISTS <key1> <key2> ...]
|
||||
desc: |
|
||||
Check if 'n' keys exist in the current table. This will return the number of keys that exist
|
||||
as an unsigned integer.
|
||||
return: [Integer]
|
||||
- name: LSKEYS
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [LSKEYS <limit>, LSKEYS <entity>, LSKEYS <entity> <limit>]
|
||||
desc: |
|
||||
Returns a flat string array of keys present in the current table or in the provided entity.
|
||||
If no `<limit>` is given, then a maximum of 10 keys are returned. If a limit is specified,
|
||||
then a maximum of `<limit>` keys are returned. The order of keys is meaningless.
|
||||
return: [Typed Array]
|
||||
string:
|
||||
- name: GET
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [GET <key>]
|
||||
desc: Get the value of a key from the current table, if it exists
|
||||
return: [Rcode 1, String, Binstr]
|
||||
- name: MGET
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [MGET <key1> <key2> ...]
|
||||
desc: Get the value of 'n' keys from the current table, if they exist
|
||||
return: [Typed Array]
|
||||
- name: SET
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [SET <key> <value>]
|
||||
desc: Set the value of a key in the current table, if it doesn't already exist
|
||||
return: [Rcode 0, Rcode 2, Rcode 5]
|
||||
- name: MSET
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [MSET <key1> <value1> <key2> <value2> ...]
|
||||
desc: |
|
||||
Set the value of 'n' keys in the current table, if they don't already exist. This will
|
||||
return the number of keys that were set as an unsigned integer.
|
||||
return: [Integer, Rcode 5]
|
||||
- name: UPDATE
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [UPDATE <key> <value>]
|
||||
desc: Update the value of an existing key in the current table
|
||||
return: [Rcode 0, Rcode 1, Rcode 5]
|
||||
- name: MUPDATE
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [MUPDATE <key1> <value1> <key2> <value2> ...]
|
||||
desc: |
|
||||
Update the value of 'n' keys in the current table, if they already exist. This will return
|
||||
the number of keys that were updated as an unsigned integer.
|
||||
return: [Integer, Rcode 5]
|
||||
- name: SSET
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [SSET <key1> <value1> <key2> <value2> ...]
|
||||
desc: Set all keys to the given values only if all of them don't exist in the current table
|
||||
return: [Rcode 0, Rcode 2, Rcode 5]
|
||||
- name: SDEL
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [SDEL <key1> <key2> ...]
|
||||
desc: |
|
||||
Delete all keys if all of the keys exist in the current table. Do note that if a single key doesn't
|
||||
exist, then a `Nil` code is returned.
|
||||
return: [Rcode 0, Rcode 1, Rcode 5]
|
||||
- name: SUPDATE
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [SUPDATE <key1> <value1> <key2> <value2> ...]
|
||||
desc: |
|
||||
Update all keys if all of the keys exist in the current table. Do note that if a single key doesn't
|
||||
exist, then a `Nil` code is returned.
|
||||
return: [Rcode 0, Rcode 1, Rcode 5]
|
||||
- name: USET
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [USET <key1> <value1> <key2> <value2> ...]
|
||||
desc: SET all keys if they don't exist, or UPDATE them if they do exist. This operation performs `USET`s in the current table
|
||||
return: [Integer, Rcode 5]
|
||||
- name: KEYLEN
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [KEYLEN <key>]
|
||||
desc: Returns the length of the UTF-8 string, if it exists in the current table
|
||||
return: [Integer, Rcode 1]
|
||||
- name: POP
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [POP <key>]
|
||||
desc: |
|
||||
Deletes and return the value of the provided key from the current table.
|
||||
If the database is poisoned, this will return a server error.
|
||||
return: [String, Binstr, Rcode 5]
|
||||
- name: MPOP
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [MPOP <key1> <key2> ...]
|
||||
desc: |
|
||||
Deletes and returns the values of the provided 'n' keys from the current table.
|
||||
If the database is poisoned, this will return a server error
|
||||
return: [Typed Array, Rcode 5]
|
||||
lists:
|
||||
- name: LGET
|
||||
desc: |
|
||||
`LGET` can be used to access the items in a list. Through the sub-actions provided by `lget`,
|
||||
you can access multiple or individual elements in lists.
|
||||
subactions:
|
||||
- name: LGET
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list>]
|
||||
desc: |
|
||||
Returns all the values contained in a the provided list, if it exists in the current
|
||||
table.
|
||||
return: [Typed Array, Rcode 1]
|
||||
- name: limit
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list> limit <limit>]
|
||||
desc: Returns a maximum of `limit` values from the provided list, if it exists in the current table
|
||||
return: [Typed Array, Rcode 1]
|
||||
- name: len
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list> len]
|
||||
desc: Returns the length of the list
|
||||
return: [Integer, Rcode 1]
|
||||
- name: valueat
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list> valueat <index>]
|
||||
desc: Returns the element present at the provided `index`, if it exists in the given list.
|
||||
return: [String, binstr, Rcode 1, bad-list-index]
|
||||
- name: first
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list> first]
|
||||
desc: Returns the first element present in the list, if it exists.
|
||||
return: [String, binstr, Rcode 1, list-is-empty]
|
||||
- name: last
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list> last]
|
||||
desc: Returns the last element present in the list, if it exists.
|
||||
return: [String, binstr, Rcode 1, list-is-empty]
|
||||
- name: range
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [LGET <list> range <start>, LGET <list> range <start> <stop>]
|
||||
desc: |
|
||||
Returns items in the given range. If no value for `stop` is provided, all the elements from that
|
||||
index are returned. If a value for `stop` is provided, then a subarray is returned
|
||||
return: [Typed Array, Rcode 1, bad-list-index]
|
||||
- name: LMOD
|
||||
desc: |
|
||||
`LMOD` can be used to mutate the elements in a list
|
||||
subactions:
|
||||
- name: push
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LMOD <list> push <v1> <v2> ...]
|
||||
desc: Appends the elements to the end of the provided list, if it exists.
|
||||
return: [Rcode 0, Rcode 1, Rcode 5]
|
||||
- name: insert
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LMOD <list> insert <index> <value>]
|
||||
desc: |
|
||||
Inserts the element to the provided index, if it is valid while shifting elements
|
||||
to the right if required
|
||||
return: [Rcode 0, Rcode 1, Rcode 5, bad-list-index]
|
||||
- name: pop
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LMOD <list> pop, LMOD <list> pop <index>]
|
||||
desc: |
|
||||
Removes the element from the end of the list if no index is provided or from the provided
|
||||
index while shifting elements to the right if required.
|
||||
return: [String, Binstr, Rcode 1, Rcode 5, bad-list-index]
|
||||
- name: remove
|
||||
complexity: O(1)
|
||||
accept: [AnyArray]
|
||||
syntax: [LMOD <list> remove <index>]
|
||||
desc: |
|
||||
Removes the element at the provided index from the list, shifting elements to the right
|
||||
if required.
|
||||
return: [Rcode 0, Rcode 1, Rcode 5, bad-list-index]
|
||||
- name: clear
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [LMOD <list> clear]
|
||||
desc: |
|
||||
Removes all the elements present in the list
|
||||
return: [Rcode 0, Rcode 1, Rcode 5]
|
||||
- name: LSET
|
||||
desc: |
|
||||
`LSET` can be used to create empty lists or lists with the provided values.
|
||||
subactions:
|
||||
- name: LSET
|
||||
complexity: O(n)
|
||||
accept: [AnyArray]
|
||||
syntax: [LSET <list>, LSET <list> <value1> <value2> ...]
|
||||
desc: |
|
||||
Creates a list with the provided values, or simply creates an empty list if it doesn't
|
||||
already exist in the table.
|
||||
return: [Rcode 0, Rcode 2, Rcode 5]
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 180 KiB |
@ -1,9 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2003
|
||||
noart = true
|
||||
|
||||
[ssl]
|
||||
key="../key.pem"
|
||||
chain="../cert.pem"
|
||||
port = 2004
|
@ -0,0 +1,17 @@
|
||||
system:
|
||||
mode: prod
|
||||
|
||||
auth:
|
||||
plugin: pwd
|
||||
root_pass: mypassword12345678
|
||||
|
||||
endpoints:
|
||||
insecure:
|
||||
host: 127.0.0.1
|
||||
port: 2003
|
||||
secure:
|
||||
host: 127.0.0.1
|
||||
port: 2004
|
||||
cert: ../cert.pem
|
||||
private_key: ../key.pem
|
||||
pkey_passphrase: ../passphrase.txt
|
@ -1,12 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2005
|
||||
noart = true
|
||||
|
||||
[auth]
|
||||
origin_key = "4527387f92a381cbe804593f33991d327d456a97"
|
||||
|
||||
[ssl]
|
||||
key = "../key.pem"
|
||||
chain = "../cert.pem"
|
||||
port = 2006
|
@ -1,14 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2007
|
||||
noart = true
|
||||
|
||||
[snapshot]
|
||||
every = 3600
|
||||
atmost = 4
|
||||
failsafe = true
|
||||
|
||||
[ssl]
|
||||
key = "../key.pem"
|
||||
chain = "../cert.pem"
|
||||
port = 2008
|
@ -0,0 +1,30 @@
|
||||
skysh 0.8.0
|
||||
Sayan N. <ohsayan@outlook.com>
|
||||
The Skytable interactive shell (skysh)
|
||||
|
||||
USAGE:
|
||||
skysh [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
--help Diplays this help message
|
||||
--version Displays the shell version
|
||||
|
||||
OPTIONS:
|
||||
--endpoint Set the endpoint for the connection
|
||||
--user Set the user for this client session
|
||||
--password Set the password for this client session
|
||||
--tls-cert Set the TLS certificate to use (for TLS endpoints)
|
||||
|
||||
NOTES:
|
||||
- When no endpoint is specified, skysh will attempt to connect to the default
|
||||
TCP endpoint `tcp@127.0.0.1:2003`
|
||||
- When no user is specified, skysh will attempt to authenticate as root
|
||||
- All connections need an username and password. If this is not provided
|
||||
via arguments, it will be asked for interactively
|
||||
- Endpoints are specified using the Skytable endpoint syntax. For example,
|
||||
the default TCP endpoint is `tcp@127.0.0.1:2003` while the default TLS
|
||||
endpoint is `tls@127.0.0.1:2004`
|
||||
- If you choose to use a TLS endpoint, you must provide a certificate.
|
||||
Failing to do so will throw an error, as expected
|
||||
- All history is stored in the `.sky_history` file. If you wish to delete
|
||||
it, simply remove the file
|
@ -0,0 +1,16 @@
|
||||
Welcome to the Skytable Interactive shell (REPL environment).
|
||||
|
||||
Here are a few tips to help you get started:
|
||||
- Skytable uses its own query language called BlueQL. It is mostly like SQL
|
||||
but with some important changes for security
|
||||
- Since BlueQL doesn't need a query terminator ';' you do not need to use it
|
||||
here for running queries
|
||||
- You might be surprised to see that you can use literals in this REPL while
|
||||
Skytable does not allow the use of literals for security concerns. This is
|
||||
because whenever you run a query, the REPL turns it into a parameterized query.
|
||||
- You can also run some `skysh` specific commands:
|
||||
- `!help` displays this help message
|
||||
- `clear` clears the terminal screen
|
||||
- `exit` exits the REPL session
|
||||
|
||||
Now, it's time to get querying!
|
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Created on Wed Nov 15 2023
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::error::{CliError, CliResult},
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEvent},
|
||||
terminal,
|
||||
},
|
||||
libsky::CliAction,
|
||||
std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
io::{self, Write},
|
||||
process::exit,
|
||||
},
|
||||
};
|
||||
|
||||
const TXT_HELP: &str = include_str!("../help_text/help");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientConfig {
|
||||
pub kind: ClientConfigKind,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl ClientConfig {
|
||||
pub fn new(kind: ClientConfigKind, username: String, password: String) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
username,
|
||||
password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientConfigKind {
|
||||
Tcp(String, u16),
|
||||
Tls(String, u16, String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Task {
|
||||
HelpMessage(String),
|
||||
OpenShell(ClientConfig),
|
||||
}
|
||||
|
||||
enum TaskInner {
|
||||
HelpMsg(String),
|
||||
OpenShell(HashMap<String, String>),
|
||||
}
|
||||
|
||||
fn load_env() -> CliResult<TaskInner> {
|
||||
let action = libsky::parse_cli_args_disallow_duplicate()?;
|
||||
match action {
|
||||
CliAction::Help => Ok(TaskInner::HelpMsg(TXT_HELP.into())),
|
||||
CliAction::Version => Ok(TaskInner::HelpMsg(libsky::version_msg("skysh"))),
|
||||
CliAction::Action(a) => Ok(TaskInner::OpenShell(a)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse() -> CliResult<Task> {
|
||||
let mut args = match load_env()? {
|
||||
TaskInner::HelpMsg(msg) => return Ok(Task::HelpMessage(msg)),
|
||||
TaskInner::OpenShell(args) => args,
|
||||
};
|
||||
let endpoint = match args.remove("--endpoint") {
|
||||
None => ClientConfigKind::Tcp("127.0.0.1".into(), 2003),
|
||||
Some(ep) => {
|
||||
// should be in the format protocol@host:port
|
||||
let proto_host_port: Vec<&str> = ep.split("@").collect();
|
||||
if proto_host_port.len() != 2 {
|
||||
return Err(CliError::ArgsErr("invalid value for --endpoint".into()));
|
||||
}
|
||||
let (protocol, host_port) = (proto_host_port[0], proto_host_port[1]);
|
||||
let host_port: Vec<&str> = host_port.split(":").collect();
|
||||
if host_port.len() != 2 {
|
||||
return Err(CliError::ArgsErr("invalid value for --endpoint".into()));
|
||||
}
|
||||
let (host, port) = (host_port[0], host_port[1]);
|
||||
let port = match port.parse::<u16>() {
|
||||
Ok(port) => port,
|
||||
Err(e) => {
|
||||
return Err(CliError::ArgsErr(format!(
|
||||
"invalid value for endpoint port. {e}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
let tls_cert = args.remove("--tls-cert");
|
||||
match protocol {
|
||||
"tcp" => {
|
||||
// TODO(@ohsayan): warn!
|
||||
ClientConfigKind::Tcp(host.into(), port)
|
||||
}
|
||||
"tls" => {
|
||||
// we need a TLS cert
|
||||
match tls_cert {
|
||||
Some(path) => {
|
||||
let cert = fs::read_to_string(path)?;
|
||||
ClientConfigKind::Tls(host.into(), port, cert)
|
||||
}
|
||||
None => {
|
||||
return Err(CliError::ArgsErr(format!(
|
||||
"must provide TLS cert when using TLS endpoint"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(CliError::ArgsErr(format!(
|
||||
"unknown protocol scheme `{protocol}`"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let username = match args.remove("--user") {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
// default
|
||||
"root".into()
|
||||
}
|
||||
};
|
||||
let password = match args.remove("--password") {
|
||||
Some(p) => p,
|
||||
None => read_password("Enter password: ")?,
|
||||
};
|
||||
if args.is_empty() {
|
||||
Ok(Task::OpenShell(ClientConfig::new(
|
||||
endpoint, username, password,
|
||||
)))
|
||||
} else {
|
||||
Err(CliError::ArgsErr(format!("found unknown arguments")))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_password(prompt: &str) -> Result<String, std::io::Error> {
|
||||
terminal::enable_raw_mode()?;
|
||||
print!("{prompt}");
|
||||
io::stdout().flush()?;
|
||||
let mut password = String::new();
|
||||
loop {
|
||||
match event::read()? {
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: event::KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
terminal::disable_raw_mode()?;
|
||||
println!();
|
||||
exit(0x00)
|
||||
}
|
||||
Event::Key(KeyEvent { code, .. }) => match code {
|
||||
KeyCode::Backspace => {
|
||||
let _ = password.pop();
|
||||
}
|
||||
KeyCode::Char(c) => password.push(c),
|
||||
KeyCode::Enter => break,
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
terminal::disable_raw_mode()?;
|
||||
println!();
|
||||
Ok(password)
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Created on Wed Nov 15 2023
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use core::fmt;
|
||||
|
||||
pub type CliResult<T> = Result<T, CliError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CliError {
|
||||
QueryError(String),
|
||||
ArgsErr(String),
|
||||
ClientError(skytable::error::Error),
|
||||
IoError(std::io::Error),
|
||||
}
|
||||
|
||||
impl From<libsky::ArgParseError> for CliError {
|
||||
fn from(e: libsky::ArgParseError) -> Self {
|
||||
match e {
|
||||
libsky::ArgParseError::Duplicate(d) => {
|
||||
Self::ArgsErr(format!("duplicate value for `{d}`"))
|
||||
}
|
||||
libsky::ArgParseError::MissingValue(m) => {
|
||||
Self::ArgsErr(format!("missing value for `{m}`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<skytable::error::Error> for CliError {
|
||||
fn from(cle: skytable::error::Error) -> Self {
|
||||
Self::ClientError(cle)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for CliError {
|
||||
fn from(ioe: std::io::Error) -> Self {
|
||||
Self::IoError(ioe)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::ArgsErr(e) => write!(f, "incorrect arguments. {e}"),
|
||||
Self::ClientError(e) => write!(f, "client error. {e}"),
|
||||
Self::IoError(e) => write!(f, "i/o error. {e}"),
|
||||
Self::QueryError(e) => write!(f, "invalid query. {e}"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Nov 03 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
macro_rules! write_str {
|
||||
($st:ident) => {
|
||||
println!("\"{}\"", $st)
|
||||
};
|
||||
($idx:ident, $st:ident) => {
|
||||
println!("({}) \"{}\"", $idx, $st)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_binstr {
|
||||
($st:ident) => {
|
||||
println!("{}", BinaryData($st))
|
||||
};
|
||||
($idx:ident, $st:ident) => {
|
||||
println!("({}) {}", $idx, BinaryData($st))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_int {
|
||||
($int:ident) => {
|
||||
println!("{}", $int)
|
||||
};
|
||||
($idx:ident, $st:ident) => {
|
||||
println!("({}) \"{}\"", $idx, $st)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_err {
|
||||
($idx:expr, $err:ident) => {
|
||||
err!(if let Some(idx) = $idx {
|
||||
format!("({}) ({})\n", idx, $err)
|
||||
} else {
|
||||
format!("({})\n", $err)
|
||||
})
|
||||
};
|
||||
($idx:ident, $err:literal) => {
|
||||
err!(
|
||||
(if let Some(idx) = $idx {
|
||||
format!("({}) ({})\n", idx, $err)
|
||||
} else {
|
||||
format!("({})\n", $err)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_okay {
|
||||
() => {
|
||||
crossterm::execute!(
|
||||
std::io::stdout(),
|
||||
SetForegroundColor(Color::Cyan),
|
||||
Print("(Okay)\n".to_string()),
|
||||
ResetColor
|
||||
)
|
||||
.expect("Failed to write to stdout")
|
||||
};
|
||||
($idx:ident) => {
|
||||
crossterm::execute!(
|
||||
std::io::stdout(),
|
||||
SetForegroundColor(Color::Cyan),
|
||||
Print(format!("({}) (Okay)\n", $idx)),
|
||||
ResetColor
|
||||
)
|
||||
.expect("Failed to write to stdout")
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! err {
|
||||
($input:expr) => {
|
||||
crossterm::execute!(
|
||||
std::io::stdout(),
|
||||
::crossterm::style::SetForegroundColor(::crossterm::style::Color::Red),
|
||||
::crossterm::style::Print($input),
|
||||
::crossterm::style::ResetColor
|
||||
)
|
||||
.expect("Failed to write to stdout")
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! eskysh {
|
||||
($e:expr) => {
|
||||
err!(format!("[SKYSH ERROR] {}\n", $e))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! fatal {
|
||||
($e:expr) => {{
|
||||
err!($e);
|
||||
::std::process::exit(0x01);
|
||||
}};
|
||||
($e:expr, $desc:expr) => {{
|
||||
err!(format!($e, $desc));
|
||||
println!();
|
||||
::std::process::exit(0x01)
|
||||
}};
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Created on Thu Nov 16 2023
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::error::{CliError, CliResult},
|
||||
skytable::{
|
||||
error::ClientResult, query::SQParam, response::Response, Connection, ConnectionTls, Query,
|
||||
},
|
||||
};
|
||||
|
||||
pub trait IsConnection {
|
||||
fn execute_query(&mut self, q: Query) -> ClientResult<Response>;
|
||||
}
|
||||
|
||||
impl IsConnection for Connection {
|
||||
fn execute_query(&mut self, q: Query) -> ClientResult<Response> {
|
||||
self.query(&q)
|
||||
}
|
||||
}
|
||||
|
||||
impl IsConnection for ConnectionTls {
|
||||
fn execute_query(&mut self, q: Query) -> ClientResult<Response> {
|
||||
self.query(&q)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Item {
|
||||
UInt(u64),
|
||||
SInt(i64),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Bin(Vec<u8>),
|
||||
}
|
||||
|
||||
impl SQParam for Item {
|
||||
fn append_param(self, buf: &mut Vec<u8>) {
|
||||
match self {
|
||||
Item::UInt(u) => u.append_param(buf),
|
||||
Item::SInt(s) => s.append_param(buf),
|
||||
Item::Float(f) => f.append_param(buf),
|
||||
Item::String(s) => s.append_param(buf),
|
||||
Item::Bin(b) => SQParam::append_param(&*b, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Parameterizer {
|
||||
buf: Vec<u8>,
|
||||
i: usize,
|
||||
params: Vec<Item>,
|
||||
query: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ExecKind {
|
||||
Standard(Query),
|
||||
UseSpace(Query, String),
|
||||
UseNull(Query),
|
||||
PrintSpecial(Query),
|
||||
}
|
||||
|
||||
impl Parameterizer {
|
||||
pub fn new(q: String) -> Self {
|
||||
Self {
|
||||
buf: q.into_bytes(),
|
||||
i: 0,
|
||||
params: vec![],
|
||||
query: vec![],
|
||||
}
|
||||
}
|
||||
pub fn parameterize(mut self) -> CliResult<ExecKind> {
|
||||
while self.not_eof() {
|
||||
match self.buf[self.i] {
|
||||
b if b.is_ascii_alphabetic() || b == b'_' => self.read_ident(),
|
||||
b if b.is_ascii_digit() => self.read_unsigned_integer(),
|
||||
b'-' => self.read_signed_integer(),
|
||||
quote_style @ (b'"' | b'\'') => {
|
||||
self.i += 1;
|
||||
self.read_string(quote_style)
|
||||
}
|
||||
b'`' => {
|
||||
self.i += 1;
|
||||
self.read_binary()
|
||||
}
|
||||
sym => {
|
||||
self.i += 1;
|
||||
Vec::push(&mut self.query, sym);
|
||||
Ok(())
|
||||
}
|
||||
}?
|
||||
}
|
||||
match String::from_utf8(self.query) {
|
||||
Ok(qstr) => {
|
||||
let mut q = Query::new(&qstr);
|
||||
self.params.into_iter().for_each(|p| {
|
||||
q.push_param(p);
|
||||
});
|
||||
Ok(if qstr.eq_ignore_ascii_case("use null") {
|
||||
ExecKind::UseNull(q)
|
||||
} else {
|
||||
if qstr.len() > 8 {
|
||||
let qstr = &qstr[..8];
|
||||
if qstr.eq_ignore_ascii_case("inspect ") {
|
||||
return Ok(ExecKind::PrintSpecial(q));
|
||||
}
|
||||
}
|
||||
let mut splits = qstr.split_ascii_whitespace();
|
||||
let tok_use = splits.next();
|
||||
let tok_name = splits.next();
|
||||
match (tok_use, tok_name) {
|
||||
(Some(tok_use), Some(tok_name))
|
||||
if tok_use.eq_ignore_ascii_case("use")
|
||||
&& !tok_name.eq_ignore_ascii_case("$current") =>
|
||||
{
|
||||
ExecKind::UseSpace(q, tok_name.into())
|
||||
}
|
||||
_ => ExecKind::Standard(q),
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(_) => Err(CliError::QueryError("query is not valid UTF-8".into())),
|
||||
}
|
||||
}
|
||||
fn read_string(&mut self, quote_style: u8) -> CliResult<()> {
|
||||
self.query.push(b'?');
|
||||
let mut string = Vec::new();
|
||||
let mut terminated = false;
|
||||
while self.not_eof() && !terminated {
|
||||
let b = self.buf[self.i];
|
||||
if b == b'\\' {
|
||||
self.i += 1;
|
||||
// escape sequence
|
||||
if self.i == self.buf.len() {
|
||||
// string was not terminated
|
||||
return Err(CliError::QueryError("string not terminated".into()));
|
||||
}
|
||||
match self.buf[self.i] {
|
||||
b'\\' => {
|
||||
// escaped \
|
||||
string.push(b'\\');
|
||||
}
|
||||
b if b == quote_style => {
|
||||
// escape quote
|
||||
string.push(quote_style);
|
||||
}
|
||||
_ => return Err(CliError::QueryError("unknown escape sequence".into())),
|
||||
}
|
||||
}
|
||||
if b == quote_style {
|
||||
terminated = true;
|
||||
} else {
|
||||
string.push(b);
|
||||
}
|
||||
self.i += 1;
|
||||
}
|
||||
if terminated {
|
||||
match String::from_utf8(string) {
|
||||
Ok(s) => self.params.push(Item::String(s)),
|
||||
Err(_) => return Err(CliError::QueryError("invalid UTF-8 string".into())),
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
return Err(CliError::QueryError("string not terminated".into()));
|
||||
}
|
||||
}
|
||||
fn read_ident(&mut self) -> CliResult<()> {
|
||||
// we're looking at an ident
|
||||
let start = self.i;
|
||||
self.i += 1;
|
||||
while self.not_eof() {
|
||||
if self.buf[self.i].is_ascii_alphanumeric() || self.buf[self.i] == b'_' {
|
||||
self.i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let stop = self.i;
|
||||
self.query.extend(&self.buf[start..stop]);
|
||||
Ok(())
|
||||
}
|
||||
fn read_float(&mut self, start: usize) -> CliResult<()> {
|
||||
self.read_until_number_escape();
|
||||
let stop = self.i;
|
||||
match core::str::from_utf8(&self.buf[start..stop]).map(|v| v.parse()) {
|
||||
Ok(Ok(num)) => self.params.push(Item::Float(num)),
|
||||
_ => {
|
||||
return Err(CliError::QueryError(
|
||||
"invalid floating point literal".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn read_signed_integer(&mut self) -> CliResult<()> {
|
||||
self.query.push(b'?');
|
||||
// we must have encountered a `-`
|
||||
let start = self.i;
|
||||
self.read_until_number_escape();
|
||||
let stop = self.i;
|
||||
match core::str::from_utf8(&self.buf[start..stop]).map(|v| v.parse()) {
|
||||
Ok(Ok(s)) => self.params.push(Item::SInt(s)),
|
||||
_ => {
|
||||
return Err(CliError::QueryError(
|
||||
"invalid signed integer literal".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn read_unsigned_integer(&mut self) -> CliResult<()> {
|
||||
self.query.push(b'?');
|
||||
let start = self.i;
|
||||
let mut ret = 0u64;
|
||||
while self.not_eof() {
|
||||
match self.buf[self.i] {
|
||||
b if b.is_ascii_digit() => {
|
||||
self.i += 1;
|
||||
ret = match ret
|
||||
.checked_mul(10)
|
||||
.map(|v| v.checked_add((b & 0x0f) as u64))
|
||||
{
|
||||
Some(Some(r)) => r,
|
||||
_ => return Err(CliError::QueryError("bad value for integer".into())),
|
||||
};
|
||||
}
|
||||
b'.' => {
|
||||
self.i += 1;
|
||||
// uh oh, that's a float
|
||||
return self.read_float(start);
|
||||
}
|
||||
b if b == b' ' || b == b'\t' || b.is_ascii_punctuation() => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
// nothing else is valid here
|
||||
return Err(CliError::QueryError(
|
||||
"invalid unsigned integer literal".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.params.push(Item::UInt(ret));
|
||||
Ok(())
|
||||
}
|
||||
fn read_until_number_escape(&mut self) {
|
||||
while self.not_eof() {
|
||||
let b = self.buf[self.i];
|
||||
if b == b'\n' || b == b'\t' || b.is_ascii_punctuation() {
|
||||
break;
|
||||
}
|
||||
self.i += 1;
|
||||
}
|
||||
}
|
||||
fn read_binary(&mut self) -> CliResult<()> {
|
||||
self.query.push(b'?');
|
||||
let start = self.i;
|
||||
while self.not_eof() {
|
||||
let b = self.buf[self.i];
|
||||
self.i += 1;
|
||||
if b == b'`' {
|
||||
self.params
|
||||
.push(Item::Bin(self.buf[start..self.i].to_vec()));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(CliError::QueryError("binary literal not terminated".into()))
|
||||
}
|
||||
fn not_eof(&self) -> bool {
|
||||
self.i < self.buf.len()
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Created on Thu Nov 16 2023
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::query::ExecKind;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
args::{ClientConfig, ClientConfigKind},
|
||||
error::{CliError, CliResult},
|
||||
query::{self, IsConnection},
|
||||
resp,
|
||||
},
|
||||
crossterm::{cursor, execute, terminal},
|
||||
rustyline::{config::Configurer, error::ReadlineError, DefaultEditor},
|
||||
skytable::Config,
|
||||
std::io::{stdout, ErrorKind},
|
||||
};
|
||||
|
||||
const SKYSH_HISTORY_FILE: &str = ".sky_history";
|
||||
const TXT_WELCOME: &str = include_str!("../help_text/welcome");
|
||||
|
||||
pub fn start(cfg: ClientConfig) -> CliResult<()> {
|
||||
match cfg.kind {
|
||||
ClientConfigKind::Tcp(host, port) => {
|
||||
let c = Config::new(&host, port, &cfg.username, &cfg.password).connect()?;
|
||||
println!(
|
||||
"Authenticated as '{}' on {}:{} over Skyhash/TCP\n---",
|
||||
&cfg.username, &host, &port
|
||||
);
|
||||
repl(c)
|
||||
}
|
||||
ClientConfigKind::Tls(host, port, cert) => {
|
||||
let c = Config::new(&host, port, &cfg.username, &cfg.password).connect_tls(&cert)?;
|
||||
println!(
|
||||
"Authenticated as '{}' on {}:{} over Skyhash/TLS\n---",
|
||||
&cfg.username, &host, &port
|
||||
);
|
||||
repl(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn repl<C: IsConnection>(mut con: C) -> CliResult<()> {
|
||||
let init_editor = || {
|
||||
let mut editor = DefaultEditor::new()?;
|
||||
editor.set_auto_add_history(true);
|
||||
editor.set_history_ignore_dups(true)?;
|
||||
editor.bind_sequence(
|
||||
rustyline::KeyEvent(
|
||||
rustyline::KeyCode::BracketedPasteStart,
|
||||
rustyline::Modifiers::NONE,
|
||||
),
|
||||
rustyline::Cmd::Noop,
|
||||
);
|
||||
match editor.load_history(SKYSH_HISTORY_FILE) {
|
||||
Ok(()) => {}
|
||||
Err(e) => match e {
|
||||
ReadlineError::Io(ref ioe) => match ioe.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
println!("{TXT_WELCOME}");
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
e => return Err(e),
|
||||
},
|
||||
}
|
||||
rustyline::Result::Ok(editor)
|
||||
};
|
||||
let mut editor = match init_editor() {
|
||||
Ok(e) => e,
|
||||
Err(e) => fatal!("error: failed to init REPL. {e}"),
|
||||
};
|
||||
let mut prompt = "> ".to_owned();
|
||||
loop {
|
||||
match editor.readline(&prompt) {
|
||||
Ok(line) => match line.as_str() {
|
||||
"!help" => println!("{TXT_WELCOME}"),
|
||||
"exit" => break,
|
||||
"clear" => clear_screen()?,
|
||||
_ => {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match query::Parameterizer::new(line).parameterize() {
|
||||
Ok(q) => {
|
||||
let mut new_prompt = None;
|
||||
let mut special = false;
|
||||
let q = match q {
|
||||
ExecKind::Standard(q) => q,
|
||||
ExecKind::UseNull(q) => {
|
||||
new_prompt = Some("> ".into());
|
||||
q
|
||||
}
|
||||
ExecKind::UseSpace(q, space) => {
|
||||
new_prompt = Some(format!("{space}> "));
|
||||
q
|
||||
}
|
||||
ExecKind::PrintSpecial(q) => {
|
||||
special = true;
|
||||
q
|
||||
}
|
||||
};
|
||||
if resp::format_response(con.execute_query(q)?, special) {
|
||||
if let Some(pr) = new_prompt {
|
||||
prompt = pr;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
CliError::QueryError(e) => {
|
||||
eprintln!("[skysh error]: bad query. {e}");
|
||||
continue;
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
Err(e) => match e {
|
||||
ReadlineError::Interrupted | ReadlineError::Eof => {
|
||||
// done
|
||||
break;
|
||||
}
|
||||
ReadlineError::WindowResized => {}
|
||||
e => fatal!("error: failed to read line REPL. {e}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
editor
|
||||
.save_history(SKYSH_HISTORY_FILE)
|
||||
.expect("failed to save history");
|
||||
println!("Goodbye!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_screen() -> std::io::Result<()> {
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
|
||||
execute!(stdout, cursor::MoveTo(0, 0))
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Created on Thu Nov 16 2023
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crossterm::style::Stylize,
|
||||
skytable::response::{Response, Row, Value},
|
||||
};
|
||||
|
||||
pub fn format_response(resp: Response, print_special: bool) -> bool {
|
||||
match resp {
|
||||
Response::Empty => println!("{}", "(Okay)".cyan()),
|
||||
Response::Error(e) => {
|
||||
println!("{}", format!("(server error code: {e})").red());
|
||||
return false;
|
||||
}
|
||||
Response::Value(v) => {
|
||||
print_value(v, print_special);
|
||||
println!();
|
||||
}
|
||||
Response::Row(r) => {
|
||||
print_row(r);
|
||||
println!();
|
||||
}
|
||||
Response::Rows(rows) => {
|
||||
if rows.is_empty() {
|
||||
println!("{}", "[0 rows returned]".grey().italic());
|
||||
} else {
|
||||
for (i, row) in rows.into_iter().enumerate().map(|(i, r)| (i + 1, r)) {
|
||||
print!("{} ", format!("({i})").grey().bold());
|
||||
print_row(row);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
true
|
||||
}
|
||||
|
||||
fn print_row(r: Row) {
|
||||
print!("(");
|
||||
let mut columns = r.into_values().into_iter().peekable();
|
||||
while let Some(cell) = columns.next() {
|
||||
print_value(cell, false);
|
||||
if columns.peek().is_some() {
|
||||
print!(", ");
|
||||
}
|
||||
}
|
||||
print!(")");
|
||||
}
|
||||
|
||||
fn print_value(v: Value, print_special: bool) {
|
||||
match v {
|
||||
Value::Null => print!("{}", "null".grey().italic()),
|
||||
Value::String(s) => print_string(&s, print_special),
|
||||
Value::Binary(b) => print_binary(&b),
|
||||
Value::Bool(b) => print!("{b}"),
|
||||
Value::UInt8(i) => print!("{i}"),
|
||||
Value::UInt16(i) => print!("{i}"),
|
||||
Value::UInt32(i) => print!("{i}"),
|
||||
Value::UInt64(i) => print!("{i}"),
|
||||
Value::SInt8(i) => print!("{i}"),
|
||||
Value::SInt16(i) => print!("{i}"),
|
||||
Value::SInt32(i) => print!("{i}"),
|
||||
Value::SInt64(i) => print!("{i}"),
|
||||
Value::Float32(f) => print!("{f}"),
|
||||
Value::Float64(f) => print!("{f}"),
|
||||
Value::List(items) => {
|
||||
print!("[");
|
||||
let mut items = items.into_iter().peekable();
|
||||
while let Some(item) = items.next() {
|
||||
print_value(item, print_special);
|
||||
if items.peek().is_some() {
|
||||
print!(", ");
|
||||
}
|
||||
}
|
||||
print!("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_binary(b: &[u8]) {
|
||||
let mut it = b.into_iter().peekable();
|
||||
print!("[");
|
||||
while let Some(byte) = it.next() {
|
||||
print!("{byte}");
|
||||
if it.peek().is_some() {
|
||||
print!(", ");
|
||||
}
|
||||
}
|
||||
print!("]");
|
||||
}
|
||||
|
||||
fn print_string(s: &str, print_special: bool) {
|
||||
if print_special {
|
||||
print!("{}", s.italic().grey());
|
||||
} else {
|
||||
print!("\"");
|
||||
for ch in s.chars() {
|
||||
if ch == '"' {
|
||||
print!("\\{ch}");
|
||||
} else if ch == '\t' {
|
||||
print!("\\t");
|
||||
} else if ch == '\n' {
|
||||
print!("\\n");
|
||||
} else {
|
||||
print!("{ch}");
|
||||
}
|
||||
}
|
||||
print!("\"");
|
||||
}
|
||||
}
|
@ -1,262 +0,0 @@
|
||||
/*
|
||||
* Created on Wed May 12 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::tokenizer,
|
||||
core::fmt,
|
||||
crossterm::style::{Color, Print, ResetColor, SetForegroundColor},
|
||||
skytable::{
|
||||
aio, error::Error, types::Array, types::FlatElement, Element, Pipeline, Query, RespCode,
|
||||
},
|
||||
};
|
||||
|
||||
type SkyResult<T> = Result<T, Error>;
|
||||
|
||||
pub enum Runner {
|
||||
Insecure(aio::Connection),
|
||||
Secure(aio::TlsConnection),
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub async fn new_insecure(host: &str, port: u16) -> SkyResult<Self> {
|
||||
let con = aio::Connection::new(host, port).await?;
|
||||
Ok(Self::Insecure(con))
|
||||
}
|
||||
pub async fn new_secure(host: &str, port: u16, cert: &str) -> SkyResult<Self> {
|
||||
let con = aio::TlsConnection::new(host, port, cert).await?;
|
||||
Ok(Self::Secure(con))
|
||||
}
|
||||
pub async fn run_pipeline(&mut self, pipeline: Pipeline) {
|
||||
let ret = match self {
|
||||
Self::Insecure(con) => con.run_pipeline(pipeline).await,
|
||||
Self::Secure(con) => con.run_pipeline(pipeline).await,
|
||||
};
|
||||
let retok = match ret {
|
||||
Ok(r) => r,
|
||||
Err(e) => fatal!("An I/O error occurred while querying: {}", e),
|
||||
};
|
||||
for (idx, resp) in retok
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, resp)| (idx + 1, resp))
|
||||
{
|
||||
println!("[Response {}]", idx);
|
||||
print_element(resp);
|
||||
}
|
||||
}
|
||||
pub async fn run_query(&mut self, unescaped: &str) {
|
||||
let query: Query = match tokenizer::get_query(unescaped.as_bytes()) {
|
||||
Ok(q) => q,
|
||||
Err(e) => {
|
||||
err!(format!("[Syntax Error: {}]\n", e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let ret = match self {
|
||||
Self::Insecure(con) => con.run_query_raw(&query).await,
|
||||
Self::Secure(con) => con.run_query_raw(&query).await,
|
||||
};
|
||||
match ret {
|
||||
Ok(resp) => print_element(resp),
|
||||
Err(e) => fatal!("An I/O error occurred while querying: {}", e),
|
||||
}
|
||||
}
|
||||
pub async fn check_entity(&mut self, blank: &mut String, prompt: &mut String) {
|
||||
let query: Query = tokenizer::get_query(b"whereami").unwrap();
|
||||
let ret = match self {
|
||||
Self::Insecure(con) => con.run_query_raw(&query).await,
|
||||
Self::Secure(con) => con.run_query_raw(&query).await,
|
||||
};
|
||||
let ret = match ret {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => fatal!("An I/O error occurred while querying: {}", e),
|
||||
};
|
||||
match ret {
|
||||
Element::Array(Array::NonNullStr(srr)) => match srr.len() {
|
||||
1 => {
|
||||
*blank = format!(" {blank}> ", blank = " ".repeat(srr[0].len()));
|
||||
*prompt = format!("skysh@{ks}> ", ks = srr[0]);
|
||||
}
|
||||
2 => {
|
||||
let ks = &srr[0];
|
||||
let tbl = &srr[1];
|
||||
*blank = format!(
|
||||
" {blank}> ",
|
||||
blank = " ".repeat(ks.len() + tbl.len() + 1)
|
||||
);
|
||||
*prompt = format!("skysh@{ks}:{tbl}> ", ks = ks, tbl = tbl);
|
||||
}
|
||||
count => fatal!(
|
||||
"The server returned {} IDs while checking entity state",
|
||||
count
|
||||
),
|
||||
},
|
||||
_ => fatal!("The server returned the wrong data type for entity state check"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_float(float: f32, idx: Option<usize>) {
|
||||
if let Some(idx) = idx {
|
||||
println!("({idx}) {float}")
|
||||
} else {
|
||||
println!("{float}");
|
||||
}
|
||||
}
|
||||
|
||||
fn print_element(el: Element) {
|
||||
match el {
|
||||
Element::String(st) => write_str!(st),
|
||||
Element::Binstr(st) => write_binstr!(st),
|
||||
Element::Array(Array::Bin(brr)) => print_bin_array(brr),
|
||||
Element::Array(Array::Str(srr)) => print_str_array(srr),
|
||||
Element::RespCode(r) => print_rcode(r, None),
|
||||
Element::UnsignedInt(int) => write_int!(int),
|
||||
Element::Array(Array::Flat(frr)) => write_flat_array(frr),
|
||||
Element::Array(Array::Recursive(a)) => print_array(a),
|
||||
Element::Array(Array::NonNullBin(nbrr)) => print_array_nonnull_bin(nbrr),
|
||||
Element::Array(Array::NonNullStr(nsrr)) => print_array_nonnull_str(nsrr),
|
||||
Element::Float(float) => print_float(float, None),
|
||||
_ => eskysh!("The server possibly sent a newer data type that we can't parse"),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_rcode(rcode: RespCode, idx: Option<usize>) {
|
||||
match rcode {
|
||||
RespCode::Okay => write_okay!(),
|
||||
RespCode::ActionError => write_err!(idx, "Action Error"),
|
||||
RespCode::ErrorString(st) => write_err!(idx, st),
|
||||
RespCode::OtherError => write_err!(idx, "Other Error"),
|
||||
RespCode::NotFound => write_err!(idx, "Not Found"),
|
||||
RespCode::OverwriteError => write_err!(idx, "Overwrite Error"),
|
||||
RespCode::PacketError => write_err!(idx, "Packet Error"),
|
||||
RespCode::ServerError => write_err!(idx, "Server Error"),
|
||||
RespCode::UnknownDataType => write_err!(idx, "Unknown data type"),
|
||||
RespCode::EncodingError => write_err!(idx, "Encoding error"),
|
||||
RespCode::AuthBadCredentials => write_err!(idx, "auth bad credentials"),
|
||||
RespCode::AuthPermissionError => write_err!(idx, "auth permission error"),
|
||||
_ => write_err!(idx, "Unknown error"),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_bin_array(bin_array: Vec<Option<Vec<u8>>>) {
|
||||
bin_array.into_iter().enumerate().for_each(|(idx, elem)| {
|
||||
let idx = idx + 1;
|
||||
match elem {
|
||||
Some(ele) => {
|
||||
write_binstr!(idx, ele);
|
||||
}
|
||||
None => print_rcode(RespCode::NotFound, Some(idx)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn print_str_array(str_array: Vec<Option<String>>) {
|
||||
str_array.into_iter().enumerate().for_each(|(idx, elem)| {
|
||||
let idx = idx + 1;
|
||||
match elem {
|
||||
Some(ele) => {
|
||||
write_str!(idx, ele);
|
||||
}
|
||||
None => print_rcode(RespCode::NotFound, Some(idx)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn print_array_nonnull_str(str_array: Vec<String>) {
|
||||
str_array.into_iter().enumerate().for_each(|(idx, elem)| {
|
||||
let idx = idx + 1;
|
||||
write_str!(idx, elem)
|
||||
})
|
||||
}
|
||||
|
||||
fn print_array_nonnull_bin(str_array: Vec<Vec<u8>>) {
|
||||
str_array.into_iter().enumerate().for_each(|(idx, elem)| {
|
||||
let idx = idx + 1;
|
||||
write_binstr!(idx, elem)
|
||||
})
|
||||
}
|
||||
|
||||
fn write_flat_array(flat_array: Vec<FlatElement>) {
|
||||
for (idx, item) in flat_array.into_iter().enumerate() {
|
||||
let idx = idx + 1;
|
||||
match item {
|
||||
FlatElement::String(st) => write_str!(idx, st),
|
||||
FlatElement::Binstr(st) => {
|
||||
write_binstr!(idx, st)
|
||||
}
|
||||
FlatElement::RespCode(rc) => print_rcode(rc, Some(idx)),
|
||||
FlatElement::UnsignedInt(int) => write_int!(int, idx),
|
||||
_ => eskysh!("Element typed cannot yet be parsed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_array(array: Vec<Element>) {
|
||||
for (idx, item) in array.into_iter().enumerate() {
|
||||
let idx = idx + 1;
|
||||
match item {
|
||||
Element::String(st) => write_str!(idx, st),
|
||||
Element::RespCode(rc) => print_rcode(rc, Some(idx)),
|
||||
Element::UnsignedInt(int) => write_int!(idx, int),
|
||||
Element::Array(Array::Bin(brr)) => print_bin_array(brr),
|
||||
Element::Array(Array::Str(srr)) => print_str_array(srr),
|
||||
_ => eskysh!("Nested arrays cannot be printed just yet"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BinaryData(Vec<u8>);
|
||||
|
||||
impl fmt::Display for BinaryData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "b\"")?;
|
||||
for b in self.0.iter() {
|
||||
let b = *b;
|
||||
// See this: https://doc.rust-lang.org/reference/tokens.html#byte-escapes
|
||||
// this idea was borrowed from the Bytes crate
|
||||
#[allow(clippy::manual_range_contains)]
|
||||
if b == b'\n' {
|
||||
write!(f, "\\n")?;
|
||||
} else if b == b'\r' {
|
||||
write!(f, "\\r")?;
|
||||
} else if b == b'\t' {
|
||||
write!(f, "\\t")?;
|
||||
} else if b == b'\\' || b == b'"' {
|
||||
write!(f, "\\{}", b as char)?;
|
||||
} else if b == b'\0' {
|
||||
write!(f, "\\0")?;
|
||||
// ASCII printable
|
||||
} else if b >= 0x20 && b < 0x7f {
|
||||
write!(f, "{}", b as char)?;
|
||||
} else {
|
||||
write!(f, "\\x{:02x}", b)?;
|
||||
}
|
||||
}
|
||||
write!(f, "\"")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Created on Sun Oct 10 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::tokenizer::{get_query, TokenizerError};
|
||||
|
||||
fn query_from(input: &[u8]) -> Result<Vec<String>, TokenizerError> {
|
||||
get_query(input)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_tokenization() {
|
||||
let input = "set x 100".as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec!["set".to_owned(), "x".to_owned(), "100".to_owned()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_quote_tokens() {
|
||||
let input = "set 'x with a whitespace' 100".as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec![
|
||||
"set".to_owned(),
|
||||
"x with a whitespace".to_owned(),
|
||||
"100".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_quote_tokens() {
|
||||
let input = r#"set "x with a whitespace" 100"#.as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec![
|
||||
"set".to_owned(),
|
||||
"x with a whitespace".to_owned(),
|
||||
"100".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_and_double_quote_tokens() {
|
||||
let input = r#"set "x with a whitespace" 'y with a whitespace'"#.as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec![
|
||||
"set".to_owned(),
|
||||
"x with a whitespace".to_owned(),
|
||||
"y with a whitespace".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_single_quote_tokens() {
|
||||
let input = r#"'set' 'x with a whitespace' 'y with a whitespace'"#.as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec![
|
||||
"set".to_owned(),
|
||||
"x with a whitespace".to_owned(),
|
||||
"y with a whitespace".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_double_quote_tokens() {
|
||||
let input = r#""set" "x with a whitespace" "y with a whitespace""#.as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec![
|
||||
"set".to_owned(),
|
||||
"x with a whitespace".to_owned(),
|
||||
"y with a whitespace".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_single_quote() {
|
||||
let input = r#"'get' 'x with a whitespace"#.as_bytes();
|
||||
let ret = format!("{}", query_from(input).unwrap_err());
|
||||
assert_eq!(ret, "mismatched quotes near end of: `x with a whitespace`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_double_quote() {
|
||||
let input = r#"'get' "x with a whitespace"#.as_bytes();
|
||||
let ret = format!("{}", query_from(input).unwrap_err());
|
||||
assert_eq!(ret, "mismatched quotes near end of: `x with a whitespace`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extra_whitespace() {
|
||||
let input = "set x '100'".as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec!["set".to_owned(), "x".to_owned(), "100".to_owned()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_singly_quoted() {
|
||||
let input = "set tables' wth".as_bytes();
|
||||
let ret = query_from(input).unwrap_err();
|
||||
assert_eq!(ret, TokenizerError::ExpectedWhitespace("tables".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_after_quote_nospace() {
|
||||
let input = "get 'rust'ferris".as_bytes();
|
||||
let ret = query_from(input).unwrap_err();
|
||||
assert_eq!(ret, TokenizerError::ExpectedWhitespace("rust'".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_after_double_quote_nospace() {
|
||||
let input = r#"get "rust"ferris"#.as_bytes();
|
||||
let ret = query_from(input).unwrap_err();
|
||||
assert_eq!(ret, TokenizerError::ExpectedWhitespace("rust\"".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_comment() {
|
||||
let input = "set x 100 # sets x to 100".as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert_eq!(
|
||||
ret,
|
||||
vec!["set".to_owned(), "x".to_owned(), "100".to_owned()]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_comment() {
|
||||
let input = "# what is going on?".as_bytes();
|
||||
let ret = query_from(input).unwrap();
|
||||
assert!(ret.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_comment() {
|
||||
let input = "set x \"# ooh la la\"".as_bytes();
|
||||
assert_eq!(
|
||||
query_from(input).unwrap(),
|
||||
vec!["set".to_owned(), "x".to_owned(), "# ooh la la".to_owned()]
|
||||
);
|
||||
let input = "set x \"#\"".as_bytes();
|
||||
assert_eq!(
|
||||
query_from(input).unwrap(),
|
||||
vec!["set".to_owned(), "x".to_owned(), "#".to_owned()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blueql_query() {
|
||||
let input = b"create model mymodel(string, binary)";
|
||||
assert_eq!(
|
||||
query_from(input).unwrap(),
|
||||
vec!["create model mymodel(string, binary)"]
|
||||
);
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
/*
|
||||
* Created on Sat Oct 09 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! This module provides a simple way to avoid "the funk" with "funky input queries". It simply
|
||||
//! tokenizes char-by-char analyzing quotes et al as required
|
||||
//!
|
||||
|
||||
use {
|
||||
core::fmt,
|
||||
skytable::{types::RawString, Query},
|
||||
std::collections::HashSet,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref BLUEQL_KW: HashSet<&'static [u8]> = {
|
||||
let mut hs = HashSet::new();
|
||||
hs.insert("create".as_bytes());
|
||||
hs.insert(b"inspect");
|
||||
hs.insert(b"drop");
|
||||
hs.insert(b"use");
|
||||
hs
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TokenizerError {
|
||||
QuoteMismatch(String),
|
||||
BacktickMismatch(String),
|
||||
ExpectedWhitespace(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for TokenizerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::QuoteMismatch(expr) => write!(f, "mismatched quotes near end of: `{}`", expr),
|
||||
Self::ExpectedWhitespace(expr) => {
|
||||
write!(f, "expected whitespace near end of: `{}`", expr)
|
||||
}
|
||||
Self::BacktickMismatch(expr) => {
|
||||
write!(f, "mismatched backticks near end of: `{}`", expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SequentialQuery {
|
||||
fn push(&mut self, input: &[u8]);
|
||||
fn new() -> Self;
|
||||
fn is_empty(&self) -> bool;
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
impl SequentialQuery for Vec<String> {
|
||||
fn push(&mut self, input: &[u8]) {
|
||||
Vec::push(self, String::from_utf8_lossy(input).to_string())
|
||||
}
|
||||
fn is_empty(&self) -> bool {
|
||||
Vec::len(self) == 0
|
||||
}
|
||||
fn new() -> Self {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SequentialQuery for Query {
|
||||
fn push(&mut self, input: &[u8]) {
|
||||
Query::push(self, RawString::from(input.to_owned()))
|
||||
}
|
||||
fn is_empty(&self) -> bool {
|
||||
Query::len(self) == 0
|
||||
}
|
||||
fn new() -> Self {
|
||||
Query::new()
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(@ohsayan): Fix this entire impl. At this point, it's almost like legacy code
|
||||
pub fn get_query<T: SequentialQuery>(inp: &[u8]) -> Result<T, TokenizerError> {
|
||||
assert!(!inp.is_empty(), "Input is empty");
|
||||
let mut query = T::new();
|
||||
let mut it = inp.iter().peekable();
|
||||
macro_rules! pos {
|
||||
() => {
|
||||
inp.len() - it.len()
|
||||
};
|
||||
}
|
||||
macro_rules! expect_whitespace {
|
||||
($start:expr) => {
|
||||
match it.peek() {
|
||||
Some(b) => match **b {
|
||||
b' ' => {}
|
||||
_ => {
|
||||
return Err(TokenizerError::ExpectedWhitespace(
|
||||
String::from_utf8_lossy(&inp[$start..pos!()]).to_string(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
// skip useless starting whitespace
|
||||
while let Some(b' ') = it.next() {}
|
||||
let end_of_first = match it.position(|x| *x == b' ') {
|
||||
Some(e) => e + 1,
|
||||
None if it.len() == 0 => inp.len(),
|
||||
None => {
|
||||
return Err(TokenizerError::ExpectedWhitespace(
|
||||
String::from_utf8_lossy(inp).to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
if BLUEQL_KW.contains(inp[..end_of_first].to_ascii_lowercase().as_slice()) {
|
||||
query.push(inp);
|
||||
return Ok(query);
|
||||
} else {
|
||||
it = inp.iter().peekable();
|
||||
}
|
||||
'outer: while let Some(tok) = it.next() {
|
||||
match tok {
|
||||
b'\'' => {
|
||||
// hmm, quotes; let's see where it ends
|
||||
let pos = pos!();
|
||||
let qidx = it.position(|x| *x == b'\'');
|
||||
match qidx {
|
||||
Some(idx) => query.push(&inp[pos..idx + pos]),
|
||||
None => {
|
||||
let end = pos!();
|
||||
return Err(TokenizerError::QuoteMismatch(
|
||||
String::from_utf8_lossy(&inp[pos..end]).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
expect_whitespace!(pos);
|
||||
}
|
||||
b'"' => {
|
||||
// hmm, quotes; let's see where it ends
|
||||
let pos = pos!();
|
||||
let qidx = it.position(|x| *x == b'"');
|
||||
match qidx {
|
||||
Some(idx) => query.push(&inp[pos..idx + pos]),
|
||||
None => {
|
||||
let end = pos!();
|
||||
return Err(TokenizerError::QuoteMismatch(
|
||||
String::from_utf8_lossy(&inp[pos..end]).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
expect_whitespace!(pos);
|
||||
}
|
||||
b'`' => {
|
||||
// hmm, backtick? let's look for the end
|
||||
let pos = pos!();
|
||||
let qidx = it.position(|x| *x == b'`');
|
||||
match qidx {
|
||||
Some(idx) => query.push(&inp[pos..idx + pos]),
|
||||
None => {
|
||||
let end = pos!();
|
||||
return Err(TokenizerError::BacktickMismatch(
|
||||
String::from_utf8_lossy(&inp[pos..end]).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
expect_whitespace!(pos);
|
||||
}
|
||||
b' ' => {
|
||||
// this just prevents control from being handed to the wildcard
|
||||
continue;
|
||||
}
|
||||
b'#' => {
|
||||
// so this is an inline comment; skip until newline
|
||||
let _ = it.position(|x| *x == b'\n');
|
||||
}
|
||||
_ => {
|
||||
let start = pos!() - 1;
|
||||
let mut end = start;
|
||||
// alpha? cool, go on
|
||||
'inner: while let Some(tok) = it.peek() {
|
||||
match **tok {
|
||||
b' ' => {
|
||||
it.next();
|
||||
break 'inner;
|
||||
}
|
||||
b'\'' | b'"' => {
|
||||
return Err(TokenizerError::ExpectedWhitespace(
|
||||
String::from_utf8_lossy(&inp[start..pos!()]).to_string(),
|
||||
))
|
||||
}
|
||||
b'#' => continue 'outer,
|
||||
_ => {
|
||||
end += 1;
|
||||
it.next();
|
||||
continue 'inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
end += 1;
|
||||
query.push(&inp[start..end]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(query)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# This is a 'bad' configuration file since it contains an invalid port
|
||||
[server]
|
||||
port = 20033002
|
@ -1,8 +0,0 @@
|
||||
# This is a bad toml file since it contains an invalid value of `every` in BGSAVE
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2003
|
||||
|
||||
[bgsave]
|
||||
every = 0.5 # Not possible!
|
||||
enabled = true
|
@ -1,6 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2003
|
||||
|
||||
[bgsave]
|
||||
enabled = true
|
@ -1,6 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2003
|
||||
|
||||
[bgsave]
|
||||
every = 600
|
@ -1,5 +0,0 @@
|
||||
# this is the default configuration file for Docker images. Modify it as required
|
||||
[server]
|
||||
host = "0.0.0.0"
|
||||
port = 2003
|
||||
noart = true
|
@ -0,0 +1,14 @@
|
||||
# this is the default configuration for the Debian package. Modify to use your own settings.
|
||||
system:
|
||||
mode: prod
|
||||
rs_window: 300
|
||||
|
||||
auth:
|
||||
plugin: pwd
|
||||
# replace with your root password of choice
|
||||
root_pass: rootpass
|
||||
|
||||
endpoints:
|
||||
insecure:
|
||||
host: 127.0.0.1
|
||||
port: 2003
|
@ -1,4 +0,0 @@
|
||||
# This makes use of an IPv6 address
|
||||
[server]
|
||||
host = "::1"
|
||||
port = 2003
|
@ -1,4 +0,0 @@
|
||||
[server]
|
||||
host = '127.0.0.1' # i.e localhost
|
||||
port = 2003 # The port to bind to
|
||||
noart = true # No terminal artwork
|
@ -1,5 +0,0 @@
|
||||
# This is a 'good' configuration file since it contains a valid port
|
||||
# and appropriate keys
|
||||
[server]
|
||||
host = '127.0.0.1'
|
||||
port = 2003 # Set the server's port to 2003
|
@ -1,17 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1" # The IP address to which you want sdb to bind to
|
||||
port = 2003 # The port to which you want sdb to bind to
|
||||
# Set `noart` to true if you want to disable terminal artwork
|
||||
noart = false
|
||||
|
||||
[bgsave]
|
||||
# Run `BGSAVE` `every` seconds. For example, setting this to 60 will cause BGSAVE to run
|
||||
# after every 2 minutes
|
||||
enabled = true
|
||||
every = 120
|
||||
|
||||
[snapshot]
|
||||
# Create a snapshot every hour (1 hour = 60 minutes = 60 * 60 seconds = 3600 seconds)
|
||||
every = 3600
|
||||
# How many of the snapshots to keep
|
||||
atmost = 4 # keep the four most recent snapshots
|
@ -1,17 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2003
|
||||
noart = false
|
||||
|
||||
[ssl]
|
||||
key = "/path/to/keyfile.pem"
|
||||
chain = "/path/to/chain.pem"
|
||||
port = 2004
|
||||
|
||||
[bgsave]
|
||||
enabled = true
|
||||
every = 120
|
||||
|
||||
[snapshot]
|
||||
every = 3600
|
||||
atmost = 4
|
@ -1,40 +0,0 @@
|
||||
# This is a complete sdb configuration template which is always kept updated
|
||||
# to include all the configuration options. I encourage you to always use this
|
||||
# when you use a configuration file
|
||||
# Instead of deleting entire sections from this file, comment them out, so that you
|
||||
# now what you've kept enabled and what you've kept disabled. This helps avoid
|
||||
# configuration problems during production
|
||||
|
||||
# This is a *REQUIRED* key
|
||||
[server]
|
||||
host = "127.0.0.1" # The IP address to which you want sdb to bind to
|
||||
port = 2003 # The port to which you want sdb to bind to
|
||||
noart = false # Set `noart` to true if you want to disable terminal artwork
|
||||
maxcon = 50000 # set the maximum number of clients that the server can accept
|
||||
mode = "dev" # Set this to `prod` when you're running in production and `dev` when in development
|
||||
|
||||
# This is an optional key
|
||||
[auth]
|
||||
# the origin key to be used to claim the root account
|
||||
origin_key = "4527387f92a381cbe804593f33991d327d456a97"
|
||||
|
||||
# This key is *OPTIONAL*
|
||||
[bgsave]
|
||||
# Run `BGSAVE` `every` seconds. For example, setting this to 60 will cause BGSAVE to run
|
||||
# after every 2 minutes
|
||||
enabled = true
|
||||
every = 120
|
||||
|
||||
# This key is *OPTIONAL*
|
||||
[snapshot]
|
||||
every = 3600 # Make a snapshot after every 1 hour (60min * 60sec= 3600secs)
|
||||
atmost = 4 # Keep the 4 most recent snapshots
|
||||
failsafe = true # stops accepting writes if snapshotting fails
|
||||
|
||||
# This key is *OPTIONAL*, used for TLS/SSL config
|
||||
[ssl]
|
||||
key = "/path/to/keyfile.pem"
|
||||
chain = "/path/to/chain.pem"
|
||||
port = 2004
|
||||
only = true # optional to enable SSL-only requests
|
||||
passin = "/path/to/cert/passphrase.txt" # optional to programmatically verify the TLS cert
|
@ -0,0 +1,22 @@
|
||||
system:
|
||||
mode: prod
|
||||
rs_window: 600
|
||||
|
||||
auth:
|
||||
plugin: pwd
|
||||
# replace with your root password of choice
|
||||
root_pass: password
|
||||
|
||||
endpoints:
|
||||
secure:
|
||||
host: 127.0.0.1
|
||||
port: 2004
|
||||
# replace `cert` with the path to your self-signed certificate
|
||||
cert: cert.pem
|
||||
# replace `private_key` with the path to your private key
|
||||
private_key: private.key
|
||||
# replace `passphrase.txt` with the path to your private key passphrase
|
||||
pkey_passphrase: passphrase.txt
|
||||
insecure:
|
||||
host: 127.0.0.1
|
||||
port: 2003
|
@ -1,7 +0,0 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 2003
|
||||
|
||||
[bgsave]
|
||||
enabled = true
|
||||
every = 600 # Every 10 minutes
|
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "libstress"
|
||||
version = "0.8.0"
|
||||
authors = ["Sayan Nandan <nandansayan@outlook.com>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# external deps
|
||||
crossbeam-channel = "0.5.6"
|
||||
rayon = "1.6.1"
|
||||
log = "0.4.17"
|
||||
rand = "0.8.5"
|
@ -1,469 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Jun 16 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # libstress
|
||||
//!
|
||||
//! Tools for emulating concurrent query behavior to _stress test_ the database server.
|
||||
//! As of now, this crate provides a [`Workpool`] which is a generic synchronous threadpool
|
||||
//! for doing multiple operations. But Workpool is a little different from standard threadpool
|
||||
//! implementations in it categorizing a job to be made up of three parts, namely:
|
||||
//!
|
||||
//! - The init_pre_loop_var (the pre-loop stage)
|
||||
//! - The on_loop (the in-loop stage)
|
||||
//! - The on_exit (the post-loop stage)
|
||||
//!
|
||||
//! These stages form a part of the event loop.
|
||||
//!
|
||||
//! ## The event loop
|
||||
//!
|
||||
//! A task runs in a loop with the `on_loop` routine to which the a reference of the result of
|
||||
//! the `init_pre_loop_var` is sent that is initialized. The loop proceeds whenever a worker
|
||||
//! receives a task or else it blocks the current thread, waiting for a task. Hence the loop
|
||||
//! cannot be terminated by an execute call. Instead, the _event loop_ is terminated when the
|
||||
//! Workpool is dropped, either by scoping out, or by using the provided finish-like methods
|
||||
//! (that call the destructor).
|
||||
//!
|
||||
//! ## Worker lifetime
|
||||
//!
|
||||
//! If a runtime panic occurs in the pre-loop stage, then the entire worker just terminates. Hence
|
||||
//! this worker is no longer able to perform any tasks. Similarly, if a runtime panic occurs in
|
||||
//! the in-loop stage, the worker terminates and is no longer available to do any work. This will
|
||||
//! be reflected when the workpool attempts to terminate in entirety, i.e when the threads are joined
|
||||
//! to the parent thread
|
||||
//!
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
#![deny(unused_imports)]
|
||||
|
||||
pub mod traits;
|
||||
pub use rayon;
|
||||
|
||||
use {
|
||||
core::marker::PhantomData,
|
||||
crossbeam_channel::{bounded, unbounded, Receiver as CReceiver, Sender as CSender},
|
||||
rayon::prelude::{IntoParallelIterator, ParallelIterator},
|
||||
std::{fmt::Display, thread},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WorkpoolError {
|
||||
ThreadStartFailure(usize, usize),
|
||||
}
|
||||
|
||||
impl Display for WorkpoolError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorkpoolError::ThreadStartFailure(expected, started) => {
|
||||
write!(
|
||||
f,
|
||||
"couldn't start all threads. expected {expected} but started {started}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type WorkpoolResult<T> = Result<T, WorkpoolError>;
|
||||
|
||||
/// A Job. The UIn type parameter is the type that will be used to execute the action
|
||||
/// Nothing is a variant used by the drop implementation to terminate all the workers
|
||||
/// and call the exit_loop function
|
||||
enum JobType<UIn> {
|
||||
Task(UIn),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
/// A worker
|
||||
///
|
||||
/// The only reason we use option is to reduce the effort needed to implement [`Drop`] for the
|
||||
/// [`Workpool`]
|
||||
struct Worker {
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
/// Initialize a new worker
|
||||
fn new<Inp: 'static, UIn, Lv, Lp, Ex>(
|
||||
id: usize,
|
||||
job_receiver: CReceiver<JobType<UIn>>,
|
||||
init_pre_loop_var: Lv,
|
||||
on_exit: Ex,
|
||||
on_loop: Lp,
|
||||
wgtx: CSender<()>,
|
||||
) -> Self
|
||||
where
|
||||
UIn: Send + Sync + 'static,
|
||||
Lv: Fn() -> Inp + 'static + Send,
|
||||
Lp: Fn(&mut Inp, UIn) + Send + Sync + 'static,
|
||||
Ex: Fn(&mut Inp) + Send + 'static,
|
||||
{
|
||||
let thread = thread::Builder::new()
|
||||
.name(format!("worker-{id}"))
|
||||
.spawn(move || {
|
||||
let on_loop = on_loop;
|
||||
let mut pre_loop_var = init_pre_loop_var();
|
||||
wgtx.send(()).unwrap();
|
||||
drop(wgtx);
|
||||
loop {
|
||||
let action = job_receiver.recv().unwrap();
|
||||
match action {
|
||||
JobType::Task(tsk) => on_loop(&mut pre_loop_var, tsk),
|
||||
JobType::Nothing => {
|
||||
on_exit(&mut pre_loop_var);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Self {
|
||||
thread: Some(thread),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pool configuration setting to easily generate [`Workpool`]s without
|
||||
/// having to clone an entire pool and its threads upfront
|
||||
pub struct PoolConfig<Inp, UIn, Lv, Lp, Ex> {
|
||||
/// the pool size
|
||||
count: usize,
|
||||
/// the function that sets the pre-loop variable
|
||||
init_pre_loop_var: Lv,
|
||||
/// the function to be executed on worker termination
|
||||
on_exit: Ex,
|
||||
/// the function to be executed on loop
|
||||
on_loop: Lp,
|
||||
/// a marker for `Inp` since no parameters use it directly
|
||||
_marker: PhantomData<(Inp, UIn)>,
|
||||
/// check if self needs a pool for parallel iterators
|
||||
needs_iterator_pool: bool,
|
||||
/// expected maximum number of jobs
|
||||
expected_max_sends: Option<usize>,
|
||||
}
|
||||
|
||||
impl<Inp: 'static, UIn, Lv, Lp, Ex> PoolConfig<Inp, UIn, Lv, Lp, Ex>
|
||||
where
|
||||
UIn: Send + Sync + 'static,
|
||||
Inp: Sync,
|
||||
Ex: Fn(&mut Inp) + Send + Sync + 'static + Clone,
|
||||
Lv: Fn() -> Inp + Send + Sync + 'static + Clone,
|
||||
Lp: Fn(&mut Inp, UIn) + Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Create a new pool config
|
||||
pub fn new(
|
||||
count: usize,
|
||||
init_pre_loop_var: Lv,
|
||||
on_loop: Lp,
|
||||
on_exit: Ex,
|
||||
needs_iterator_pool: bool,
|
||||
expected_max_sends: Option<usize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
count,
|
||||
init_pre_loop_var,
|
||||
on_loop,
|
||||
on_exit,
|
||||
needs_iterator_pool,
|
||||
_marker: PhantomData,
|
||||
expected_max_sends,
|
||||
}
|
||||
}
|
||||
/// Get a new [`Workpool`] from the current config
|
||||
pub fn get_pool(&self) -> WorkpoolResult<Workpool<Inp, UIn, Lv, Lp, Ex>> {
|
||||
self.get_pool_with_workers(self.count)
|
||||
}
|
||||
/// Get a [`Workpool`] with the base config but with a different number of workers
|
||||
pub fn get_pool_with_workers(
|
||||
&self,
|
||||
count: usize,
|
||||
) -> WorkpoolResult<Workpool<Inp, UIn, Lv, Lp, Ex>> {
|
||||
Workpool::new(
|
||||
count,
|
||||
self.init_pre_loop_var.clone(),
|
||||
self.on_loop.clone(),
|
||||
self.on_exit.clone(),
|
||||
self.needs_iterator_pool,
|
||||
self.expected_max_sends,
|
||||
)
|
||||
}
|
||||
/// Get a [`Workpool`] with the base config but with a custom loop-stage closure
|
||||
pub fn with_loop_closure<Dlp>(&self, lp: Dlp) -> WorkpoolResult<Workpool<Inp, UIn, Lv, Dlp, Ex>>
|
||||
where
|
||||
Dlp: Fn(&mut Inp, UIn) + Clone + Send + Sync + 'static,
|
||||
{
|
||||
Workpool::new(
|
||||
self.count,
|
||||
self.init_pre_loop_var.clone(),
|
||||
lp,
|
||||
self.on_exit.clone(),
|
||||
self.needs_iterator_pool,
|
||||
self.expected_max_sends,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Workpool
|
||||
///
|
||||
/// A Workpool is a generic synchronous thread pool that can be used to perform, well, anything.
|
||||
/// A workpool has to be initialized with the number of workers, the pre_loop_variable (set this
|
||||
/// to None if there isn't any). what to do on loop and what to do on exit of each worker. The
|
||||
/// closures are kept as `Clone`able types just to reduce complexity with copy (we were lazy).
|
||||
///
|
||||
/// ## Clones
|
||||
///
|
||||
/// Workpool clones simply create a new workpool with the same on_exit, on_loop and init_pre_loop_var
|
||||
/// configurations. This provides a very convenient interface if one desires to use multiple workpools
|
||||
/// to do the _same kind of thing_
|
||||
///
|
||||
/// ## Actual thread count
|
||||
///
|
||||
/// The actual thread count will depend on whether the caller requests the initialization of an
|
||||
/// iterator pool or not. If the caller does request for an iterator pool, then the number of threads
|
||||
/// spawned will be twice the number of the set workers. Else, the number of spawned threads is equal
|
||||
/// to the number of workers.
|
||||
pub struct Workpool<Inp, UIn, Lv, Lp, Ex> {
|
||||
/// the workers
|
||||
workers: Vec<Worker>,
|
||||
/// the sender that sends jobs
|
||||
job_distributor: CSender<JobType<UIn>>,
|
||||
/// the function that sets the pre-loop variable
|
||||
init_pre_loop_var: Lv,
|
||||
/// the function to be executed on worker termination
|
||||
on_exit: Ex,
|
||||
/// the function to be executed on loop
|
||||
on_loop: Lp,
|
||||
/// a marker for `Inp` since no parameters use it directly
|
||||
_marker: PhantomData<Inp>,
|
||||
/// check if self needs a pool for parallel iterators
|
||||
needs_iterator_pool: bool,
|
||||
/// expected maximum number of sends
|
||||
expected_max_sends: Option<usize>,
|
||||
}
|
||||
|
||||
impl<Inp: 'static, UIn, Lv, Ex, Lp> Workpool<Inp, UIn, Lv, Lp, Ex>
|
||||
where
|
||||
UIn: Send + Sync + 'static,
|
||||
Ex: Fn(&mut Inp) + Send + Sync + 'static + Clone,
|
||||
Lv: Fn() -> Inp + Send + Sync + 'static + Clone,
|
||||
Lp: Fn(&mut Inp, UIn) + Send + Sync + 'static + Clone,
|
||||
Inp: Sync,
|
||||
{
|
||||
/// Create a new workpool
|
||||
pub fn new(
|
||||
count: usize,
|
||||
init_pre_loop_var: Lv,
|
||||
on_loop: Lp,
|
||||
on_exit: Ex,
|
||||
needs_iterator_pool: bool,
|
||||
expected_max_sends: Option<usize>,
|
||||
) -> WorkpoolResult<Self> {
|
||||
// init threadpool for iterator
|
||||
if needs_iterator_pool {
|
||||
// initialize a global threadpool for parallel iterators
|
||||
let _ = rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(count)
|
||||
.build_global();
|
||||
}
|
||||
assert!(count != 0, "Runtime panic: Bad value `0` for thread count");
|
||||
let (sender, receiver) = match expected_max_sends {
|
||||
Some(limit) => bounded(limit),
|
||||
None => unbounded(),
|
||||
};
|
||||
let (wgtx, wgrx) = bounded::<()>(count);
|
||||
let mut workers = Vec::with_capacity(count);
|
||||
for i in 0..count {
|
||||
workers.push(Worker::new(
|
||||
i,
|
||||
receiver.clone(),
|
||||
init_pre_loop_var.clone(),
|
||||
on_exit.clone(),
|
||||
on_loop.clone(),
|
||||
wgtx.clone(),
|
||||
));
|
||||
}
|
||||
drop(wgtx);
|
||||
let sum: usize = wgrx.iter().map(|_| 1usize).sum();
|
||||
if sum == count {
|
||||
Ok(Self {
|
||||
workers,
|
||||
job_distributor: sender,
|
||||
init_pre_loop_var,
|
||||
on_exit,
|
||||
on_loop,
|
||||
_marker: PhantomData,
|
||||
needs_iterator_pool,
|
||||
expected_max_sends,
|
||||
})
|
||||
} else {
|
||||
Err(WorkpoolError::ThreadStartFailure(count, sum))
|
||||
}
|
||||
}
|
||||
pub fn clone_pool(&self) -> WorkpoolResult<Self> {
|
||||
Self::new(
|
||||
self.workers.len(),
|
||||
self.init_pre_loop_var.clone(),
|
||||
self.on_loop.clone(),
|
||||
self.on_exit.clone(),
|
||||
self.needs_iterator_pool,
|
||||
self.expected_max_sends,
|
||||
)
|
||||
}
|
||||
/// Execute something
|
||||
pub fn execute(&self, inp: UIn) {
|
||||
self.job_distributor
|
||||
.send(JobType::Task(inp))
|
||||
.expect("Worker thread crashed")
|
||||
}
|
||||
/// Execute something that can be executed as a parallel iterator
|
||||
/// For the best performance, it is recommended that you pass true for `needs_iterator_pool`
|
||||
/// on initialization of the [`Workpool`]
|
||||
pub fn execute_iter(&self, iter: impl IntoParallelIterator<Item = UIn>) {
|
||||
iter.into_par_iter().for_each(|inp| self.execute(inp))
|
||||
}
|
||||
/// Does the same thing as [`execute_iter`] but drops self ensuring that all the
|
||||
/// workers actually finish their tasks
|
||||
pub fn execute_and_finish_iter(self, iter: impl IntoParallelIterator<Item = UIn>) {
|
||||
self.execute_iter(iter);
|
||||
drop(self);
|
||||
}
|
||||
/// Initialize a new [`Workpool`] with the default count of threads. This is equal
|
||||
/// to 2 * the number of logical cores.
|
||||
pub fn new_default_threads(
|
||||
init_pre_loop_var: Lv,
|
||||
on_loop: Lp,
|
||||
on_exit: Ex,
|
||||
needs_iterator_pool: bool,
|
||||
expected_max_sends: Option<usize>,
|
||||
) -> WorkpoolResult<Self> {
|
||||
// we'll naively use the number of CPUs present on the system times 2 to determine
|
||||
// the number of workers (sure the scheduler does tricks all the time)
|
||||
let worker_count = thread::available_parallelism().map_or(1, usize::from) * 2;
|
||||
Self::new(
|
||||
worker_count,
|
||||
init_pre_loop_var,
|
||||
on_loop,
|
||||
on_exit,
|
||||
needs_iterator_pool,
|
||||
expected_max_sends,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inp, UIn, Lv, Lp, Ex> Drop for Workpool<Inp, UIn, Lp, Lv, Ex> {
|
||||
fn drop(&mut self) {
|
||||
for _ in &self.workers {
|
||||
self.job_distributor.send(JobType::Nothing).unwrap();
|
||||
}
|
||||
for worker in &mut self.workers {
|
||||
if let Some(thread) = worker.thread.take() {
|
||||
thread.join().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod utils {
|
||||
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
use rand::distributions::{Alphanumeric, Standard};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::TryReserveError;
|
||||
|
||||
/// Generate a random UTF-8 string
|
||||
pub fn ran_string(len: usize, rand: impl rand::Rng) -> String {
|
||||
let rand_string: String = rand
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(len)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
rand_string
|
||||
}
|
||||
/// Generate a vector of random bytes
|
||||
pub fn ran_bytes(len: usize, rand: impl rand::Rng) -> Vec<u8> {
|
||||
rand.sample_iter(&Standard).take(len).collect()
|
||||
}
|
||||
/// Generate multiple vectors of random bytes
|
||||
pub fn generate_random_byte_vector(
|
||||
count: usize,
|
||||
size: usize,
|
||||
mut rng: impl rand::Rng,
|
||||
unique: bool,
|
||||
) -> Result<Vec<Vec<u8>>, TryReserveError> {
|
||||
if unique {
|
||||
let mut keys = HashSet::new();
|
||||
keys.try_reserve(size)?;
|
||||
(0..count).into_iter().for_each(|_| {
|
||||
let mut ran = ran_bytes(size, &mut rng);
|
||||
while keys.contains(&ran) {
|
||||
ran = ran_bytes(size, &mut rng);
|
||||
}
|
||||
keys.insert(ran);
|
||||
});
|
||||
Ok(keys.into_iter().collect())
|
||||
} else {
|
||||
let mut keys = Vec::new();
|
||||
keys.try_reserve_exact(size)?;
|
||||
let ran_byte_key = ran_bytes(size, &mut rng);
|
||||
(0..count).for_each(|_| keys.push(ran_byte_key.clone()));
|
||||
Ok(keys)
|
||||
}
|
||||
}
|
||||
/// Generate a vector of random UTF-8 valid strings
|
||||
pub fn generate_random_string_vector(
|
||||
count: usize,
|
||||
size: usize,
|
||||
mut rng: impl rand::Rng,
|
||||
unique: bool,
|
||||
) -> Result<Vec<String>, TryReserveError> {
|
||||
if unique {
|
||||
let mut keys = HashSet::new();
|
||||
keys.try_reserve(size)?;
|
||||
(0..count).into_iter().for_each(|_| {
|
||||
let mut ran = ran_string(size, &mut rng);
|
||||
while keys.contains(&ran) {
|
||||
ran = ran_string(size, &mut rng);
|
||||
}
|
||||
keys.insert(ran);
|
||||
});
|
||||
Ok(keys.into_iter().collect())
|
||||
} else {
|
||||
let mut keys = Vec::new();
|
||||
keys.try_reserve_exact(size)?;
|
||||
(0..count)
|
||||
.into_iter()
|
||||
.map(|_| ran_string(size, &mut rng))
|
||||
.for_each(|bytes| keys.push(bytes));
|
||||
Ok(keys)
|
||||
}
|
||||
}
|
||||
pub fn rand_alphastring(len: usize, rng: &mut impl rand::Rng) -> String {
|
||||
(0..len)
|
||||
.map(|_| {
|
||||
let idx = rng.gen_range(0..CHARSET.len());
|
||||
CHARSET[idx] as char
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Jun 18 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// A trait for aggresive erroring
|
||||
pub trait ExitError<T> {
|
||||
/// Abort the process if the type errors with an error code or
|
||||
/// return the type
|
||||
fn exit_error<Ms>(self, msg: Ms) -> T
|
||||
where
|
||||
Ms: ToString;
|
||||
}
|
||||
|
||||
impl<T, E> ExitError<T> for Result<T, E>
|
||||
where
|
||||
E: fmt::Display,
|
||||
{
|
||||
fn exit_error<Ms>(self, msg: Ms) -> T
|
||||
where
|
||||
Ms: ToString,
|
||||
{
|
||||
match self {
|
||||
Self::Err(e) => {
|
||||
log::error!("{} : '{}'", msg.to_string(), e);
|
||||
std::process::exit(0x01);
|
||||
}
|
||||
Self::Ok(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExitError<T> for Option<T> {
|
||||
fn exit_error<Ms>(self, msg: Ms) -> T
|
||||
where
|
||||
Ms: ToString,
|
||||
{
|
||||
match self {
|
||||
Self::None => {
|
||||
log::error!("{}", msg.to_string());
|
||||
std::process::exit(0x01);
|
||||
}
|
||||
Self::Some(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
Skytable is a free and open-source NoSQL database that aims
|
||||
to provide flexibility in data modeling at scale.
|
||||
The `skytable` package contains the database server (`skyd`),
|
||||
an interactive command-line client (`skysh`), a benchmarking
|
||||
tool (`sky-bench`) and a migration tool (`sky-migrate`).
|
||||
an interactive command-line client (`skysh`) and a benchmarking
|
||||
tool (`sky-bench`).
|
@ -1,15 +1,35 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
SKY_DIR=/var/lib/skytable
|
||||
systemctl daemon-reload
|
||||
|
||||
if [ $1 = "install" ]; then
|
||||
echo "Doing '$1'"
|
||||
if [ "$1" = "configure" ]; then
|
||||
# Enable and start skyd on fresh install
|
||||
systemctl enable skyd
|
||||
fi
|
||||
systemctl start skyd
|
||||
echo "Generating password and configuration"
|
||||
|
||||
if [ $1 = "upgrade" ]; then
|
||||
if [ -f /var/lib/skytable/config.yaml ]; then
|
||||
echo "Configuration already exists. Not updating configuration."
|
||||
else
|
||||
mv /var/lib/skytable/config.yaml.tmp /var/lib/skytable/config.yaml
|
||||
# Generate and set password
|
||||
if [ ! -f "$SKY_DIR/config.yaml" ]; then
|
||||
echo "Error: The file $SKY_DIR/config.yaml does not exist."
|
||||
exit 1 # Exit with an error code
|
||||
fi
|
||||
PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 16 ; echo '')
|
||||
sed -i "s/rootpass/$PASSWORD/g" "$SKY_DIR/config.yaml"
|
||||
echo "Your root password is: '$PASSWORD'. You can change this using the config file in $SKY_DIR/config.yaml"
|
||||
fi
|
||||
elif [ "$1" = "upgrade" ]; then
|
||||
# On upgrade, just restart skyd
|
||||
echo "Not changing configuration. This is an upgrade."
|
||||
systemctl stop skyd
|
||||
systemctl start skyd
|
||||
fi
|
||||
|
||||
systemctl start skyd
|
||||
echo "Done executing post install scripts."
|
||||
|
||||
#DEBHELPER#
|
||||
#DEBHELPER#
|
||||
|
@ -0,0 +1,27 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
SKY_DIR=/var/lib/skytable
|
||||
SERVICE_NAME=skyd
|
||||
|
||||
# Only perform cleanup on package removal, not on upgrade
|
||||
case "$1" in
|
||||
remove)
|
||||
echo "Removing the skytable user..."
|
||||
# Remove the user and group, if they exist
|
||||
# This will not remove the /var/lib/skytable directory
|
||||
if getent passwd skytable > /dev/null; then
|
||||
deluser --system skytable
|
||||
fi
|
||||
if getent group skytable > /dev/null; then
|
||||
delgroup skytable
|
||||
fi
|
||||
echo "Removing the configuration file ..."
|
||||
rm /var/lib/skytable/config.yaml
|
||||
echo "Cleanup complete."
|
||||
;;
|
||||
*)
|
||||
# No action required for other cases (upgrade, failed-upgrade, etc.)
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
@ -0,0 +1,6 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
echo "Stopping processes"
|
||||
systemctl stop skyd
|
||||
systemctl disable skyd
|
||||
echo "Stopped processes"
|
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
CONFIG_FILE="/var/lib/skytable/config.yaml"
|
||||
PASSWORD_MARKER="rootpass"
|
||||
IP_MARKER="127.0.0.1"
|
||||
|
||||
generate_password() {
|
||||
uuidgen | cut -c -16
|
||||
}
|
||||
|
||||
sed -i "s/$IP_MARKER/0.0.0.0/g" "$CONFIG_FILE"
|
||||
|
||||
if grep -q "$PASSWORD_MARKER" "$CONFIG_FILE"; then
|
||||
# Password not set, generate a new one
|
||||
PASSWORD=$(generate_password)
|
||||
sed -i "s/$PASSWORD_MARKER/$PASSWORD/g" "$CONFIG_FILE"
|
||||
echo "Generated Password: $PASSWORD"
|
||||
else
|
||||
echo "Using existing password in config file"
|
||||
fi
|
||||
|
||||
exec skyd --config "$CONFIG_FILE"
|
@ -1,9 +0,0 @@
|
||||
fn main() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
println!("cargo:rerun-if-changed=native/flock-posix.c");
|
||||
cc::Build::new()
|
||||
.file("native/flock-posix.c")
|
||||
.compile("libflock-posix.a");
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Aug 19 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # `DEL` queries
|
||||
//! This module provides functions to work with `DEL` queries
|
||||
|
||||
use crate::{
|
||||
corestore::table::DataModel, dbnet::prelude::*,
|
||||
kvengine::encoding::ENCODING_LUT_ITER, util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run a `DEL` query
|
||||
///
|
||||
/// Do note that this function is blocking since it acquires a write lock.
|
||||
/// It will write an entire datagroup, for this `del` action
|
||||
fn del(handle: &Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |size| size != 0)?;
|
||||
let table = get_tbl_ref!(handle, con);
|
||||
macro_rules! remove {
|
||||
($engine:expr) => {{
|
||||
let encoding_is_okay = ENCODING_LUT_ITER[$engine.is_key_encoded()](act.as_ref());
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
let done_howmany: Option<usize>;
|
||||
{
|
||||
if registry::state_okay() {
|
||||
let mut many = 0;
|
||||
act.for_each(|key| {
|
||||
many += $engine.remove_unchecked(key) as usize;
|
||||
});
|
||||
done_howmany = Some(many);
|
||||
} else {
|
||||
done_howmany = None;
|
||||
}
|
||||
}
|
||||
if let Some(done_howmany) = done_howmany {
|
||||
con.write_usize(done_howmany).await?;
|
||||
} else {
|
||||
con._write_raw(P::RCODE_SERVER_ERR).await?;
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
}};
|
||||
}
|
||||
match table.get_model_ref() {
|
||||
DataModel::KV(kve) => {
|
||||
remove!(kve)
|
||||
}
|
||||
DataModel::KVExtListmap(kvlmap) => {
|
||||
remove!(kvlmap)
|
||||
}
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => return util::err(P::RSTRING_WRONG_MODEL),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Aug 19 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # `EXISTS` queries
|
||||
//! This module provides functions to work with `EXISTS` queries
|
||||
|
||||
use crate::{
|
||||
corestore::table::DataModel, dbnet::prelude::*,
|
||||
kvengine::encoding::ENCODING_LUT_ITER, queryengine::ActionIter, util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run an `EXISTS` query
|
||||
fn exists(handle: &Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len != 0)?;
|
||||
let mut how_many_of_them_exist = 0usize;
|
||||
macro_rules! exists {
|
||||
($engine:expr) => {{
|
||||
let encoding_is_okay = ENCODING_LUT_ITER[$engine.is_key_encoded()](act.as_ref());
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
act.for_each(|key| {
|
||||
how_many_of_them_exist += $engine.exists_unchecked(key) as usize;
|
||||
});
|
||||
con.write_usize(how_many_of_them_exist).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
}};
|
||||
}
|
||||
let tbl = get_tbl_ref!(handle, con);
|
||||
match tbl.get_model_ref() {
|
||||
DataModel::KV(kve) => exists!(kve),
|
||||
DataModel::KVExtListmap(kve) => exists!(kve),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => return util::err(P::RSTRING_WRONG_MODEL),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Created on Thu Sep 24 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{dbnet::prelude::*, queryengine::ActionIter};
|
||||
|
||||
action!(
|
||||
/// Delete all the keys in the database
|
||||
fn flushdb(handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len < 2)?;
|
||||
if registry::state_okay() {
|
||||
if act.is_empty() {
|
||||
// flush the current table
|
||||
get_tbl_ref!(handle, con).truncate_table();
|
||||
} else {
|
||||
// flush the entity
|
||||
let raw_entity = unsafe { act.next_unchecked() };
|
||||
let entity = handle_entity!(con, raw_entity);
|
||||
get_tbl!(&entity, handle, con).truncate_table();
|
||||
}
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
} else {
|
||||
con._write_raw(P::RCODE_SERVER_ERR).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Aug 14 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # `GET` queries
|
||||
//! This module provides functions to work with `GET` queries
|
||||
|
||||
use crate::{dbnet::prelude::*, util::compiler};
|
||||
|
||||
action!(
|
||||
/// Run a `GET` query
|
||||
fn get(
|
||||
handle: &crate::corestore::Corestore,
|
||||
con: &mut Connection<C, P>,
|
||||
mut act: ActionIter<'a>,
|
||||
) {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
unsafe {
|
||||
match kve.get_cloned(act.next_unchecked()) {
|
||||
Ok(Some(val)) => {
|
||||
con.write_mono_length_prefixed_with_tsymbol(&val, kve.get_value_tsymbol())
|
||||
.await?
|
||||
}
|
||||
Err(_) => compiler::cold_err(con._write_raw(P::RCODE_ENCODING_ERROR)).await?,
|
||||
Ok(_) => con._write_raw(P::RCODE_NIL).await?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Created on Sun Sep 27 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::dbnet::prelude::*;
|
||||
|
||||
action!(
|
||||
/// Run a `KEYLEN` query
|
||||
///
|
||||
/// At this moment, `keylen` only supports a single key
|
||||
fn keylen(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
let res: Option<usize> = {
|
||||
let reader = handle.get_table_with::<P, KVEBlob>()?;
|
||||
unsafe {
|
||||
// UNSAFE(@ohsayan): this is completely safe as we've already checked
|
||||
// the number of arguments is one
|
||||
match reader.get(act.next_unchecked()) {
|
||||
Ok(v) => v.map(|b| b.len()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(value) = res {
|
||||
// Good, we got the key's length, write it off to the stream
|
||||
con.write_usize(value).await?;
|
||||
} else {
|
||||
// Ah, couldn't find that key
|
||||
con._write_raw(P::RCODE_NIL).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Sep 17 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{corestore::SharedSlice, dbnet::prelude::*};
|
||||
|
||||
const LEN: &[u8] = "LEN".as_bytes();
|
||||
const LIMIT: &[u8] = "LIMIT".as_bytes();
|
||||
const VALUEAT: &[u8] = "VALUEAT".as_bytes();
|
||||
const LAST: &[u8] = "LAST".as_bytes();
|
||||
const FIRST: &[u8] = "FIRST".as_bytes();
|
||||
const RANGE: &[u8] = "RANGE".as_bytes();
|
||||
|
||||
struct Range {
|
||||
start: usize,
|
||||
stop: Option<usize>,
|
||||
}
|
||||
|
||||
impl Range {
|
||||
pub fn new(start: usize) -> Self {
|
||||
Self { start, stop: None }
|
||||
}
|
||||
pub fn set_stop(&mut self, stop: usize) {
|
||||
self.stop = Some(stop);
|
||||
}
|
||||
pub fn into_vec(self, slice: &[SharedSlice]) -> Option<Vec<SharedSlice>> {
|
||||
slice
|
||||
.get(self.start..self.stop.unwrap_or(slice.len()))
|
||||
.map(|slc| slc.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
action! {
|
||||
/// Handle an `LGET` query for the list model (KVExt)
|
||||
/// ## Syntax
|
||||
/// - `LGET <mylist>` will return the full list
|
||||
/// - `LGET <mylist> LEN` will return the length of the list
|
||||
/// - `LGET <mylist> LIMIT <limit>` will return a maximum of `limit` elements
|
||||
/// - `LGET <mylist> VALUEAT <index>` will return the value at the provided index
|
||||
/// - `LGET <mylist> FIRST` will return the first item
|
||||
/// - `LGET <mylist> LAST` will return the last item
|
||||
/// if it exists
|
||||
fn lget(handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len != 0)?;
|
||||
let listmap = handle.get_table_with::<P, KVEList>()?;
|
||||
// get the list name
|
||||
let listname = unsafe { act.next_unchecked() };
|
||||
// now let us see what we need to do
|
||||
macro_rules! get_numeric_count {
|
||||
() => {
|
||||
match unsafe { String::from_utf8_lossy(act.next_unchecked()) }.parse::<usize>() {
|
||||
Ok(int) => int,
|
||||
Err(_) => return util::err(P::RCODE_WRONGTYPE_ERR),
|
||||
}
|
||||
};
|
||||
}
|
||||
match act.next_uppercase().as_ref() {
|
||||
None => {
|
||||
// just return everything in the list
|
||||
let items = match listmap.list_cloned_full(listname) {
|
||||
Ok(Some(list)) => list,
|
||||
Ok(None) => return Err(P::RCODE_NIL.into()),
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
};
|
||||
writelist!(con, listmap, items);
|
||||
}
|
||||
Some(subaction) => {
|
||||
match subaction.as_ref() {
|
||||
LEN => {
|
||||
ensure_length::<P>(act.len(), |len| len == 0)?;
|
||||
match listmap.list_len(listname) {
|
||||
Ok(Some(len)) => con.write_usize(len).await?,
|
||||
Ok(None) => return Err(P::RCODE_NIL.into()),
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
}
|
||||
}
|
||||
LIMIT => {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
let count = get_numeric_count!();
|
||||
match listmap.list_cloned(listname, count) {
|
||||
Ok(Some(items)) => writelist!(con, listmap, items),
|
||||
Ok(None) => return Err(P::RCODE_NIL.into()),
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
}
|
||||
}
|
||||
VALUEAT => {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
let idx = get_numeric_count!();
|
||||
let maybe_value = listmap.get(listname).map(|list| {
|
||||
list.map(|lst| lst.read().get(idx).cloned())
|
||||
});
|
||||
match maybe_value {
|
||||
Ok(v) => match v {
|
||||
Some(Some(value)) => {
|
||||
con.write_mono_length_prefixed_with_tsymbol(
|
||||
&value, listmap.get_value_tsymbol()
|
||||
).await?;
|
||||
}
|
||||
Some(None) => {
|
||||
// bad index
|
||||
return Err(P::RSTRING_LISTMAP_BAD_INDEX.into());
|
||||
}
|
||||
None => {
|
||||
// not found
|
||||
return Err(P::RCODE_NIL.into());
|
||||
}
|
||||
}
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
}
|
||||
}
|
||||
LAST => {
|
||||
ensure_length::<P>(act.len(), |len| len == 0)?;
|
||||
let maybe_value = listmap.get(listname).map(|list| {
|
||||
list.map(|lst| lst.read().last().cloned())
|
||||
});
|
||||
match maybe_value {
|
||||
Ok(v) => match v {
|
||||
Some(Some(value)) => {
|
||||
con.write_mono_length_prefixed_with_tsymbol(
|
||||
&value, listmap.get_value_tsymbol()
|
||||
).await?;
|
||||
},
|
||||
Some(None) => return Err(P::RSTRING_LISTMAP_LIST_IS_EMPTY.into()),
|
||||
None => return Err(P::RCODE_NIL.into()),
|
||||
}
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
}
|
||||
}
|
||||
FIRST => {
|
||||
ensure_length::<P>(act.len(), |len| len == 0)?;
|
||||
let maybe_value = listmap.get(listname).map(|list| {
|
||||
list.map(|lst| lst.read().first().cloned())
|
||||
});
|
||||
match maybe_value {
|
||||
Ok(v) => match v {
|
||||
Some(Some(value)) => {
|
||||
con.write_mono_length_prefixed_with_tsymbol(
|
||||
&value, listmap.get_value_tsymbol()
|
||||
).await?;
|
||||
},
|
||||
Some(None) => return Err(P::RSTRING_LISTMAP_LIST_IS_EMPTY.into()),
|
||||
None => return Err(P::RCODE_NIL.into()),
|
||||
}
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
}
|
||||
}
|
||||
RANGE => {
|
||||
match act.next_string_owned() {
|
||||
Some(start) => {
|
||||
let start: usize = match start.parse() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return util::err(P::RCODE_WRONGTYPE_ERR),
|
||||
};
|
||||
let mut range = Range::new(start);
|
||||
if let Some(stop) = act.next_string_owned() {
|
||||
let stop: usize = match stop.parse() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return util::err(P::RCODE_WRONGTYPE_ERR),
|
||||
};
|
||||
range.set_stop(stop);
|
||||
};
|
||||
match listmap.get(listname) {
|
||||
Ok(Some(list)) => {
|
||||
let ret = range.into_vec(&list.read());
|
||||
match ret {
|
||||
Some(ret) => {
|
||||
writelist!(con, listmap, ret);
|
||||
},
|
||||
None => return Err(P::RSTRING_LISTMAP_BAD_INDEX.into()),
|
||||
}
|
||||
}
|
||||
Ok(None) => return Err(P::RCODE_NIL.into()),
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
}
|
||||
}
|
||||
None => return Err(P::RCODE_ACTION_ERR.into()),
|
||||
}
|
||||
}
|
||||
_ => return Err(P::RCODE_UNKNOWN_ACTION.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Sep 15 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{corestore::SharedSlice, dbnet::prelude::*, util::compiler};
|
||||
|
||||
const CLEAR: &[u8] = "CLEAR".as_bytes();
|
||||
const PUSH: &[u8] = "PUSH".as_bytes();
|
||||
const REMOVE: &[u8] = "REMOVE".as_bytes();
|
||||
const INSERT: &[u8] = "INSERT".as_bytes();
|
||||
const POP: &[u8] = "POP".as_bytes();
|
||||
|
||||
action! {
|
||||
/// Handle `LMOD` queries
|
||||
/// ## Syntax
|
||||
/// - `LMOD <mylist> push <value>`
|
||||
/// - `LMOD <mylist> pop <optional idx>`
|
||||
/// - `LMOD <mylist> insert <index> <value>`
|
||||
/// - `LMOD <mylist> remove <index>`
|
||||
/// - `LMOD <mylist> clear`
|
||||
fn lmod(handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len > 1)?;
|
||||
let listmap = handle.get_table_with::<P, KVEList>()?;
|
||||
// get the list name
|
||||
let listname = unsafe { act.next_unchecked() };
|
||||
macro_rules! get_numeric_count {
|
||||
() => {
|
||||
match unsafe { String::from_utf8_lossy(act.next_unchecked()) }.parse::<usize>() {
|
||||
Ok(int) => int,
|
||||
Err(_) => return Err(P::RCODE_WRONGTYPE_ERR.into()),
|
||||
}
|
||||
};
|
||||
}
|
||||
// now let us see what we need to do
|
||||
match unsafe { act.next_uppercase_unchecked() }.as_ref() {
|
||||
CLEAR => {
|
||||
ensure_length::<P>(act.len(), |len| len == 0)?;
|
||||
let list = match listmap.get_inner_ref().get(listname) {
|
||||
Some(l) => l,
|
||||
_ => return Err(P::RCODE_NIL.into()),
|
||||
};
|
||||
let okay = if registry::state_okay() {
|
||||
list.write().clear();
|
||||
P::RCODE_OKAY
|
||||
} else {
|
||||
P::RCODE_SERVER_ERR
|
||||
};
|
||||
con._write_raw(okay).await?
|
||||
}
|
||||
PUSH => {
|
||||
ensure_boolean_or_aerr::<P>(!act.is_empty())?;
|
||||
let list = match listmap.get_inner_ref().get(listname) {
|
||||
Some(l) => l,
|
||||
_ => return Err(P::RCODE_NIL.into()),
|
||||
};
|
||||
let venc_ok = listmap.get_val_encoder();
|
||||
let ret = if compiler::likely(act.as_ref().all(venc_ok)) {
|
||||
if registry::state_okay() {
|
||||
list.write().extend(act.map(SharedSlice::new));
|
||||
P::RCODE_OKAY
|
||||
} else {
|
||||
P::RCODE_SERVER_ERR
|
||||
}
|
||||
} else {
|
||||
P::RCODE_ENCODING_ERROR
|
||||
};
|
||||
con._write_raw(ret).await?
|
||||
}
|
||||
REMOVE => {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
let idx_to_remove = get_numeric_count!();
|
||||
if registry::state_okay() {
|
||||
let maybe_value = listmap.get_inner_ref().get(listname).map(|list| {
|
||||
let mut wlock = list.write();
|
||||
if idx_to_remove < wlock.len() {
|
||||
wlock.remove(idx_to_remove);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
con._write_raw(P::OKAY_BADIDX_NIL_NLUT[maybe_value]).await?
|
||||
} else {
|
||||
return Err(P::RCODE_SERVER_ERR.into());
|
||||
}
|
||||
}
|
||||
INSERT => {
|
||||
ensure_length::<P>(act.len(), |len| len == 2)?;
|
||||
let idx_to_insert_at = get_numeric_count!();
|
||||
let bts = unsafe { act.next_unchecked() };
|
||||
let ret = if compiler::likely(listmap.is_val_ok(bts)) {
|
||||
if registry::state_okay() {
|
||||
// okay state, good to insert
|
||||
let maybe_insert = match listmap.get(listname) {
|
||||
Ok(lst) => lst.map(|list| {
|
||||
let mut wlock = list.write();
|
||||
if idx_to_insert_at < wlock.len() {
|
||||
// we can insert
|
||||
wlock.insert(idx_to_insert_at, SharedSlice::new(bts));
|
||||
true
|
||||
} else {
|
||||
// oops, out of bounds
|
||||
false
|
||||
}
|
||||
}),
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
};
|
||||
P::OKAY_BADIDX_NIL_NLUT[maybe_insert]
|
||||
} else {
|
||||
// flush broken; server err
|
||||
P::RCODE_SERVER_ERR
|
||||
}
|
||||
} else {
|
||||
// encoding failed, uh
|
||||
P::RCODE_ENCODING_ERROR
|
||||
};
|
||||
con._write_raw(ret).await?
|
||||
}
|
||||
POP => {
|
||||
ensure_length::<P>(act.len(), |len| len < 2)?;
|
||||
let idx = if act.len() == 1 {
|
||||
// we have an idx
|
||||
Some(get_numeric_count!())
|
||||
} else {
|
||||
// no idx
|
||||
None
|
||||
};
|
||||
if registry::state_okay() {
|
||||
let maybe_pop = match listmap.get(listname) {
|
||||
Ok(lst) => lst.map(|list| {
|
||||
let mut wlock = list.write();
|
||||
if let Some(idx) = idx {
|
||||
if idx < wlock.len() {
|
||||
// so we can pop
|
||||
Some(wlock.remove(idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
wlock.pop()
|
||||
}
|
||||
}),
|
||||
Err(()) => return Err(P::RCODE_ENCODING_ERROR.into()),
|
||||
};
|
||||
match maybe_pop {
|
||||
Some(Some(val)) => {
|
||||
con.write_mono_length_prefixed_with_tsymbol(
|
||||
&val, listmap.get_value_tsymbol()
|
||||
).await?;
|
||||
}
|
||||
Some(None) => {
|
||||
con._write_raw(P::RSTRING_LISTMAP_BAD_INDEX).await?;
|
||||
}
|
||||
None => con._write_raw(P::RCODE_NIL).await?,
|
||||
}
|
||||
} else {
|
||||
con._write_raw(P::RCODE_SERVER_ERR).await?
|
||||
}
|
||||
}
|
||||
_ => con._write_raw(P::RCODE_UNKNOWN_ACTION).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Sep 07 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
// modules
|
||||
pub mod lget;
|
||||
pub mod lmod;
|
||||
|
||||
use crate::{corestore::SharedSlice, dbnet::prelude::*, kvengine::LockedVec};
|
||||
|
||||
action! {
|
||||
/// Handle an `LSET` query for the list model
|
||||
/// Syntax: `LSET <listname> <values ...>`
|
||||
fn lset(handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len > 0)?;
|
||||
let listmap = handle.get_table_with::<P, KVEList>()?;
|
||||
let listname = unsafe { act.next_unchecked_bytes() };
|
||||
let list = listmap.get_inner_ref();
|
||||
if registry::state_okay() {
|
||||
let did = if let Some(entry) = list.fresh_entry(listname) {
|
||||
let v: Vec<SharedSlice> = act.map(SharedSlice::new).collect();
|
||||
entry.insert(LockedVec::new(v));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
con._write_raw(P::OKAY_OVW_BLUT[did]).await?
|
||||
} else {
|
||||
con._write_raw(P::RCODE_SERVER_ERR).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Created on Thu May 13 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
corestore::{table::DataModel, SharedSlice},
|
||||
dbnet::prelude::*,
|
||||
};
|
||||
|
||||
const DEFAULT_COUNT: usize = 10;
|
||||
|
||||
action!(
|
||||
/// Run an `LSKEYS` query
|
||||
fn lskeys(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |size| size < 4)?;
|
||||
let (table, count) = if act.is_empty() {
|
||||
(get_tbl!(handle, con), DEFAULT_COUNT)
|
||||
} else if act.len() == 1 {
|
||||
// two args, could either be count or an entity
|
||||
let nextret = unsafe { act.next_unchecked() };
|
||||
if unsafe { ucidx!(nextret, 0) }.is_ascii_digit() {
|
||||
// noice, this is a number; let's try to parse it
|
||||
let count = if let Ok(cnt) = String::from_utf8_lossy(nextret).parse::<usize>() {
|
||||
cnt
|
||||
} else {
|
||||
return util::err(P::RCODE_WRONGTYPE_ERR);
|
||||
};
|
||||
(get_tbl!(handle, con), count)
|
||||
} else {
|
||||
// sigh, an entity
|
||||
let entity = handle_entity!(con, nextret);
|
||||
(get_tbl!(&entity, handle, con), DEFAULT_COUNT)
|
||||
}
|
||||
} else {
|
||||
// an entity and a count, gosh this fella is really trying us
|
||||
let entity_ret = unsafe { act.next().unsafe_unwrap() };
|
||||
let count_ret = unsafe { act.next().unsafe_unwrap() };
|
||||
let entity = handle_entity!(con, entity_ret);
|
||||
let count = if let Ok(cnt) = String::from_utf8_lossy(count_ret).parse::<usize>() {
|
||||
cnt
|
||||
} else {
|
||||
return util::err(P::RCODE_WRONGTYPE_ERR);
|
||||
};
|
||||
(get_tbl!(&entity, handle, con), count)
|
||||
};
|
||||
let tsymbol = match table.get_model_ref() {
|
||||
DataModel::KV(kv) => kv.get_value_tsymbol(),
|
||||
DataModel::KVExtListmap(kv) => kv.get_value_tsymbol(),
|
||||
};
|
||||
let items: Vec<SharedSlice> = match table.get_model_ref() {
|
||||
DataModel::KV(kv) => kv.get_inner_ref().get_keys(count),
|
||||
DataModel::KVExtListmap(kv) => kv.get_inner_ref().get_keys(count),
|
||||
};
|
||||
con.write_typed_non_null_array_header(items.len(), tsymbol)
|
||||
.await?;
|
||||
for key in items {
|
||||
con.write_typed_non_null_array_element(&key).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Created on Thu Nov 11 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
Don't modulo because it's an L1 miss and an L2 hit. Use lowbit checks to check for parity
|
||||
*/
|
||||
|
||||
#[macro_export]
|
||||
/// endian independent check to see if the lowbit is set or not. Returns true if the lowbit
|
||||
/// is set. this is undefined to be applied on signed values on one's complement targets
|
||||
macro_rules! is_lowbit_set {
|
||||
($v:expr) => {
|
||||
$v & 1 == 1
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
/// endian independent check to see if the lowbit is unset or not. Returns true if the lowbit
|
||||
/// is unset. this is undefined to be applied on signed values on one's complement targets
|
||||
macro_rules! is_lowbit_unset {
|
||||
($v:expr) => {
|
||||
$v & 1 == 0
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_tbl {
|
||||
($entity:expr, $store:expr, $con:expr) => {{
|
||||
$crate::actions::translate_ddl_error::<P, ::std::sync::Arc<$crate::corestore::table::Table>>(
|
||||
$store.get_table($entity),
|
||||
)?
|
||||
}};
|
||||
($store:expr, $con:expr) => {{
|
||||
match $store.get_ctable() {
|
||||
Some(tbl) => tbl,
|
||||
None => return $crate::util::err(P::RSTRING_DEFAULT_UNSET),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_tbl_ref {
|
||||
($store:expr, $con:expr) => {{
|
||||
match $store.get_ctable_ref() {
|
||||
Some(tbl) => tbl,
|
||||
None => return $crate::util::err(P::RSTRING_DEFAULT_UNSET),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! handle_entity {
|
||||
($con:expr, $ident:expr) => {{
|
||||
match $crate::blueql::util::from_slice_action_result::<P>(&$ident) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}};
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Created on Thu Aug 27 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
dbnet::prelude::*, kvengine::encoding::ENCODING_LUT_ITER, queryengine::ActionIter,
|
||||
util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run an `MGET` query
|
||||
///
|
||||
fn mget(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |size| size != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
let encoding_is_okay = ENCODING_LUT_ITER[kve.is_key_encoded()](act.as_ref());
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
con.write_typed_array_header(act.len(), kve.get_value_tsymbol())
|
||||
.await?;
|
||||
for key in act {
|
||||
match kve.get_cloned_unchecked(key) {
|
||||
Some(v) => con.write_typed_array_element(&v).await?,
|
||||
None => con.write_typed_array_element_null().await?,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Aug 19 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # Actions
|
||||
//!
|
||||
//! Actions are like shell commands, you provide arguments -- they return output. This module contains a collection
|
||||
//! of the actions supported by Skytable
|
||||
//!
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
pub mod dbsize;
|
||||
pub mod del;
|
||||
pub mod exists;
|
||||
pub mod flushdb;
|
||||
pub mod get;
|
||||
pub mod keylen;
|
||||
pub mod lists;
|
||||
pub mod lskeys;
|
||||
pub mod mget;
|
||||
pub mod mpop;
|
||||
pub mod mset;
|
||||
pub mod mupdate;
|
||||
pub mod pop;
|
||||
pub mod set;
|
||||
pub mod strong;
|
||||
pub mod update;
|
||||
pub mod uset;
|
||||
pub mod whereami;
|
||||
use {
|
||||
crate::{corestore::memstore::DdlError, protocol::interface::ProtocolSpec, util},
|
||||
std::io::Error as IoError,
|
||||
};
|
||||
|
||||
/// A generic result for actions
|
||||
pub type ActionResult<T> = Result<T, ActionError>;
|
||||
|
||||
/// Errors that can occur while running actions
|
||||
#[derive(Debug)]
|
||||
pub enum ActionError {
|
||||
ActionError(&'static [u8]),
|
||||
IoError(std::io::Error),
|
||||
}
|
||||
|
||||
impl PartialEq for ActionError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::ActionError(a1), Self::ActionError(a2)) => a1 == a2,
|
||||
(Self::IoError(ioe1), Self::IoError(ioe2)) => ioe1.to_string() == ioe2.to_string(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static [u8]> for ActionError {
|
||||
fn from(e: &'static [u8]) -> Self {
|
||||
Self::ActionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for ActionError {
|
||||
fn from(e: IoError) -> Self {
|
||||
Self::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn map_ddl_error_to_status<P: ProtocolSpec>(e: DdlError) -> ActionError {
|
||||
let r = match e {
|
||||
DdlError::AlreadyExists => P::RSTRING_ALREADY_EXISTS,
|
||||
DdlError::DdlTransactionFailure => P::RSTRING_DDL_TRANSACTIONAL_FAILURE,
|
||||
DdlError::DefaultNotFound => P::RSTRING_DEFAULT_UNSET,
|
||||
DdlError::NotEmpty => P::RSTRING_KEYSPACE_NOT_EMPTY,
|
||||
DdlError::NotReady => P::RSTRING_NOT_READY,
|
||||
DdlError::ObjectNotFound => P::RSTRING_CONTAINER_NOT_FOUND,
|
||||
DdlError::ProtectedObject => P::RSTRING_PROTECTED_OBJECT,
|
||||
DdlError::StillInUse => P::RSTRING_STILL_IN_USE,
|
||||
DdlError::WrongModel => P::RSTRING_WRONG_MODEL,
|
||||
};
|
||||
ActionError::ActionError(r)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn translate_ddl_error<P: ProtocolSpec, T>(r: Result<T, DdlError>) -> Result<T, ActionError> {
|
||||
match r {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => Err(map_ddl_error_to_status::<P>(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_length<P: ProtocolSpec>(len: usize, is_valid: fn(usize) -> bool) -> ActionResult<()> {
|
||||
if util::compiler::likely(is_valid(len)) {
|
||||
Ok(())
|
||||
} else {
|
||||
util::err(P::RCODE_ACTION_ERR)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_boolean_or_aerr<P: ProtocolSpec>(boolean: bool) -> ActionResult<()> {
|
||||
if util::compiler::likely(boolean) {
|
||||
Ok(())
|
||||
} else {
|
||||
util::err(P::RCODE_ACTION_ERR)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod heya {
|
||||
//! Respond to `HEYA` queries
|
||||
use crate::dbnet::prelude::*;
|
||||
action!(
|
||||
/// Returns a `HEY!` `Response`
|
||||
fn heya(_handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len == 0 || len == 1)?;
|
||||
if act.len() == 1 {
|
||||
let raw_byte = unsafe { act.next_unchecked() };
|
||||
con.write_mono_length_prefixed_with_tsymbol(raw_byte, b'+')
|
||||
.await?;
|
||||
} else {
|
||||
con._write_raw(P::ELEMRESP_HEYA).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Aug 11 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
corestore, dbnet::prelude::*, kvengine::encoding::ENCODING_LUT_ITER,
|
||||
queryengine::ActionIter, util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run an MPOP action
|
||||
fn mpop(handle: &corestore::Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len != 0)?;
|
||||
if registry::state_okay() {
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
let encoding_is_okay = ENCODING_LUT_ITER[kve.is_key_encoded()](act.as_ref());
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
con.write_typed_array_header(act.len(), kve.get_value_tsymbol())
|
||||
.await?;
|
||||
for key in act {
|
||||
match kve.pop_unchecked(key) {
|
||||
Some(val) => con.write_typed_array_element(&val).await?,
|
||||
None => con.write_typed_array_element_null().await?,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
} else {
|
||||
// don't begin the operation at all if the database is poisoned
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Created on Thu Aug 27 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
corestore::SharedSlice, dbnet::prelude::*,
|
||||
kvengine::encoding::ENCODING_LUT_ITER_PAIR, util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run an `MSET` query
|
||||
fn mset(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
let howmany = act.len();
|
||||
ensure_length::<P>(howmany, |size| size & 1 == 0 && size != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
let encoding_is_okay = ENCODING_LUT_ITER_PAIR[kve.get_encoding_tuple()](&act);
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
let done_howmany: Option<usize> = if registry::state_okay() {
|
||||
let mut didmany = 0;
|
||||
while let (Some(key), Some(val)) = (act.next(), act.next()) {
|
||||
didmany +=
|
||||
kve.set_unchecked(SharedSlice::new(key), SharedSlice::new(val)) as usize;
|
||||
}
|
||||
Some(didmany)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(done_howmany) = done_howmany {
|
||||
con.write_usize(done_howmany).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Created on Thu Aug 27 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
corestore::SharedSlice, dbnet::prelude::*,
|
||||
kvengine::encoding::ENCODING_LUT_ITER_PAIR, util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run an `MUPDATE` query
|
||||
fn mupdate(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
let howmany = act.len();
|
||||
ensure_length::<P>(howmany, |size| size & 1 == 0 && size != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
let encoding_is_okay = ENCODING_LUT_ITER_PAIR[kve.get_encoding_tuple()](&act);
|
||||
let done_howmany: Option<usize>;
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
if registry::state_okay() {
|
||||
let mut didmany = 0;
|
||||
while let (Some(key), Some(val)) = (act.next(), act.next()) {
|
||||
didmany +=
|
||||
kve.update_unchecked(SharedSlice::new(key), SharedSlice::new(val)) as usize;
|
||||
}
|
||||
done_howmany = Some(didmany);
|
||||
} else {
|
||||
done_howmany = None;
|
||||
}
|
||||
if let Some(done_howmany) = done_howmany {
|
||||
con.write_usize(done_howmany).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Created on Mon Jun 14 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::dbnet::prelude::*;
|
||||
|
||||
action! {
|
||||
fn pop(handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
let key = unsafe {
|
||||
// SAFETY: We have checked for there to be one arg
|
||||
act.next_unchecked()
|
||||
};
|
||||
if registry::state_okay() {
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
match kve.pop(key) {
|
||||
Ok(Some(val)) => con.write_mono_length_prefixed_with_tsymbol(
|
||||
&val, kve.get_value_tsymbol()
|
||||
).await?,
|
||||
Ok(None) => return util::err(P::RCODE_NIL),
|
||||
Err(()) => return util::err(P::RCODE_ENCODING_ERROR),
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Aug 14 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # `SET` queries
|
||||
//! This module provides functions to work with `SET` queries
|
||||
|
||||
use crate::{corestore::SharedSlice, dbnet::prelude::*, queryengine::ActionIter};
|
||||
|
||||
action!(
|
||||
/// Run a `SET` query
|
||||
fn set(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len == 2)?;
|
||||
if registry::state_okay() {
|
||||
let did_we = {
|
||||
let writer = handle.get_table_with::<P, KVEBlob>()?;
|
||||
match unsafe {
|
||||
// UNSAFE(@ohsayan): This is completely safe as we've already checked
|
||||
// that there are exactly 2 arguments
|
||||
writer.set(
|
||||
SharedSlice::new(act.next().unsafe_unwrap()),
|
||||
SharedSlice::new(act.next().unsafe_unwrap()),
|
||||
)
|
||||
} {
|
||||
Ok(true) => Some(true),
|
||||
Ok(false) => Some(false),
|
||||
Err(()) => None,
|
||||
}
|
||||
};
|
||||
con._write_raw(P::SET_NLUT[did_we]).await?;
|
||||
} else {
|
||||
con._write_raw(P::RCODE_SERVER_ERR).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Jul 30 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # Strong Actions
|
||||
//! Strong actions are like "do all" or "fail all" actions, built specifically for
|
||||
//! multiple keys. So let's say you used `SSET` instead of `MSET` for setting keys:
|
||||
//! what'd be the difference?
|
||||
//! In this case, if all the keys are non-existing, which is a requirement for `MSET`,
|
||||
//! only then would the keys be set. That is, only if all the keys can be set, will the action
|
||||
//! run and return code `0` - otherwise the action won't do anything and return an overwrite error.
|
||||
//! There is no point of using _strong actions_ for a single key/value pair, since it will only
|
||||
//! slow things down due to the checks performed.
|
||||
//! Do note that this isn't the same as the gurantees provided by ACID transactions
|
||||
|
||||
pub use self::{sdel::sdel, sset::sset, supdate::supdate};
|
||||
mod sdel;
|
||||
mod sset;
|
||||
mod supdate;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StrongActionResult {
|
||||
/// Internal server error
|
||||
ServerError,
|
||||
/// A single value was not found
|
||||
Nil,
|
||||
/// An overwrite error for a single value
|
||||
OverwriteError,
|
||||
/// An encoding error occurred
|
||||
EncodingError,
|
||||
/// Everything worked as expected
|
||||
Okay,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl StrongActionResult {
|
||||
pub const fn is_ok(&self) -> bool {
|
||||
matches!(self, StrongActionResult::Okay)
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Jul 30 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
actions::strong::StrongActionResult,
|
||||
dbnet::prelude::*,
|
||||
kvengine::{KVEStandard, SingleEncoder},
|
||||
protocol::iter::DerefUnsafeSlice,
|
||||
util::compiler,
|
||||
},
|
||||
core::slice::Iter,
|
||||
};
|
||||
|
||||
action! {
|
||||
/// Run an `SDEL` query
|
||||
///
|
||||
/// This either returns `Okay` if all the keys were `del`eted, or it returns a
|
||||
/// `Nil`, which is code `1`
|
||||
fn sdel(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
if registry::state_okay() {
|
||||
// guarantee one check: consistency
|
||||
let key_encoder = kve.get_key_encoder();
|
||||
let outcome = unsafe {
|
||||
// UNSAFE(@ohsayan): The lifetime of `act` ensures that the
|
||||
// pointers are still valid
|
||||
self::snapshot_and_del(kve, key_encoder, act.into_inner())
|
||||
};
|
||||
match outcome {
|
||||
StrongActionResult::Okay => con._write_raw(P::RCODE_OKAY).await?,
|
||||
StrongActionResult::Nil => {
|
||||
// good, it failed because some key didn't exist
|
||||
return util::err(P::RCODE_NIL);
|
||||
},
|
||||
StrongActionResult::ServerError => return util::err(P::RCODE_SERVER_ERR),
|
||||
StrongActionResult::EncodingError => {
|
||||
// error we love to hate: encoding error, ugh
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
},
|
||||
StrongActionResult::OverwriteError => unsafe {
|
||||
// SAFETY check: never the case
|
||||
impossible!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Snapshot the current status and then delete maintaining concurrency
|
||||
/// guarantees
|
||||
pub(super) fn snapshot_and_del<'a, T: 'a + DerefUnsafeSlice>(
|
||||
kve: &'a KVEStandard,
|
||||
key_encoder: SingleEncoder,
|
||||
act: Iter<'a, T>,
|
||||
) -> StrongActionResult {
|
||||
let mut snapshots = Vec::with_capacity(act.len());
|
||||
let mut err_enc = false;
|
||||
let iter_stat_ok;
|
||||
{
|
||||
iter_stat_ok = act.as_ref().iter().all(|key| {
|
||||
let key = unsafe {
|
||||
// UNSAFE(@ohsayan): The caller has passed a slice and they should
|
||||
// ensure that it is valid
|
||||
key.deref_slice()
|
||||
};
|
||||
if compiler::likely(key_encoder(key)) {
|
||||
if let Some(snap) = kve.take_snapshot_unchecked(key) {
|
||||
snapshots.push(snap);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
err_enc = true;
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
cfg_test!({
|
||||
// give the caller 10 seconds to do some crap
|
||||
do_sleep!(10 s);
|
||||
});
|
||||
if compiler::unlikely(err_enc) {
|
||||
return compiler::cold_err(StrongActionResult::EncodingError);
|
||||
}
|
||||
if registry::state_okay() {
|
||||
// guarantee upholded: consistency
|
||||
if iter_stat_ok {
|
||||
// nice, all keys exist; let's plonk 'em
|
||||
let kve = kve;
|
||||
let lowtable = kve.get_inner_ref();
|
||||
act.zip(snapshots).for_each(|(key, snapshot)| {
|
||||
let key = unsafe {
|
||||
// UNSAFE(@ohsayan): The caller has passed a slice and they should
|
||||
// ensure that it is valid
|
||||
key.deref_slice()
|
||||
};
|
||||
// the check is very important: some thread may have updated the
|
||||
// value after we snapshotted it. In that case, let this key
|
||||
// be whatever the "newer" value is. Since our snapshot is a "happens-before"
|
||||
// thing, this is absolutely fine
|
||||
let _ = lowtable.remove_if(key, |_, val| val.eq(&snapshot));
|
||||
});
|
||||
StrongActionResult::Okay
|
||||
} else {
|
||||
StrongActionResult::Nil
|
||||
}
|
||||
} else {
|
||||
StrongActionResult::ServerError
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Jul 30 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
actions::strong::StrongActionResult,
|
||||
corestore::SharedSlice,
|
||||
dbnet::prelude::*,
|
||||
kvengine::{DoubleEncoder, KVEStandard},
|
||||
protocol::iter::DerefUnsafeSlice,
|
||||
util::compiler,
|
||||
},
|
||||
core::slice::Iter,
|
||||
};
|
||||
|
||||
action! {
|
||||
/// Run an `SSET` query
|
||||
///
|
||||
/// This either returns `Okay` if all the keys were set, or it returns an
|
||||
/// `Overwrite Error` or code `2`
|
||||
fn sset(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
let howmany = act.len();
|
||||
ensure_length::<P>(howmany, |size| size & 1 == 0 && size != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
if registry::state_okay() {
|
||||
let encoder = kve.get_double_encoder();
|
||||
let outcome = unsafe {
|
||||
// UNSAFE(@ohsayan): The lifetime of `act` guarantees that the
|
||||
// pointers remain valid
|
||||
self::snapshot_and_insert(kve, encoder, act.into_inner())
|
||||
};
|
||||
match outcome {
|
||||
StrongActionResult::Okay => con._write_raw(P::RCODE_OKAY).await?,
|
||||
StrongActionResult::OverwriteError => return util::err(P::RCODE_OVERWRITE_ERR),
|
||||
StrongActionResult::ServerError => return util::err(P::RCODE_SERVER_ERR),
|
||||
StrongActionResult::EncodingError => {
|
||||
// error we love to hate: encoding error, ugh
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
},
|
||||
StrongActionResult::Nil => unsafe {
|
||||
// SAFETY check: never the case
|
||||
impossible!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a consistent snapshot of the database at this current point in time
|
||||
/// and then mutate the entries, respecting concurrency guarantees
|
||||
pub(super) fn snapshot_and_insert<'a, T: 'a + DerefUnsafeSlice>(
|
||||
kve: &'a KVEStandard,
|
||||
encoder: DoubleEncoder,
|
||||
mut act: Iter<'a, T>,
|
||||
) -> StrongActionResult {
|
||||
let mut enc_err = false;
|
||||
let lowtable = kve.get_inner_ref();
|
||||
let key_iter_stat_ok;
|
||||
{
|
||||
key_iter_stat_ok = act.as_ref().chunks_exact(2).all(|kv| unsafe {
|
||||
let key = ucidx!(kv, 0).deref_slice();
|
||||
let value = ucidx!(kv, 1).deref_slice();
|
||||
if compiler::likely(encoder(key, value)) {
|
||||
lowtable.get(key).is_none()
|
||||
} else {
|
||||
enc_err = true;
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
cfg_test!({
|
||||
// give the caller 10 seconds to do some crap
|
||||
do_sleep!(10 s);
|
||||
});
|
||||
if compiler::unlikely(enc_err) {
|
||||
return compiler::cold_err(StrongActionResult::EncodingError);
|
||||
}
|
||||
if registry::state_okay() {
|
||||
if key_iter_stat_ok {
|
||||
let _kve = kve;
|
||||
let lowtable = lowtable;
|
||||
// fine, the keys were non-existent when we looked at them
|
||||
while let (Some(key), Some(value)) = (act.next(), act.next()) {
|
||||
unsafe {
|
||||
if let Some(fresh) =
|
||||
lowtable.fresh_entry(SharedSlice::new(key.deref_slice()))
|
||||
{
|
||||
fresh.insert(SharedSlice::new(value.deref_slice()));
|
||||
}
|
||||
// we don't care if some other thread initialized the value we checked
|
||||
// it. We expected a fresh entry, so that's what we'll check and use
|
||||
}
|
||||
}
|
||||
StrongActionResult::Okay
|
||||
} else {
|
||||
StrongActionResult::OverwriteError
|
||||
}
|
||||
} else {
|
||||
StrongActionResult::ServerError
|
||||
}
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Jul 30 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
actions::strong::StrongActionResult,
|
||||
corestore::SharedSlice,
|
||||
dbnet::prelude::*,
|
||||
kvengine::{DoubleEncoder, KVEStandard},
|
||||
protocol::iter::DerefUnsafeSlice,
|
||||
util::compiler,
|
||||
},
|
||||
core::slice::Iter,
|
||||
};
|
||||
|
||||
action! {
|
||||
/// Run an `SUPDATE` query
|
||||
///
|
||||
/// This either returns `Okay` if all the keys were updated, or it returns `Nil`
|
||||
/// or code `1`
|
||||
fn supdate(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, act: ActionIter<'a>) {
|
||||
let howmany = act.len();
|
||||
ensure_length::<P>(howmany, |size| size & 1 == 0 && size != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
if registry::state_okay() {
|
||||
let encoder = kve.get_double_encoder();
|
||||
let outcome = unsafe {
|
||||
// UNSAFE(@ohsayan): the lifetime of `act` ensure ptr validity
|
||||
self::snapshot_and_update(kve, encoder, act.into_inner())
|
||||
};
|
||||
match outcome {
|
||||
StrongActionResult::Okay => con._write_raw(P::RCODE_OKAY).await?,
|
||||
StrongActionResult::Nil => {
|
||||
// good, it failed because some key didn't exist
|
||||
return util::err(P::RCODE_NIL);
|
||||
},
|
||||
StrongActionResult::ServerError => return util::err(P::RCODE_SERVER_ERR),
|
||||
StrongActionResult::EncodingError => {
|
||||
// error we love to hate: encoding error, ugh
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
},
|
||||
StrongActionResult::OverwriteError => unsafe {
|
||||
// SAFETY check: never the case
|
||||
impossible!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a consistent snapshot of the database at this point in time. Once snapshotting
|
||||
/// completes, mutate the entries in place while keeping up with isolation guarantees
|
||||
/// `(all_okay, enc_err)`
|
||||
pub(super) fn snapshot_and_update<'a, T: 'a + DerefUnsafeSlice>(
|
||||
kve: &'a KVEStandard,
|
||||
encoder: DoubleEncoder,
|
||||
mut act: Iter<'a, T>,
|
||||
) -> StrongActionResult {
|
||||
let mut enc_err = false;
|
||||
let mut snapshots = Vec::with_capacity(act.len());
|
||||
let iter_stat_ok;
|
||||
{
|
||||
// snapshot the values at this point in time
|
||||
iter_stat_ok = act.as_ref().chunks_exact(2).all(|kv| unsafe {
|
||||
let key = ucidx!(kv, 0).deref_slice();
|
||||
let value = ucidx!(kv, 1).deref_slice();
|
||||
if compiler::likely(encoder(key, value)) {
|
||||
if let Some(snapshot) = kve.take_snapshot_unchecked(key) {
|
||||
snapshots.push(snapshot);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
enc_err = true;
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
cfg_test!({
|
||||
// give the caller 10 seconds to do some crap
|
||||
do_sleep!(10 s);
|
||||
});
|
||||
if compiler::unlikely(enc_err) {
|
||||
return compiler::cold_err(StrongActionResult::EncodingError);
|
||||
}
|
||||
if registry::state_okay() {
|
||||
// uphold consistency
|
||||
if iter_stat_ok {
|
||||
let kve = kve;
|
||||
// good, so all the values existed when we snapshotted them; let's update 'em
|
||||
let mut snap_cc = snapshots.into_iter();
|
||||
let lowtable = kve.get_inner_ref();
|
||||
while let (Some(key), Some(value), Some(snapshot)) =
|
||||
(act.next(), act.next(), snap_cc.next())
|
||||
{
|
||||
unsafe {
|
||||
// When we snapshotted, we looked at `snapshot`. If the value is still the
|
||||
// same, then we'll update it. Otherwise, let it be
|
||||
if let Some(mut mutable) =
|
||||
lowtable.mut_entry(SharedSlice::new(key.deref_slice()))
|
||||
{
|
||||
if mutable.value().eq(&snapshot) {
|
||||
mutable.insert(SharedSlice::new(value.deref_slice()));
|
||||
} else {
|
||||
drop(mutable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StrongActionResult::Okay
|
||||
} else {
|
||||
StrongActionResult::Nil
|
||||
}
|
||||
} else {
|
||||
StrongActionResult::ServerError
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Jul 30 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
mod sdel_concurrency_tests {
|
||||
use {
|
||||
super::super::sdel,
|
||||
crate::{corestore::SharedSlice, kvengine::KVEStandard},
|
||||
std::{sync::Arc, thread},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_snapshot_okay() {
|
||||
let kve = KVEStandard::init(true, true);
|
||||
kve.upsert(SharedSlice::from("k1"), SharedSlice::from("v1"))
|
||||
.unwrap();
|
||||
kve.upsert(SharedSlice::from("k2"), SharedSlice::from("v2"))
|
||||
.unwrap();
|
||||
let encoder = kve.get_key_encoder();
|
||||
let it = bi!("k1", "k2");
|
||||
let ret = sdel::snapshot_and_del(&kve, encoder, it.as_ref().iter().as_ref().iter());
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_sdel_snapshot_fail_with_t2() {
|
||||
let kve = Arc::new(KVEStandard::init(true, true));
|
||||
let kve1 = kve.clone();
|
||||
let encoder = kve.get_key_encoder();
|
||||
{
|
||||
kve.upsert(SharedSlice::from("k1"), SharedSlice::from("v1"))
|
||||
.unwrap();
|
||||
kve.upsert(SharedSlice::from("k2"), SharedSlice::from("v2"))
|
||||
.unwrap();
|
||||
}
|
||||
let it = bi!("k1", "k2");
|
||||
// sdel will wait 10s for us
|
||||
let t1handle = thread::spawn(move || {
|
||||
sdel::snapshot_and_del(&kve1, encoder, it.as_ref().iter().as_ref().iter())
|
||||
});
|
||||
// we have 10s: we sleep 5 to let the snapshot complete (thread spawning takes time)
|
||||
do_sleep!(5 s);
|
||||
assert!(kve
|
||||
.update(SharedSlice::from("k1"), SharedSlice::from("updated-v1"))
|
||||
.unwrap());
|
||||
// let us join t1
|
||||
let ret = t1handle.join().unwrap();
|
||||
assert!(ret.is_ok());
|
||||
// although we told sdel to delete it, it.as_ref().iter() shouldn't because we externally
|
||||
// updated the value
|
||||
assert!(kve.exists(&SharedSlice::from("k1")).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
mod sset_concurrency_tests {
|
||||
use {
|
||||
super::super::sset,
|
||||
crate::{corestore::SharedSlice, kvengine::KVEStandard},
|
||||
std::{sync::Arc, thread},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_snapshot_okay() {
|
||||
let kve = KVEStandard::init(true, true);
|
||||
let encoder = kve.get_double_encoder();
|
||||
let it = bi!("k1", "v1", "k2", "v2");
|
||||
let ret = sset::snapshot_and_insert(&kve, encoder, it.as_ref().iter());
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_sset_snapshot_fail_with_t2() {
|
||||
let kve = Arc::new(KVEStandard::init(true, true));
|
||||
let kve1 = kve.clone();
|
||||
let encoder = kve.get_double_encoder();
|
||||
let it = bi!("k1", "v1", "k2", "v2");
|
||||
// sset will wait 10s for us
|
||||
let t1handle =
|
||||
thread::spawn(move || sset::snapshot_and_insert(&kve1, encoder, it.as_ref().iter()));
|
||||
// we have 10s: we sleep 5 to let the snapshot complete (thread spawning takes time)
|
||||
do_sleep!(5 s);
|
||||
// update the value externally
|
||||
assert!(kve
|
||||
.set(SharedSlice::from("k1"), SharedSlice::from("updated-v1"))
|
||||
.unwrap());
|
||||
// let us join t1
|
||||
let ret = t1handle.join().unwrap();
|
||||
// but set won't fail because someone set it before it did; this is totally
|
||||
// acceptable because we only wanted to set it if it matches the status when
|
||||
// we created a snapshot
|
||||
assert!(ret.is_ok());
|
||||
// although we told sset to set a key, but it shouldn't because we updated it
|
||||
assert_eq!(
|
||||
kve.get(&SharedSlice::from("k1")).unwrap().unwrap().clone(),
|
||||
SharedSlice::from("updated-v1")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod supdate_concurrency_tests {
|
||||
use {
|
||||
super::super::supdate,
|
||||
crate::{corestore::SharedSlice, kvengine::KVEStandard},
|
||||
std::{sync::Arc, thread},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_snapshot_okay() {
|
||||
let kve = KVEStandard::init(true, true);
|
||||
kve.upsert(SharedSlice::from("k1"), SharedSlice::from("v1"))
|
||||
.unwrap();
|
||||
kve.upsert(SharedSlice::from("k2"), SharedSlice::from("v2"))
|
||||
.unwrap();
|
||||
let encoder = kve.get_double_encoder();
|
||||
let it = bi!("k1", "v1", "k2", "v2");
|
||||
let ret = supdate::snapshot_and_update(&kve, encoder, it.as_ref().iter());
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_supdate_snapshot_fail_with_t2() {
|
||||
let kve = Arc::new(KVEStandard::init(true, true));
|
||||
kve.upsert(SharedSlice::from("k1"), SharedSlice::from("v1"))
|
||||
.unwrap();
|
||||
kve.upsert(SharedSlice::from("k2"), SharedSlice::from("v2"))
|
||||
.unwrap();
|
||||
let kve1 = kve.clone();
|
||||
let encoder = kve.get_double_encoder();
|
||||
let it = bi!("k1", "v1", "k2", "v2");
|
||||
// supdate will wait 10s for us
|
||||
let t1handle =
|
||||
thread::spawn(move || supdate::snapshot_and_update(&kve1, encoder, it.as_ref().iter()));
|
||||
// we have 10s: we sleep 5 to let the snapshot complete (thread spawning takes time)
|
||||
do_sleep!(5 s);
|
||||
// lets update the value externally
|
||||
assert!(kve
|
||||
.update(SharedSlice::from("k1"), SharedSlice::from("updated-v1"))
|
||||
.unwrap());
|
||||
// let us join t1
|
||||
let ret = t1handle.join().unwrap();
|
||||
assert!(ret.is_ok());
|
||||
// although we told supdate to update the key, it.as_ref().iter() shouldn't because we updated it
|
||||
// externally; hence our `updated-v1` value should persist
|
||||
assert_eq!(
|
||||
kve.get(&SharedSlice::from("k1")).unwrap().unwrap().clone(),
|
||||
SharedSlice::from("updated-v1")
|
||||
);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Created on Mon Aug 17 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//! # `UPDATE` queries
|
||||
//! This module provides functions to work with `UPDATE` queries
|
||||
//!
|
||||
|
||||
use crate::{corestore::SharedSlice, dbnet::prelude::*};
|
||||
|
||||
action!(
|
||||
/// Run an `UPDATE` query
|
||||
fn update(handle: &Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len == 2)?;
|
||||
if registry::state_okay() {
|
||||
let did_we = {
|
||||
let writer = handle.get_table_with::<P, KVEBlob>()?;
|
||||
match unsafe {
|
||||
// UNSAFE(@ohsayan): This is completely safe as we've already checked
|
||||
// that there are exactly 2 arguments
|
||||
writer.update(
|
||||
SharedSlice::new(act.next_unchecked()),
|
||||
SharedSlice::new(act.next_unchecked()),
|
||||
)
|
||||
} {
|
||||
Ok(true) => Some(true),
|
||||
Ok(false) => Some(false),
|
||||
Err(()) => None,
|
||||
}
|
||||
};
|
||||
con._write_raw(P::UPDATE_NLUT[did_we]).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Created on Fri Sep 25 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
corestore::SharedSlice, dbnet::prelude::*,
|
||||
kvengine::encoding::ENCODING_LUT_ITER_PAIR, queryengine::ActionIter, util::compiler,
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Run an `USET` query
|
||||
///
|
||||
/// This is like "INSERT or UPDATE"
|
||||
fn uset(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
let howmany = act.len();
|
||||
ensure_length::<P>(howmany, |size| size & 1 == 0 && size != 0)?;
|
||||
let kve = handle.get_table_with::<P, KVEBlob>()?;
|
||||
let encoding_is_okay = ENCODING_LUT_ITER_PAIR[kve.get_encoding_tuple()](&act);
|
||||
if compiler::likely(encoding_is_okay) {
|
||||
if registry::state_okay() {
|
||||
while let (Some(key), Some(val)) = (act.next(), act.next()) {
|
||||
kve.upsert_unchecked(SharedSlice::new(key), SharedSlice::new(val));
|
||||
}
|
||||
con.write_usize(howmany / 2).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Oct 13 2020
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2020, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
dbnet::prelude::*, kvengine::encoding,
|
||||
storage::v1::sengine::SnapshotActionResult,
|
||||
},
|
||||
core::str,
|
||||
std::path::{Component, PathBuf},
|
||||
};
|
||||
|
||||
action!(
|
||||
/// Create a snapshot
|
||||
///
|
||||
fn mksnap(handle: &crate::corestore::Corestore, con: &mut Connection<C, P>, mut act: ActionIter<'a>) {
|
||||
let engine = handle.get_engine();
|
||||
if act.is_empty() {
|
||||
// traditional mksnap
|
||||
match engine.mksnap(handle.clone_store()).await {
|
||||
SnapshotActionResult::Ok => con._write_raw(P::RCODE_OKAY).await?,
|
||||
SnapshotActionResult::Failure => return util::err(P::RCODE_SERVER_ERR),
|
||||
SnapshotActionResult::Disabled => return util::err(P::RSTRING_SNAPSHOT_DISABLED),
|
||||
SnapshotActionResult::Busy => return util::err(P::RSTRING_SNAPSHOT_BUSY),
|
||||
_ => unsafe { impossible!() },
|
||||
}
|
||||
} else if act.len() == 1 {
|
||||
// remote snapshot, let's see what we've got
|
||||
let name = unsafe {
|
||||
// SAFETY: We have already checked that there is one item
|
||||
act.next_unchecked_bytes()
|
||||
};
|
||||
if !encoding::is_utf8(&name) {
|
||||
return util::err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
|
||||
// SECURITY: Check for directory traversal syntax
|
||||
let st = unsafe {
|
||||
// SAFETY: We have already checked for UTF-8 validity
|
||||
str::from_utf8_unchecked(&name)
|
||||
};
|
||||
let path = PathBuf::from(st);
|
||||
let illegal_snapshot = path
|
||||
.components()
|
||||
.filter(|dir| {
|
||||
// Sanitize snapshot name, to avoid directory traversal attacks
|
||||
// If the snapshot name has any root directory or parent directory, then
|
||||
// we'll allow it to pass through this adaptor.
|
||||
// As a result, this iterator will give us a count of the 'bad' components
|
||||
dir == &Component::RootDir || dir == &Component::ParentDir
|
||||
})
|
||||
.count()
|
||||
!= 0;
|
||||
if illegal_snapshot {
|
||||
return util::err(P::RSTRING_SNAPSHOT_ILLEGAL_NAME);
|
||||
}
|
||||
|
||||
// now make the snapshot
|
||||
match engine.mkrsnap(&name, handle.clone_store()).await {
|
||||
SnapshotActionResult::Ok => con._write_raw(P::RCODE_OKAY).await?,
|
||||
SnapshotActionResult::Failure => return util::err(P::RCODE_SERVER_ERR),
|
||||
SnapshotActionResult::Busy => return util::err(P::RSTRING_SNAPSHOT_BUSY),
|
||||
SnapshotActionResult::AlreadyExists => {
|
||||
return util::err(P::RSTRING_SNAPSHOT_DUPLICATE)
|
||||
}
|
||||
_ => unsafe { impossible!() },
|
||||
}
|
||||
} else {
|
||||
return util::err(P::RCODE_ACTION_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
);
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Mar 29 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
corestore::booltable::BoolTable, dbnet::prelude::*,
|
||||
storage::v1::interface::DIR_ROOT,
|
||||
},
|
||||
libsky::VERSION,
|
||||
};
|
||||
|
||||
const INFO: &[u8] = b"info";
|
||||
const METRIC: &[u8] = b"metric";
|
||||
const INFO_PROTOCOL: &[u8] = b"protocol";
|
||||
const INFO_PROTOVER: &[u8] = b"protover";
|
||||
const INFO_VERSION: &[u8] = b"version";
|
||||
const METRIC_HEALTH: &[u8] = b"health";
|
||||
const METRIC_STORAGE_USAGE: &[u8] = b"storage";
|
||||
const ERR_UNKNOWN_PROPERTY: &[u8] = b"!16\nunknown-property\n";
|
||||
const ERR_UNKNOWN_METRIC: &[u8] = b"!14\nunknown-metric\n";
|
||||
|
||||
const HEALTH_TABLE: BoolTable<&str> = BoolTable::new("good", "critical");
|
||||
|
||||
action! {
|
||||
fn sys(_handle: &Corestore, con: &mut Connection<C, P>, iter: ActionIter<'_>) {
|
||||
let mut iter = iter;
|
||||
ensure_boolean_or_aerr::<P>(iter.len() == 2)?;
|
||||
match unsafe { iter.next_lowercase_unchecked() }.as_ref() {
|
||||
INFO => sys_info(con, &mut iter).await,
|
||||
METRIC => sys_metric(con, &mut iter).await,
|
||||
_ => util::err(P::RCODE_UNKNOWN_ACTION),
|
||||
}
|
||||
}
|
||||
fn sys_info(con: &mut Connection<C, P>, iter: &mut ActionIter<'_>) {
|
||||
match unsafe { iter.next_lowercase_unchecked() }.as_ref() {
|
||||
INFO_PROTOCOL => con.write_string(P::PROTOCOL_VERSIONSTRING).await?,
|
||||
INFO_PROTOVER => con.write_float(P::PROTOCOL_VERSION).await?,
|
||||
INFO_VERSION => con.write_string(VERSION).await?,
|
||||
_ => return util::err(ERR_UNKNOWN_PROPERTY),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn sys_metric(con: &mut Connection<C, P>, iter: &mut ActionIter<'_>) {
|
||||
match unsafe { iter.next_lowercase_unchecked() }.as_ref() {
|
||||
METRIC_HEALTH => {
|
||||
con.write_string(HEALTH_TABLE[registry::state_okay()]).await?
|
||||
}
|
||||
METRIC_STORAGE_USAGE => {
|
||||
match util::os::dirsize(DIR_ROOT) {
|
||||
Ok(size) => con.write_int64(size).await?,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get storage usage with: {e}");
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => return util::err(ERR_UNKNOWN_METRIC),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Created on Sat Jun 26 2021
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
crate::{
|
||||
auth::AuthProvider,
|
||||
config::{ConfigurationSet, SnapshotConfig, SnapshotPref},
|
||||
corestore::Corestore,
|
||||
dbnet,
|
||||
diskstore::flock::FileLock,
|
||||
services,
|
||||
storage::v1::sengine::SnapshotEngine,
|
||||
util::{
|
||||
error::{Error, SkyResult},
|
||||
os::TerminationSignal,
|
||||
},
|
||||
},
|
||||
std::{sync::Arc, thread::sleep},
|
||||
tokio::{
|
||||
sync::{
|
||||
broadcast,
|
||||
mpsc::{self, Sender},
|
||||
},
|
||||
task::{self, JoinHandle},
|
||||
time::Duration,
|
||||
},
|
||||
};
|
||||
|
||||
const TERMSIG_THRESHOLD: usize = 3;
|
||||
|
||||
/// Start the server waiting for incoming connections or a termsig
|
||||
pub async fn run(
|
||||
ConfigurationSet {
|
||||
ports,
|
||||
bgsave,
|
||||
snapshot,
|
||||
maxcon,
|
||||
auth,
|
||||
protocol,
|
||||
..
|
||||
}: ConfigurationSet,
|
||||
restore_filepath: Option<String>,
|
||||
) -> SkyResult<Corestore> {
|
||||
// Intialize the broadcast channel
|
||||
let (signal, _) = broadcast::channel(1);
|
||||
let engine = match &snapshot {
|
||||
SnapshotConfig::Enabled(SnapshotPref { atmost, .. }) => SnapshotEngine::new(*atmost),
|
||||
SnapshotConfig::Disabled => SnapshotEngine::new_disabled(),
|
||||
};
|
||||
let engine = Arc::new(engine);
|
||||
// restore data
|
||||
services::restore_data(restore_filepath)
|
||||
.map_err(|e| Error::ioerror_extra(e, "restoring data from backup"))?;
|
||||
// init the store
|
||||
let db = Corestore::init_with_snapcfg(engine.clone())?;
|
||||
// refresh the snapshotengine state
|
||||
engine.parse_dir()?;
|
||||
let auth_provider = match auth.origin_key {
|
||||
Some(key) => {
|
||||
let authref = db.get_store().setup_auth();
|
||||
AuthProvider::new(authref, Some(key.into_inner()))
|
||||
}
|
||||
None => AuthProvider::new_disabled(),
|
||||
};
|
||||
|
||||
// initialize the background services
|
||||
let bgsave_handle = tokio::spawn(services::bgsave::bgsave_scheduler(
|
||||
db.clone(),
|
||||
bgsave,
|
||||
signal.subscribe(),
|
||||
));
|
||||
let snapshot_handle = tokio::spawn(services::snapshot::snapshot_service(
|
||||
engine,
|
||||
db.clone(),
|
||||
snapshot,
|
||||
signal.subscribe(),
|
||||
));
|
||||
|
||||
// bind to signals
|
||||
let termsig =
|
||||
TerminationSignal::init().map_err(|e| Error::ioerror_extra(e, "binding to signals"))?;
|
||||
// start the server (single or multiple listeners)
|
||||
let mut server = dbnet::connect(
|
||||
ports,
|
||||
protocol,
|
||||
maxcon,
|
||||
db.clone(),
|
||||
auth_provider,
|
||||
signal.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::select! {
|
||||
_ = server.run_server() => {},
|
||||
_ = termsig => {}
|
||||
}
|
||||
|
||||
log::info!("Signalling all workers to shut down");
|
||||
// drop the signal and let others exit
|
||||
drop(signal);
|
||||
server.finish_with_termsig().await;
|
||||
|
||||
// wait for the background services to terminate
|
||||
let _ = snapshot_handle.await;
|
||||
let _ = bgsave_handle.await;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
fn spawn_task(tx: Sender<bool>, db: Corestore, do_sleep: bool) -> JoinHandle<()> {
|
||||
task::spawn_blocking(move || {
|
||||
if do_sleep {
|
||||
log::info!("Waiting for 10 seconds before retrying ...");
|
||||
sleep(Duration::from_secs(10));
|
||||
}
|
||||
let ret = match crate::services::bgsave::run_bgsave(&db) {
|
||||
Ok(()) => {
|
||||
log::info!("Save before termination successful");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to run save on termination: {e}");
|
||||
false
|
||||
}
|
||||
};
|
||||
tx.blocking_send(ret).expect("Receiver dropped");
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finalize_shutdown(corestore: Corestore, pid_file: FileLock) {
|
||||
assert_eq!(
|
||||
corestore.strong_count(),
|
||||
1,
|
||||
"Correctness error. finalize_shutdown called before dropping server runtime"
|
||||
);
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.thread_name("server-final")
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let dbc = corestore.clone();
|
||||
let mut okay: bool = rt.block_on(async move {
|
||||
let db = dbc;
|
||||
let (tx, mut rx) = mpsc::channel::<bool>(1);
|
||||
spawn_task(tx.clone(), db.clone(), false);
|
||||
let spawn_again = || {
|
||||
// we failed, so we better sleep
|
||||
// now spawn it again to see the state
|
||||
spawn_task(tx.clone(), db.clone(), true)
|
||||
};
|
||||
let mut threshold = TERMSIG_THRESHOLD;
|
||||
loop {
|
||||
let termsig = match TerminationSignal::init().map_err(|e| e.to_string()) {
|
||||
Ok(sig) => sig,
|
||||
Err(e) => {
|
||||
log::error!("Failed to bind to signal with error: {e}");
|
||||
crate::exit_error();
|
||||
}
|
||||
};
|
||||
tokio::select! {
|
||||
ret = rx.recv() => {
|
||||
if ret.unwrap() {
|
||||
// that's good to go
|
||||
break true;
|
||||
} else {
|
||||
spawn_again();
|
||||
}
|
||||
}
|
||||
_ = termsig => {
|
||||
threshold -= 1;
|
||||
if threshold == 0 {
|
||||
log::error!("Termination signal received but failed to flush data. Quitting because threshold exceeded");
|
||||
break false;
|
||||
} else {
|
||||
log::error!("Termination signal received but failed to flush data. Threshold is at {threshold}");
|
||||
continue;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
okay &= services::pre_shutdown_cleanup(pid_file, Some(corestore.get_store()));
|
||||
if okay {
|
||||
log::info!("Goodbye :)");
|
||||
} else {
|
||||
log::error!("Didn't terminate successfully");
|
||||
crate::exit_error();
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Created on Mon Feb 21 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
super::provider::{Authkey, AUTHKEY_SIZE},
|
||||
crate::corestore::array::Array,
|
||||
};
|
||||
|
||||
type AuthkeyArray = Array<u8, { AUTHKEY_SIZE }>;
|
||||
const RAN_BYTES_SIZE: usize = 40;
|
||||
|
||||
/// Return a "human readable key" and the "authbytes" that can be stored
|
||||
/// safely. To do this:
|
||||
/// - Generate 64 random bytes
|
||||
/// - Encode that into base64. This is the client key
|
||||
/// - Hash the key using rcrypt. This is the server key that
|
||||
/// will be stored
|
||||
pub fn generate_full() -> (String, Authkey) {
|
||||
let mut bytes: [u8; RAN_BYTES_SIZE] = [0u8; RAN_BYTES_SIZE];
|
||||
openssl::rand::rand_bytes(&mut bytes).unwrap();
|
||||
let ret = base64::encode_config(bytes, base64::BCRYPT);
|
||||
let hash = rcrypt::hash(&ret, rcrypt::DEFAULT_COST).unwrap();
|
||||
let store_in_db = unsafe {
|
||||
let mut array = AuthkeyArray::new();
|
||||
// we guarantee that the size is equal to 40
|
||||
array.extend_from_slice_unchecked(&hash);
|
||||
array.into_array_unchecked()
|
||||
};
|
||||
(ret, store_in_db)
|
||||
}
|
||||
|
||||
/// Verify a "human readable key" against the provided "authbytes"
|
||||
pub fn verify_key(input: &[u8], hash: &[u8]) -> Option<bool> {
|
||||
rcrypt::verify(input, hash).ok()
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Created on Mon Feb 21 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* For our authn/authz, we have two important keys:
|
||||
* - The origin key: This is the key saved in the configuration file that can also be
|
||||
* used as the "recovery key" in the event the "root key" is lost. To claim the root
|
||||
* account, one needs this key. This is a fixed width key with 40 characters
|
||||
* - The root key: This is the superuser key that can be used to create/deny other
|
||||
* accounts. On claiming the root account, this key is issued
|
||||
*
|
||||
* When the root account is claimed, it can be used to create "standard users". standard
|
||||
* users have access to everything but the ability to create/revoke other users
|
||||
*/
|
||||
|
||||
mod keys;
|
||||
pub mod provider;
|
||||
pub use provider::{AuthProvider, Authmap};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::dbnet::prelude::*;
|
||||
|
||||
const AUTH_CLAIM: &[u8] = b"claim";
|
||||
const AUTH_LOGIN: &[u8] = b"login";
|
||||
const AUTH_LOGOUT: &[u8] = b"logout";
|
||||
const AUTH_ADDUSER: &[u8] = b"adduser";
|
||||
const AUTH_DELUSER: &[u8] = b"deluser";
|
||||
const AUTH_RESTORE: &[u8] = b"restore";
|
||||
const AUTH_LISTUSER: &[u8] = b"listuser";
|
||||
const AUTH_WHOAMI: &[u8] = b"whoami";
|
||||
|
||||
action! {
|
||||
/// Handle auth. Should have passed the `auth` token
|
||||
fn auth(
|
||||
con: &mut Connection<C, P>,
|
||||
auth: &mut AuthProviderHandle,
|
||||
iter: ActionIter<'_>
|
||||
) {
|
||||
let mut iter = iter;
|
||||
match iter.next_lowercase().unwrap_or_aerr::<P>()?.as_ref() {
|
||||
AUTH_LOGIN => self::_auth_login(con, auth, &mut iter).await,
|
||||
AUTH_CLAIM => self::_auth_claim(con, auth, &mut iter).await,
|
||||
AUTH_ADDUSER => {
|
||||
ensure_boolean_or_aerr::<P>(iter.len() == 1)?; // just the username
|
||||
let username = unsafe { iter.next_unchecked() };
|
||||
let key = auth.provider_mut().claim_user::<P>(username)?;
|
||||
con.write_string(&key).await?;
|
||||
Ok(())
|
||||
}
|
||||
AUTH_LOGOUT => {
|
||||
ensure_boolean_or_aerr::<P>(iter.is_empty())?; // nothing else
|
||||
auth.provider_mut().logout::<P>()?;
|
||||
auth.set_unauth();
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
Ok(())
|
||||
}
|
||||
AUTH_DELUSER => {
|
||||
ensure_boolean_or_aerr::<P>(iter.len() == 1)?; // just the username
|
||||
auth.provider_mut().delete_user::<P>(unsafe { iter.next_unchecked() })?;
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
Ok(())
|
||||
}
|
||||
AUTH_RESTORE => self::auth_restore(con, auth, &mut iter).await,
|
||||
AUTH_LISTUSER => self::auth_listuser(con, auth, &mut iter).await,
|
||||
AUTH_WHOAMI => self::auth_whoami(con, auth, &mut iter).await,
|
||||
_ => util::err(P::RCODE_UNKNOWN_ACTION),
|
||||
}
|
||||
}
|
||||
fn auth_whoami(con: &mut Connection<C, P>, auth: &mut AuthProviderHandle, iter: &mut ActionIter<'_>) {
|
||||
ensure_boolean_or_aerr::<P>(ActionIter::is_empty(iter))?;
|
||||
con.write_string(&auth.provider().whoami::<P>()?).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn auth_listuser(con: &mut Connection<C, P>, auth: &mut AuthProviderHandle, iter: &mut ActionIter<'_>) {
|
||||
ensure_boolean_or_aerr::<P>(ActionIter::is_empty(iter))?;
|
||||
let usernames = auth.provider().collect_usernames::<P>()?;
|
||||
con.write_typed_non_null_array_header(usernames.len(), b'+').await?;
|
||||
for username in usernames {
|
||||
con.write_typed_non_null_array_element(username.as_bytes()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn auth_restore(con: &mut Connection<C, P>, auth: &mut AuthProviderHandle, iter: &mut ActionIter<'_>) {
|
||||
let newkey = match iter.len() {
|
||||
1 => {
|
||||
// so this fella thinks they're root
|
||||
auth.provider().regenerate::<P>(
|
||||
unsafe { iter.next_unchecked() }
|
||||
)?
|
||||
}
|
||||
2 => {
|
||||
// so this fella is giving us the origin key
|
||||
let origin = unsafe { iter.next_unchecked() };
|
||||
let id = unsafe { iter.next_unchecked() };
|
||||
auth.provider().regenerate_using_origin::<P>(origin, id)?
|
||||
}
|
||||
_ => return util::err(P::RCODE_ACTION_ERR),
|
||||
};
|
||||
con.write_string(&newkey).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn _auth_claim(con: &mut Connection<C, P>, auth: &mut AuthProviderHandle, iter: &mut ActionIter<'_>) {
|
||||
ensure_boolean_or_aerr::<P>(iter.len() == 1)?; // just the origin key
|
||||
let origin_key = unsafe { iter.next_unchecked() };
|
||||
let key = auth.provider_mut().claim_root::<P>(origin_key)?;
|
||||
auth.set_auth();
|
||||
con.write_string(&key).await?;
|
||||
Ok(())
|
||||
}
|
||||
/// Handle a login operation only. The **`login` token is expected to be present**
|
||||
fn auth_login_only(
|
||||
con: &mut Connection<C, P>,
|
||||
auth: &mut AuthProviderHandle,
|
||||
iter: ActionIter<'_>
|
||||
) {
|
||||
let mut iter = iter;
|
||||
match iter.next_lowercase().unwrap_or_aerr::<P>()?.as_ref() {
|
||||
AUTH_LOGIN => self::_auth_login(con, auth, &mut iter).await,
|
||||
AUTH_CLAIM => self::_auth_claim(con, auth, &mut iter).await,
|
||||
AUTH_RESTORE => self::auth_restore(con, auth, &mut iter).await,
|
||||
AUTH_WHOAMI => self::auth_whoami(con, auth, &mut iter).await,
|
||||
_ => util::err(P::AUTH_CODE_PERMS),
|
||||
}
|
||||
}
|
||||
fn _auth_login(con: &mut Connection<C, P>, auth: &mut AuthProviderHandle, iter: &mut ActionIter<'_>) {
|
||||
// sweet, where's our username and password
|
||||
ensure_boolean_or_aerr::<P>(iter.len() == 2)?; // just the uname and pass
|
||||
let (username, password) = unsafe { (iter.next_unchecked(), iter.next_unchecked()) };
|
||||
auth.provider_mut().login::<P>(username, password)?;
|
||||
auth.set_auth();
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,281 +0,0 @@
|
||||
/*
|
||||
* Created on Sun Mar 06 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use {
|
||||
super::keys,
|
||||
crate::{
|
||||
actions::{ActionError, ActionResult},
|
||||
corestore::{array::Array, htable::Coremap},
|
||||
protocol::interface::ProtocolSpec,
|
||||
util::err,
|
||||
},
|
||||
std::sync::Arc,
|
||||
};
|
||||
|
||||
// constants
|
||||
/// Size of an authn key in bytes
|
||||
pub const AUTHKEY_SIZE: usize = 40;
|
||||
/// Size of an authn ID in bytes
|
||||
pub const AUTHID_SIZE: usize = 40;
|
||||
|
||||
pub mod testsuite_data {
|
||||
#![allow(unused)]
|
||||
//! Temporary users created by the testsuite in debug mode
|
||||
pub const TESTSUITE_ROOT_USER: &str = "root";
|
||||
pub const TESTSUITE_TEST_USER: &str = "testuser";
|
||||
pub const TESTSUITE_ROOT_TOKEN: &str = "XUOdVKhEONnnGwNwT7WeLqbspDgVtKex0/nwFwBSW7XJxioHwpg6H.";
|
||||
pub const TESTSUITE_TEST_TOKEN: &str = "mpobAB7EY8vnBs70d/..h1VvfinKIeEJgt1rg4wUkwF6aWCvGGR9le";
|
||||
}
|
||||
|
||||
uninit_array! {
|
||||
const USER_ROOT_ARRAY: [u8; 40] = [b'r', b'o', b'o', b't'];
|
||||
}
|
||||
/// The root user
|
||||
const USER_ROOT: AuthID = unsafe { AuthID::from_const(USER_ROOT_ARRAY, 4) };
|
||||
|
||||
/// An authn ID
|
||||
type AuthID = Array<u8, AUTHID_SIZE>;
|
||||
/// An authn key
|
||||
pub type Authkey = [u8; AUTHKEY_SIZE];
|
||||
/// Authmap
|
||||
pub type Authmap = Arc<Coremap<AuthID, Authkey>>;
|
||||
|
||||
/// The authn/authz provider
|
||||
///
|
||||
pub struct AuthProvider {
|
||||
origin: Option<Authkey>,
|
||||
/// the current user
|
||||
whoami: Option<AuthID>,
|
||||
/// a map of users
|
||||
authmap: Authmap,
|
||||
}
|
||||
|
||||
impl AuthProvider {
|
||||
fn _new(authmap: Authmap, whoami: Option<AuthID>, origin: Option<Authkey>) -> Self {
|
||||
Self {
|
||||
authmap,
|
||||
whoami,
|
||||
origin,
|
||||
}
|
||||
}
|
||||
/// New provider with no origin-key
|
||||
pub fn new_disabled() -> Self {
|
||||
Self::_new(Default::default(), None, None)
|
||||
}
|
||||
/// New provider with zero users
|
||||
#[cfg(test)]
|
||||
pub fn new_blank(origin: Option<Authkey>) -> Self {
|
||||
Self::_new(Default::default(), None, origin)
|
||||
}
|
||||
/// New provider with users from the provided map
|
||||
///
|
||||
/// ## Test suite
|
||||
/// The testsuite creates users `root` and `testuser`; this **does not** apply to
|
||||
/// release mode
|
||||
pub fn new(authmap: Arc<Coremap<AuthID, Authkey>>, origin: Option<Authkey>) -> Self {
|
||||
let slf = Self::_new(authmap, None, origin);
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// 'root' user in test mode
|
||||
slf.authmap.true_if_insert(
|
||||
AuthID::try_from_slice(testsuite_data::TESTSUITE_ROOT_USER).unwrap(),
|
||||
[
|
||||
172, 143, 117, 169, 158, 156, 33, 106, 139, 107, 20, 106, 91, 219, 34, 157, 98,
|
||||
147, 142, 91, 222, 238, 205, 120, 72, 171, 90, 218, 147, 2, 75, 67, 44, 108,
|
||||
185, 124, 55, 40, 156, 252,
|
||||
],
|
||||
);
|
||||
// 'testuser' user in test mode
|
||||
slf.authmap.true_if_insert(
|
||||
AuthID::try_from_slice(testsuite_data::TESTSUITE_TEST_USER).unwrap(),
|
||||
[
|
||||
172, 183, 60, 221, 53, 240, 231, 217, 113, 112, 98, 16, 109, 62, 235, 95, 184,
|
||||
107, 130, 139, 43, 197, 40, 31, 176, 127, 185, 22, 172, 124, 39, 225, 124, 71,
|
||||
193, 115, 176, 162, 239, 93,
|
||||
],
|
||||
);
|
||||
}
|
||||
slf
|
||||
}
|
||||
pub const fn is_enabled(&self) -> bool {
|
||||
matches!(self.origin, Some(_))
|
||||
}
|
||||
pub fn claim_root<P: ProtocolSpec>(&mut self, origin_key: &[u8]) -> ActionResult<String> {
|
||||
self.verify_origin::<P>(origin_key)?;
|
||||
// the origin key was good, let's try claiming root
|
||||
let (key, store) = keys::generate_full();
|
||||
if self.authmap.true_if_insert(USER_ROOT, store) {
|
||||
// claimed, sweet, log them in
|
||||
self.whoami = Some(USER_ROOT);
|
||||
Ok(key)
|
||||
} else {
|
||||
err(P::AUTH_ERROR_ALREADYCLAIMED)
|
||||
}
|
||||
}
|
||||
fn are_you_root<P: ProtocolSpec>(&self) -> ActionResult<bool> {
|
||||
self.ensure_enabled::<P>()?;
|
||||
match self.whoami.as_ref().map(|v| v.eq(&USER_ROOT)) {
|
||||
Some(v) => Ok(v),
|
||||
None => err(P::AUTH_CODE_PERMS),
|
||||
}
|
||||
}
|
||||
pub fn claim_user<P: ProtocolSpec>(&self, claimant: &[u8]) -> ActionResult<String> {
|
||||
self.ensure_root::<P>()?;
|
||||
self._claim_user::<P>(claimant)
|
||||
}
|
||||
pub fn _claim_user<P: ProtocolSpec>(&self, claimant: &[u8]) -> ActionResult<String> {
|
||||
let (key, store) = keys::generate_full();
|
||||
if self
|
||||
.authmap
|
||||
.true_if_insert(Self::try_auth_id::<P>(claimant)?, store)
|
||||
{
|
||||
Ok(key)
|
||||
} else {
|
||||
err(P::AUTH_ERROR_ALREADYCLAIMED)
|
||||
}
|
||||
}
|
||||
pub fn login<P: ProtocolSpec>(&mut self, account: &[u8], token: &[u8]) -> ActionResult<()> {
|
||||
self.ensure_enabled::<P>()?;
|
||||
match self
|
||||
.authmap
|
||||
.get(account)
|
||||
.map(|token_hash| keys::verify_key(token, token_hash.as_slice()))
|
||||
{
|
||||
Some(Some(true)) => {
|
||||
// great, authenticated
|
||||
self.whoami = Some(Self::try_auth_id::<P>(account)?);
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
// either the password was wrong, or the username was wrong
|
||||
err(P::AUTH_CODE_BAD_CREDENTIALS)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn regenerate_using_origin<P: ProtocolSpec>(
|
||||
&self,
|
||||
origin: &[u8],
|
||||
account: &[u8],
|
||||
) -> ActionResult<String> {
|
||||
self.verify_origin::<P>(origin)?;
|
||||
self._regenerate::<P>(account)
|
||||
}
|
||||
pub fn regenerate<P: ProtocolSpec>(&self, account: &[u8]) -> ActionResult<String> {
|
||||
self.ensure_root::<P>()?;
|
||||
self._regenerate::<P>(account)
|
||||
}
|
||||
/// Regenerate the token for the given user. This returns a new token
|
||||
fn _regenerate<P: ProtocolSpec>(&self, account: &[u8]) -> ActionResult<String> {
|
||||
let id = Self::try_auth_id::<P>(account)?;
|
||||
let (key, store) = keys::generate_full();
|
||||
if self.authmap.true_if_update(id, store) {
|
||||
Ok(key)
|
||||
} else {
|
||||
err(P::AUTH_CODE_BAD_CREDENTIALS)
|
||||
}
|
||||
}
|
||||
fn try_auth_id<P: ProtocolSpec>(authid: &[u8]) -> ActionResult<AuthID> {
|
||||
if authid.is_ascii() && authid.len() <= AUTHID_SIZE {
|
||||
Ok(unsafe {
|
||||
// We just verified the length
|
||||
AuthID::from_slice(authid)
|
||||
})
|
||||
} else {
|
||||
err(P::AUTH_ERROR_ILLEGAL_USERNAME)
|
||||
}
|
||||
}
|
||||
pub fn logout<P: ProtocolSpec>(&mut self) -> ActionResult<()> {
|
||||
self.ensure_enabled::<P>()?;
|
||||
self.whoami
|
||||
.take()
|
||||
.map(|_| ())
|
||||
.ok_or(ActionError::ActionError(P::AUTH_CODE_PERMS))
|
||||
}
|
||||
fn ensure_enabled<P: ProtocolSpec>(&self) -> ActionResult<()> {
|
||||
self.origin
|
||||
.as_ref()
|
||||
.map(|_| ())
|
||||
.ok_or(ActionError::ActionError(P::AUTH_ERROR_DISABLED))
|
||||
}
|
||||
pub fn verify_origin<P: ProtocolSpec>(&self, origin: &[u8]) -> ActionResult<()> {
|
||||
if self.get_origin::<P>()?.eq(origin) {
|
||||
Ok(())
|
||||
} else {
|
||||
err(P::AUTH_CODE_BAD_CREDENTIALS)
|
||||
}
|
||||
}
|
||||
fn get_origin<P: ProtocolSpec>(&self) -> ActionResult<&Authkey> {
|
||||
match self.origin.as_ref() {
|
||||
Some(key) => Ok(key),
|
||||
None => err(P::AUTH_ERROR_DISABLED),
|
||||
}
|
||||
}
|
||||
fn ensure_root<P: ProtocolSpec>(&self) -> ActionResult<()> {
|
||||
if self.are_you_root::<P>()? {
|
||||
Ok(())
|
||||
} else {
|
||||
err(P::AUTH_CODE_PERMS)
|
||||
}
|
||||
}
|
||||
pub fn delete_user<P: ProtocolSpec>(&self, user: &[u8]) -> ActionResult<()> {
|
||||
self.ensure_root::<P>()?;
|
||||
if user.eq(&USER_ROOT) {
|
||||
// can't delete root!
|
||||
err(P::AUTH_ERROR_FAILED_TO_DELETE_USER)
|
||||
} else if self.authmap.true_if_removed(user) {
|
||||
Ok(())
|
||||
} else {
|
||||
err(P::AUTH_CODE_BAD_CREDENTIALS)
|
||||
}
|
||||
}
|
||||
/// List all the users
|
||||
pub fn collect_usernames<P: ProtocolSpec>(&self) -> ActionResult<Vec<String>> {
|
||||
self.ensure_root::<P>()?;
|
||||
Ok(self
|
||||
.authmap
|
||||
.iter()
|
||||
.map(|kv| String::from_utf8_lossy(kv.key()).to_string())
|
||||
.collect())
|
||||
}
|
||||
/// Return the AuthID of the current user
|
||||
pub fn whoami<P: ProtocolSpec>(&self) -> ActionResult<String> {
|
||||
self.ensure_enabled::<P>()?;
|
||||
self.whoami
|
||||
.as_ref()
|
||||
.map(|v| String::from_utf8_lossy(v).to_string())
|
||||
.ok_or(ActionError::ActionError(P::AUTH_CODE_PERMS))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AuthProvider {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
authmap: self.authmap.clone(),
|
||||
whoami: None,
|
||||
origin: self.origin,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Feb 22 2022
|
||||
*
|
||||
* This file is a part of Skytable
|
||||
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
|
||||
* NoSQL database written by Sayan Nandan ("the Author") with the
|
||||
* vision to provide flexibility in data modelling without compromising
|
||||
* on performance, queryability or scalability.
|
||||
*
|
||||
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
mod keys {
|
||||
use super::super::keys::{generate_full, verify_key};
|
||||
|
||||
#[test]
|
||||
fn test_verify_key() {
|
||||
let (key, store) = generate_full();
|
||||
assert!(verify_key(key.as_bytes(), &store).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
mod authn {
|
||||
use crate::{
|
||||
actions::ActionError,
|
||||
auth::AuthProvider,
|
||||
protocol::{interface::ProtocolSpec, Skyhash2},
|
||||
};
|
||||
|
||||
const ORIG: &[u8; 40] = b"c4299d190fb9a00626797fcc138c56eae9971664";
|
||||
|
||||
#[test]
|
||||
fn claim_root_okay() {
|
||||
let mut provider = AuthProvider::new_blank(Some(*ORIG));
|
||||
let _ = provider.claim_root::<Skyhash2>(ORIG).unwrap();
|
||||
}
|
||||
#[test]
|
||||
fn claim_root_wrongkey() {
|
||||
let mut provider = AuthProvider::new_blank(Some(*ORIG));
|
||||
let claim_err = provider.claim_root::<Skyhash2>(&ORIG[1..]).unwrap_err();
|
||||
assert_eq!(
|
||||
claim_err,
|
||||
ActionError::ActionError(Skyhash2::AUTH_CODE_BAD_CREDENTIALS)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn claim_root_disabled() {
|
||||
let mut provider = AuthProvider::new_disabled();
|
||||
assert_eq!(
|
||||
provider.claim_root::<Skyhash2>(b"abcd").unwrap_err(),
|
||||
ActionError::ActionError(Skyhash2::AUTH_ERROR_DISABLED)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn claim_root_already_claimed() {
|
||||
let mut provider = AuthProvider::new_blank(Some(*ORIG));
|
||||
let _ = provider.claim_root::<Skyhash2>(ORIG).unwrap();
|
||||
assert_eq!(
|
||||
provider.claim_root::<Skyhash2>(ORIG).unwrap_err(),
|
||||
ActionError::ActionError(Skyhash2::AUTH_ERROR_ALREADYCLAIMED)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn claim_user_okay_with_login() {
|
||||
let mut provider = AuthProvider::new_blank(Some(*ORIG));
|
||||
// claim root
|
||||
let rootkey = provider.claim_root::<Skyhash2>(ORIG).unwrap();
|
||||
// login as root
|
||||
provider
|
||||
.login::<Skyhash2>(b"root", rootkey.as_bytes())
|
||||
.unwrap();
|
||||
// claim user
|
||||
let _ = provider.claim_user::<Skyhash2>(b"sayan").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claim_user_fail_not_root_with_login() {
|
||||
let mut provider = AuthProvider::new_blank(Some(*ORIG));
|
||||
// claim root
|
||||
let rootkey = provider.claim_root::<Skyhash2>(ORIG).unwrap();
|
||||
// login as root
|
||||
provider
|
||||
.login::<Skyhash2>(b"root", rootkey.as_bytes())
|
||||
.unwrap();
|
||||
// claim user
|
||||
let userkey = provider.claim_user::<Skyhash2>(b"user").unwrap();
|
||||
// login as user
|
||||
provider
|
||||
.login::<Skyhash2>(b"user", userkey.as_bytes())
|
||||
.unwrap();
|
||||
// now try to claim an user being a non-root account
|
||||
assert_eq!(
|
||||
provider.claim_user::<Skyhash2>(b"otheruser").unwrap_err(),
|
||||
ActionError::ActionError(Skyhash2::AUTH_CODE_PERMS)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn claim_user_fail_anonymous() {
|
||||
let mut provider = AuthProvider::new_blank(Some(*ORIG));
|
||||
// claim root
|
||||
let _ = provider.claim_root::<Skyhash2>(ORIG).unwrap();
|
||||
// logout
|
||||
provider.logout::<Skyhash2>().unwrap();
|
||||
// try to claim as an anonymous user
|
||||
assert_eq!(
|
||||
provider.claim_user::<Skyhash2>(b"newuser").unwrap_err(),
|
||||
ActionError::ActionError(Skyhash2::AUTH_CODE_PERMS)
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue