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
|
# The Dockerfile for the Skytable server sdb
|
||||||
#
|
|
||||||
|
|
||||||
FROM debian:stable
|
FROM debian:stable
|
||||||
|
# Copy the necessary binaries
|
||||||
COPY target/release/skyd /usr/local/bin
|
COPY target/release/skyd /usr/local/bin
|
||||||
COPY target/release/skysh /usr/local/bin
|
COPY target/release/skysh /usr/local/bin
|
||||||
RUN mkdir /etc/skytable
|
# Create necessary directories
|
||||||
RUN mkdir /var/lib/skytable
|
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
|
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
|
EXPOSE 2003/tcp
|
||||||
|
@ -1,82 +1,125 @@
|
|||||||
<html>
|
<br/><p align="center">
|
||||||
<div align="center">
|
<img width="150" src="assets/logo.jpg">
|
||||||
<img src="assets/logo.jpg" height=64 width=64>
|
</p>
|
||||||
<h1>Skytable</h1><h3>Your next NoSQL database</h3>
|
<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)
|
## 💡 What is Skytable?
|
||||||
[![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)
|
|
||||||
|
|
||||||
</div>
|
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**.
|
||||||
</html>
|
|
||||||
|
|
||||||
## 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
|
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**.
|
||||||
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.
|
|
||||||
|
|
||||||
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)
|
## 🎨 Features
|
||||||
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`!
|
|
||||||
|
|
||||||
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).
|
## 🚀 Getting started
|
||||||
- **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
|
|
||||||
|
|
||||||
**🛣️ 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>
|
1. Create a `space` and switch to it:
|
||||||
<a href="https://gitter.im/skytable/community"><img src="https://img.shields.io/badge/chat%20on-gitter-ed1965?logo=gitter&style=flat-square"></img>
|
```sql
|
||||||
</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>
|
CREATE SPACE myspace
|
||||||
</html>
|
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
|
## 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
|
Skytable is a free and open-source NoSQL database that aims
|
||||||
to provide flexibility in data modeling at scale.
|
to provide flexibility in data modeling at scale.
|
||||||
The `skytable` package contains the database server (`skyd`),
|
The `skytable` package contains the database server (`skyd`),
|
||||||
an interactive command-line client (`skysh`), a benchmarking
|
an interactive command-line client (`skysh`) and a benchmarking
|
||||||
tool (`sky-bench`) and a migration tool (`sky-migrate`).
|
tool (`sky-bench`).
|
@ -1,15 +1,35 @@
|
|||||||
#!/bin/sh -e
|
#!/bin/sh -e
|
||||||
|
|
||||||
|
SKY_DIR=/var/lib/skytable
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
if [ $1 = "install" ]; then
|
echo "Doing '$1'"
|
||||||
|
if [ "$1" = "configure" ]; then
|
||||||
|
# Enable and start skyd on fresh install
|
||||||
systemctl enable skyd
|
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 stop skyd
|
||||||
|
systemctl start skyd
|
||||||
fi
|
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