Introduction

WooriDB is a general purpose (EXPERIMENTAL) time serial database, which means it contains all entity registries indexed by DateTime and Uuid. It is schemaless, deep key-value storage and uses its own query syntax that is similar to SparQL and Crux's Datalog.

Name origin

Woori means our, in Korean, and although I developed this DB initially alone, it is in my culture and my hunsband's culture to call everything that is done for our communities and by our communities ours. I chose Woori instead of shelanu or bizdin because it easier to pronunce.

Project inspirations

  • Crux a general purpose bitemporal graph query database with support for SQL and Datalog. It was the ideallogical source of WooriDB. I had developed a rust client called Transistor which gave me the basic concepts of what I wanted WooriDB to have.
  • Datomic is a transactional database with a flexible data model, temporality and rich queries. I worked with Datomic at Nubank and it is the reason I found Juxt/Crux.
  • Prometheus An open-source monitoring system with a dimensional data model, flexible query language, efficient time series database and modern alerting approach.
  • SparQL SPARQL is a query language for RDF graph databases, it is flexible enough for query information based on datetime indexes.
  • Book Database Internals
  • Book Database System Concept
  • Book Designing Data Intensive Application
  • Professor Andy Pavlo Database Design Course.
  • Zero Trust in Time Series Data?

Naming conventions:

  • Entity Tree is similar to a SQL table, it is the data structure that contains all ids and entities map relations.
  • Entity ID is the id of an entity inside of an Entity Tree.
  • Entity map is the content of and entity associated with the entity id.

Installation and Important Information

Installation

To run WooriDB it is necessary to have Rust installed in the machine. There are two ways to do this:

  1. Go to rustup.rs and copy the command there, for unix it is curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh.
  2. Clone WooriDB and execute make setup.

Executing WooriDB

  • Release mode performance: make release in project root for performance optimization.
  • Release mode size: make run in project root for size optimization.
  • Debug mode: make debug in project root.

Docker

You can find the latest docker image at naomijub/wooridb. Currently the most stable tag is beta-8. To execute the docker container run:

  • docker run -p 1438:1438 naomijubs/wooridb:beta-8 debug for debug mode.
  • docker run -p 1438:1438 -e AUTH_HASHING_COST=8 -e ADMIN=your-admin-id -e ADMIN_PASSWORD=your-admin-pswd naomijubs/wooridb:beta-8 run for size optimization.
  • docker run -p 1438:1438 -e AUTH_HASHING_COST=8 -e ADMIN=your-admin-id -e ADMIN_PASSWORD=your-admin-pswd naomijubs/wooridb:beta-8 release for performance optimization.
  • All -e/--env can be replaced by a --env-file path/to/your/.env. Your .env file should contain the following fields:
HASHING_COST=16
PORT=1438
SESSION_EXPIRATION_TIME=3600
MAX_CONNECTIONS=1000
CLIENT_SHUTDOWN=5000
AUTH_HASHING_COST=8
ADMIN=your-admin-id
ADMIN_PASSWORD=your-admin-pswd

Important Information

Configurations

  • To run the project in debug mode it is important to export the following environment variables HASHING_COST, PORT, MAX_CONNECTIONS, CLIENT_SHUTDOWN. Default values are:
HASHING_COST=16
PORT=1438
MAX_CONNECTIONS=1000
CLIENT_SHUTDOWN=5000
  • MAX_CONNECTIONS is the maximum number of connections supported simultaneously.
  • CLIENT_SHUTDOWN is the time to drop a client's connection, use 0 to disable it.
  • To run the project in release mode it is important to export the following environment variables HASHING_COST, PORT, MAX_CONNECTIONS, CLIENT_SHUTDOWN, AUTH_HASHING_COST, ADMIN, ADMIN_PASSWORD, SESSION_EXPIRATION_TIME. There are no default values for AUTH_HASHING_COST, ADMIN, ADMIN_PASSWORD, SESSION_EXPIRATION_TIME.

Features

  • Sintax inspired by SparQL and Crux's datalog.
  • Entities are indexed by date(time).
  • Schemaless.
  • Deep Key-value storage, which means that you can have key values inside hashmap values.
  • Hashing entity map keys content with ENCRYPT.
  • Unique entity maps keys content with UNIQUE for an entity tree.
  • Hashed values are filtered out of SELECT and can only be checked with CHECK keyword.
  • Ron schemas for input and output.
    • JSON is supported via feature.
    • EDN to be supported via feature.
  • Entities are also indexed by entity_name (entity tree key) and Uuid (entity id). Entity map format is a HashMap where keys are strings and values are supported Types.
  • Stores persistent data locally.
    • S3 as a backend is to be developed.
    • DynamoDB as a backend is to be developed.
  • Able to handle very large numbers when using the P suffix.
    • Ex: 98347883122138743294728345738925783257325789353593473247832493483478935673.9347324783249348347893567393473247832493483478935673P.
  • Configuration is done via environment variables.
    • CORS
  • Authentication and Authorization via session token
  • Users created and deleted by ADMIN user.
  • Conditional Update
  • File compression done with zstd.
  • Entity id history
  • Possible Relation Algebra
    • Union by entity_id
    • Intersection by entity_id
    • Difference by entity_id
    • Join
    • Dedup
    • Sort
    • Aggregate

Naming conventions:

  • Entity Tree is similar to SQL table, it is the data structure that contains all ids and entities map relations.
  • Entity ID is the id of an entity inside Entity tree.
  • Entity map is the content of and entity associated with the entity id.

Woori Query Language

Woori Query language or WQL is WooriDB's Query Language and it is inspired by SparQL, Datalog and SQL. Its main features are:

Transactions

  • CREATE entity tree key by name.
    • UNIQUE: With unique values for entity map keys inside entity tree.
    • ENCRYPTS: With encrypted values for defined key-values inside entity map.
  • INSERT entity map into entity tree.
  • UPDATEs with SET or CONTENT entity map.
    • SET UPDATE replaces the sent entity map as the entity's map content.
    • CONTENT UPDATE updates numerical and string the current entity's map content with the sent entity map value and the other values work the same way as SET.
  • MATCH UPDATE updates entity map content with new content if match condition is satisfied.
  • DELETEs the last entity map content for an entity id.
  • EVICT
    • Evicts a specific entity id and entity map
    • Evicts all entities in the entity tree key.

Queries

  • SELECT the only way to retrieve an entity's content.
    • WHERE clause is available.
    • WHEN clause is available
  • CHECK the only way to verify keys that are encrypted.

ALL DATA STRUCTURES HASHMAPS, HASHSETS AND LIST MUST CONTAIN A , AFTER EACH ELEMENT. Example #{name, ssn,} is valid but #{name, ssn} is not valid.

Examples

CREATE

Creates an entity tree key.

  • CREATE ENTITY my_entity this will create an entity tree key named my_entity, in SQL terms it means CREATE TABLE my_entity.
  • CREATE ENTITY my_entity UNIQUES #{name, ssn,} the entity tree key named my_entity will only allow unique values for the entities keys name and ssn in its maps.
  • CREATE ENTITY my_entity ENCRYPTS #{pswd,} the entity tree key named my_entity will encrypt the entities keys that are pswd. The hashing cost of the encrypt is defined by the environment variable HASHING_COST, the recommended is between 10 and 14.
  • Encrypted keys cannot be unique so CREATE ENTITY my_entity UNIQUES #{name, ssn, pswd,} ENCRYPTS #{pswd,} is invalid but CREATE ENTITY my_entity UNIQUES #{name, ssn,} ENCRYPTS #{pswd,} is valid.

INSERT

Inserts an entity id and an entity map into entity tree key.

  • INSERT {a: 123, b: "hello julia",} INTO entity_key this will insert the entity map {a: 123, b: "hello julia",} (key a containing as Type::Integer(123) and key b containing a Type::String("hello julia")) and a random Uuid for entity ID into entity tree key entity_key.

To INSERT entity with a predefined Uuid it is necessary to use the keyword WITH after the entity tree key followed by as Uuid-V4. INSERT {a: 123, b: "hello julia",} INTO entity_key WITH 4f6fccb0-20fb-4d8e-af7c-65db30f4954a.

UPDATE SET

Updates the content by replacing the previous entity map in entity tree key my_entity_name with the entity id 48c7640e-9287-468a-a07c-2fb00da5eaed.

  • UPDATE my_entity_name SET {a: -4, b: 32,} INTO 48c7640e-9287-468a-a07c-2fb00da5eaed this will replace the current entity map stored in entity id 48c7640e-9287-468a-a07c-2fb00da5eaed.

UPDATE CONTENT

Updates the content by numerical addition or string concatenation of the previous entity map in entity tree key my_entity_name with the entity id 48c7640e-9287-468a-a07c-2fb00da5eaed. Non numerical or non string value will just be replaced. If the key doesn't exist it will be created.

  • UPDATE my_entity_name CONTENT {a: -4, b: 32,} INTO 48c7640e-9287-468a-a07c-2fb00da5eaed this will add -4 to entity map key a and add 32 to entity map key b in the current entity map stored in entity id 48c7640e-9287-468a-a07c-2fb00da5eaed.

MATCH UPDATE

Similar to SET, but it requires a precondition to be satisfied.

  • MATCH ALL(a == 1, b >= 3, c != \"hello\", d < 7,) UPDATE this_entity SET {a: 123, g: NiL,} INTO d6ca73c0-41ff-4975-8a60-fc4a061ce536 if all conditions defined inside ALL are satisfied the set update will happen.
    • ALL is an logical and between all conditions, meaning that all of them must be true.
    • ANY is an logical or between all conditions, meaning that at least one of them must be true.
    • NULL KEYS, ALL returns an error if a null key is present and ANY just ignores null keys.
    • Possible conditions are:
      • == means equals, so if a == 100, this means that the entity map key a must equal to 100.
      • != means not equal, so if a != 100, this means that the entity map key a must not equal to 100.
      • >= means greater or equal, so if a >= 100, this means that the entity map key a must br greater or equal to 100.
      • <= means lesser or equal, so if a <= 100, this means that the entity map key a must be lesser or equal to 100.
      • > means greater, so if a > 100, this means that the entity map key a must be greater than 100.
      • < means lesser, so if a < 100, this means that the entity map key a must be less than 100.

DELETE

Deletes the last entity map event for an entity ID in entity tree key, that is, it deletes the last state of an entity map.

  • DELETE 48c7640e-9287-468a-a07c-2fb00da5eaed FROM my_entity_name this will delete the last state of entity id 48c7640e-9287-468a-a07c-2fb00da5eaed in entity tree key my_entity_name from entity history.

EVICT

Removes all occurrences of an entity from the entity tree. It can be just the entity id or the whole entity tree key.

  • EVICT 48c7640e-9287-468a-a07c-2fb00da5eaed FROM my_entity_name removes all occurrences of the entity id 48c7640e-9287-468a-a07c-2fb00da5eaed from the entity tree key my_entity_name, they cannot be queried anymore.
  • EVICT my_entity removes the key my_entity from the entity tree. It cannot be queried anymore. It is similar to SQL's DROP TABLE my_entity.

CHECK

Checks for encrypted key data validity. This transaction only works with keys that are encrypted and it serves as a way to verify if the passed values are true of false against encrypted data.

  • CHECK {pswd: "my-password", ssn: 3948453,} FROM my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed this will check if keys psdw and ssn from entity id 48c7640e-9287-468a-a07c-2fb00da5eaed in entity tree key my_entity_name have the values "my-password" for pswd and 3948453 for ssn.

SELECT

This is the way to query entities from WooriDB. Similar to SQL and SparQL SELECT.

Possible SELECT combinations:

  • SELECT * FROM my_entity_name selects all entity ids and entity maps for the entity tree key my_entity_name with all the possible entities map keys.
  • SELECT #{name, last_name, age,} FROM my_entity_name selects all entity ids and entity maps for the entity tree key my_entity_name with only the keys name, last_name, age for the entities map.
  • SELECT * FROM my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed selects the entity map containing the entity id 48c7640e-9287-468a-a07c-2fb00da5eaed from the entity tree key my_entity_name with all the possible entities map keys.
  • SELECT #{name, last_name, age,} FROM my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed selects the entity map containing the entity id 48c7640e-9287-468a-a07c-2fb00da5eaed from the entity tree key my_entity_name with only the keys name, last_name, age for the entities map.
  • SELECT * FROM my_entity_name IDS IN #{48c7640e-9287-468a-a07c-2fb00da5eaed, 57c7640e-9287-448a-d07c-3db01da5earg, 54k6640e-5687-445a-d07c-5hg61da5earg,} this will return the entities map containing the entities ids #{48c7640e-9287-468a-a07c-2fb00da5eaed, 57c7640e-9287-448a-d07c-3db01da5earg, 54k6640e-5687-445a-d07c-5hg61da5earg,} from entity tree key my_entity_name. Keys set is available.
  • Select * FROM my_entity ID 0a1b16ed-886c-4c99-97c9-0b977778ec13 WHEN AT 2014-11-28T21:00:09+09:00 this will select the last entity map state for the entity id 0a1b16ed-886c-4c99-97c9-0b977778ec13 in entity tree key my_entity at date 2014-11-28. Requires to use DateTime UTC, for now.
  • SELECT * FROM entity_name ID <uuid> WHEN START 2014-11-28T09:00:09Z END 2014-11-28T21:00:09Z this will select the all entity map states for the entity id 0a1b16ed-886c-4c99-97c9-0b977778ec13 in entity tree key my_entity in the time range starting at 2014-11-28T09:00:09Z and ending at 2014-11-28T21:00:09Z.
  • SELECT * FROM my_entity WHERE { ?* my_entity:a ?a, ?* my_entity:c ?c, (== ?a 123),(or (>= ?c 4300.0), (< ?c 6.9),),} this will select all entities ids and entities maps from entity tree key my_entity that satisfy the where clause.
    • ?* my_entity:a ?a and ?* my_entity:c ?c define that the entity keys a and c from entity tree key my_entity will receive the attributed value ?a and ?c respectively.
    • (== ?a 123) selects all entities which entity map key a is equal to 123.
    • (or (>= ?c 4300.0), (< ?c 6.9),) selects all entities which entity map key c is greater than or equal to 4300.0 or is smaller than 6.9.

WHERE Clause

Possible functions for the where clause:

  • in: (in ?k1 123 34543 7645 435), ?k1 must be present in the set containing 123 34543 7645 435. NOTE: for now, please don't use ,.
  • between: (between ?k1 0 435), ?k1 must be between starting value 0 and ending value 435. If you set more than 2 arguments it will return a ClauseError.
  • like: (like ?k2 "%naomi%"), like is comparing ?k2 with the string "%naomi%" considering that % are wildcards. "%naomi" means end_with("naomi"), "naomi%" means starts_with("naomi") and "%naomi%" means contains("naomi"). Possible regex support in the future.
  • ==, >=, >, <, <=, != -> (>= ?k1 0) which means get all values that ?k1 is greater than or equal to 0.
  • or: All arguments inside the or function call will be evaluated to true if any of them is true.

Relation Algebra

Some relation algebra may be implemented:

  • Projection
  • Union
  • Intersection
  • Difference (SQL's EXCEPT?)
  • Join
  • Product (SQL's CROSS JOIN?)
  • Assign
  • Dedup
  • Sort
  • Aggregate
  • Division

Entity map value TYPES

Types Notes

  1. Precise floats or numbers larger than f64::MAX/i128::MAX can be defined with an UPPERCASE P at the end.

    • Note: This type cannot be updated with UPDATE CONTENT.
    • Ex.: INSERT {a: 98347883122138743294728345738925783257325789353593473247832493483478935673.9347324783249348347893567393473247832493483478935673P, } INTO my_entity.
  2. BLOB will not be supported. Check out To BLOB or Not To BLOB: Large Object Storage in a Database or a Filesystem

  • Char(char) contains the type char defined by 'c',
  • Integer(isize) contains the type isize, just a number without .,
  • String(String) contains any value passed with "this is a string",
  • Uuid(Uuid) contains an Uuid V4,
  • Float(f64) contains the type f64, any number containing .,
  • Boolean(bool) contains type boolean true of false,
  • Vector(Vec<Types>) contains a vector of Types,
  • Map(HashMap<String, Types>) contains a HashMap of key String and value Types,
  • Hash(String) contains a Hash generated by ENCRYPTS,
  • Precise(String) contains a very large integer or a very large float,
  • Nil contains a null/nil value,
  • DateTime contains a DateTime

Authorization and Authentication

Authentication and authorization only work with release mode, so cargo run --release is required.

Some environment variables are also required:

AUTH_HASHING_COST=8 
ADMIN=your_admin
ADMIN_PASSWORD=your_password
  • AUTH_HASHING_COST is the hashing cost for password in the authentication system.
  • ADMIN is the admin's user id.
  • ADMIN_PASSWORD is the admin's user password.

One optional environment variable can be used to configure the time to expirate a session token. SESSION_EXPIRATION_TIME has a default value of 3600 seconds, or 1 hour.

Creating new users

  • ADMIN is the only user role capable of creating new users. For now there can only be one ADMIN.

To create a new user, POST at /auth/createUser with your admin credentials and the new user info as follows (in RON format):

(
  admin_id: "your_admin",
  admin_password: "your_password",
  user_info: (
    user_password: "my_password",
    role: [User,],
  ),
)

User information consists of the user's password to be used and the user's roles. Remember to always put , at the end. Response to this request will be (user_id: \"<some-uuid>\",), containing the user's unique ID.

Available user roles are:

  • ADMIN - works primarily at /auth/createUser.
  • USER - works on all /wql/query, /wql/tx and /auth/putUserSession.
  • WRITE - only works on /wql/tx and /auth/putUserSession.
  • READ - only works on /wql/query and /auth/putUserSession.
  • HISTORY - only works on /entity-history.
  • New roles to be added as needed.

Getting a session token

To make a request at WQL endpoints you need a session token that will expire within 3600 seconds. To retrieve a session token you need to PUT at endpoint /auth/putUserSession your user credentials as follows (in RON format):

(id: "<user_id>", user_password: "<user_password>",)

Response will be a plain/text with your token.

Making auth requests to /wql/tx and /wql/query.

To avoid authentication and authorization errors, add your token to the authorization bearer header, Authorization: Bearer <your session token>. Your user needs the correct session token and the correct role for this request.

Deleting users

  • ADMIN is the only user role capable of creating new users. For now there can only be one ADMIN.

To delete a list of users, POST at /auth/deleteUsers with your admin credentials and the users ids you want to delete as follows (in RON format):

(
  admin_id: "your_admin",
  admin_password: "your_password",
  users_ids: [
    "<uui1>",
    "<uui2>",
    "<uui3>",
  ],
)

The response will be a vector containing all Uuids sent or an error FailedToDeleteUsers.

TODOs:

  • Adding other admins and removing admins is not yet implemented.

Transactions

Transaction is the name of all operations that change the database state, like CREATE, INSERT, UPDATE, MATCH, DELETE, EVICT. This is done by sending a POST request to endpoint <ip>:1438/wql/tx. An example request would be curl -X POST -H "Content-Type: application/wql" <ip>:1438/wql/tx -d 'CREATE ENTITY my_entity'. In release mode it is necessary to use header Authorization: Bearer <your session token> for this endpoint.

Reminder A comma is required at the end of every data structure representation. Ex.: {a: 123, b: 456,}, #{a, b, c,}, (a, b, c,). No need for ; at the end of each expression.

CREATE ENTITY

CREATE WQL Reference

Similar to CREATE TABLE in SQL, it creates an entity tree which matches the table name. It requires an entity name like my_entity_name after CREATE ENTITY.

Example request:

CREATE ENTITY my_entity_name

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: Create,
    entity: "my_entity_name",
    uuid: None,
    state: "",
    message: "Entity `my_entity_name` created",
)
}
  • CREATE ENTITY <entity> ENCRYPT #{keys...}: When the system has encrypted keys, the insert and upload requests take longer due to hashing function and the verify function. This is determined by the hashing cost:
bench_cost_10      ... bench:  51,474,665 ns/iter (+/- 16,006,581)
bench_cost_14      ... bench: 839,109,086 ns/iter (+/- 274,507,463)
bench_cost_4       ... bench:     795,814 ns/iter (+/- 42,838)
bench_cost_default ... bench: 195,344,338 ns/iter (+/- 8,329,675)
* Note that I don't go above 14 as it takes too long. However, it is way safer, it is a trade-off. 

INSERT

INSERT WQL Reference

Inserts a HashMap<String, Types> into the entity tree key previously created (my_entity_name). This request returns a Uuid containing the entity id.

Example request:

INSERT {a: 123,  c: \"hello\", d: \"world\",} 
INTO my_entity_name

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: Insert,
    entity: "my_entity_name",
    uuid: Some("00d025c9-eda8-4190-a33a-29998bd77bd3"),
    state: "",
    message: "Entity my_entity_name inserted with Uuid 00d025c9-eda8-4190-a33a-29998bd77bd3",
)
}

The request above will insert an entity with the following structure in my_entity_name:


#![allow(unused)]
fn main() {
{"my_entity_name": {
    Uuid(00d025c9-eda8-4190-a33a-29998bd77bd3): {a: 123, c: "hello", d: "world", tx_time: DateTime("2014-11-28T12:00:09Z"),},
}}
}
  • To pre-define the Uuid of the entity to insert use WITH keyword followed by an Uuid-v4. This uuid will be used to identify the entity map in the entity tree. As follows:
INSERT {a: 123,  c: \"hello\", d: \"world\",} 
INTO my_entity_name
WITH <ENTITY-UUID>

UPDATE

Updates the content of an entity map for an entity tree key and an entity id. There are two possible updates:

UPDATE SET

UPDATE SET WQL Reference SET updates defines the current value of the entity map to the ones being passed, so if your entity map is {a: 123, b: 12.5,} and your set update has the hashmap {a: 432, c: \"hello\",}, the current state of the entity map will be {a: 432, b: 12.5, c: \"hello\", tx_time: DateTime(\"2014-11-28T12:00:09Z\"),}.

Example request:

UPDATE my_entity_name 
SET {a: -4, b: 32,} 
INTO 00d025c9-eda8-4190-a33a-29998bd77bd3

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: UpdateSet,
    entity: "my_entity_name",
    uuid: Some("00d025c9-eda8-4190-a33a-29998bd77bd3"),
    state: "{\"b\": Integer(32),\"a\": Integer(-4), \"tx_time\": DateTime(\"2014-11-28T12:00:09Z\"),}",
    message: "Entity my_entity_name with Uuid 00d025c9-eda8-4190-a33a-29998bd77bd3 updated",
)
}

UPDATE CONTENT

UPDATE CONTENT WQL Reference CONTENT updates are a way to add numerical values and concatenate Strings, so if your entity map is {a: 432, c: \"hello\",} and your content update has the hashmap {a: -5, c: \"world\", b: 12.5, tx_time: DateTime(\"2014-11-28T12:00:09Z\"),} the current state of the entity map will be {a: 427, c: \"helloworld\", b: 12.5, tx_time: DateTime(\"2014-11-28T12:00:09Z\"),}.

Example request:

UPDATE my_entity_name 
CONTENT {a: -34, b: 7,} 
INTO 00d025c9-eda8-4190-a33a-29998bd77bd3

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: UpdateContent,
    entity: "my_entity_name",
    uuid: "00d025c9-eda8-4190-a33a-29998bd77bd3",
    state: "{\"b\": Integer(39),\"a\": Integer(-38), \"tx_time\": DateTime(\"2014-11-28T12:00:09Z\"),}",
    message: "Entity my_entity_name with Uuid 00d025c9-eda8-4190-a33a-29998bd77bd3 updated",
)
}

MATCH UPDATE

MATCH UPDATE WQL Reference

Updates entity only if precondition condition is matched. This transaction is significantly slower than other updates.

Example request:

MATCH ALL(a < 0, b >= 3) 
UPDATE my_entity_name 
SET {a: 123, g: NiL,} 
INTO 00d025c9-eda8-4190-a33a-29998bd77bd3

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: UpdateSet,
    entity: "my_entity_name",
    uuid: "00d025c9-eda8-4190-a33a-29998bd77bd3",
    state: "{\"b\": Integer(39),\"a\": Integer(123),\"g\": Nil, \"tx_time\": DateTime(\"2014-11-28T12:00:09Z\"),}",
    message: "Entity my_entity_name with Uuid 00d025c9-eda8-4190-a33a-29998bd77bd3 updated",
)
}

DELETE

DELETE WQL Reference

Deletes the last entity event for an ID, that is, it deletes the last state of an entity map. The deleted state is preserved in the database but cannot be queried anymore.

If you have, for example, one update on your entity, it will roll back to the INSERT event. However, if you have only an INSERT event then your state will become an empty hashmap.

Example request:

DELETE 00d025c9-eda8-4190-a33a-29998bd77bd3 FROM my_entity_name

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: Delete,
    entity: "my_entity",
    uuid: Some("00d025c9-eda8-4190-a33a-29998bd77bd3"),
    state: "",
    message: "Entity my_entity with Uuid 00d025c9-eda8-4190-a33a-29998bd77bd3 deleted",
)
}

TODOs:

  • Delete entity with ID at transaction-time

EVICT

EVICT WQL Reference

EVICT ENTITY ID:

Removes all occurrences of an entity map with the given ID.

Example request

EVICT 00d025c9-eda8-4190-a33a-29998bd77bd3 from my_entity_name

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: EvictEntity,
    entity: "my_entity",
    uuid: Some("00d025c9-eda8-4190-a33a-29998bd77bd3"),
    state: "",
    message: "Entity my_entity with id 6ac9d1bb-2b0c-4631-bc05-682ab4ae8306 evicted",
)
}

For now it only deletes the access to the entity history.

EVICT ENTITY:

Evicts all entity ids registries from entity tree and removes entity tree key, which means entity tree does not contain the key for the evicted entity: Similar to SQL DROP TABLE <entity>.

Example request:

EVICT my_entity

Example response:


#![allow(unused)]
fn main() {
(
    tx_type: EvictEntityTree,
    entity: "my_entity",
    uuid: None,
    state: "",
    message: "Entity my_entity evicted",
)
}

TX_TIME

Whenever you make a transaction to WooriDB (INSERT, UPDATES, MATCH) a field named tx_time will be added to the entity map, this field is of type Types::Datetime(chrono::Datetime<Utc>).

Queries

Query is the name of all operations that read the database, like SELECT, CHECK. This is done by sending a POST request to endpoint <ip>:1438/wql/query. An example request would be curl -X POST -H "Content-Type: application/wql" <ip>:1438/wql/query -d 'SELECT * FROM my_entity'. In release mode it is necessary to use header Authorization: Bearer <your session token> for this endpoint.

Reminder A comma is required at the end of every data structure representation. Ex.: {a: 123, b: 456,}, #{a, b, c,}, (a, b, c,). No need for ; at the end of each expression.

CHECK

CHECK WQL Reference

Checks for encrypted data, in entity map, validity. It requires an entity tree name after FROM and an entity id as Uuid after ID. This transaction only works with keys that are encrypted and it serves to verify if the passed values are true of false against encrypted data.

Considering entity tree key my_entity_name with entity id 48c7640e-9287-468a-a07c-2fb00da5eaed with entity map {pswd: Hash("my-password"), name: "Julia"}

Example 1:

Example request:

CHECK {pswd: \"my-password\",} 
FROM my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed

Example response:


#![allow(unused)]
fn main() {
{"pswd": true,}
}

Exmample 2:

Example request:

CHECK {pswd: \"my-password\", ssn: "1234",} 
FROM my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed

Example response:


#![allow(unused)]
fn main() {
(
 error_type: "CheckNonEncryptedKeys",
 error_message: "CHECK can only verify encrypted keys: [\"ssn\"]",
)
}

Example 3:

Example request:

CHECK {pswd: \"your-password\",} 
FROM my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed

Example response:


#![allow(unused)]
fn main() {
{"pswd": false,}
}

SELECT

SELECT WQL Reference

This is the way to query entities from WooriDB. Similar to SQL and SparQL SELECT. There are several different keywords that can be combined with SELECT, a few examples are:

* and #{keys...} can be used in all select modes.

SELECTing all entity map keys FROM entity tree key:

Same as SQL using the token * will defined that all keys in the entity map will be returned. The query SELECT * FROM entity_name selects all entity ids and entity maps found inside entity tree key entity_name. It is equivalent to SQL's Select * From table.

Example request: SELECT * from my_entity_name.

Example response:

This query will return a BTreeMap<Uuid, HashMap<String, Types>>


#![allow(unused)]
fn main() {
{
  48c7640e-9287-468a-a07c-2fb00da5eaed:
    {a: 123, b: 43.3, c: "hello", d: "world", tx_time: DateTime("2014-11-28T12:00:09Z"),},
  57c7640e-9287-448a-d07c-3db01da5earg:
    {a: 456, b: 73.3, c: "hello", d: "brasil", tx_time: DateTime("2014-11-28T12:00:09Z"),},
  54k6640e-5687-445a-d07c-5hg61da5earg:
    {a: 789, b: 93.3, c: "hello", d: "korea", tx_time: DateTime("2014-11-28T12:00:09Z"),},
}
}

SELECTing a set of entity map keys FROM entity tree key:

Differently from SQL, WQL requires the keys to be inside a set like #{a, b, c,}, which will return only the keys a, b, c. It is equivalent to SELECT a, b, c FROM table.

Example request: SELECT #{a, b, c,} FROM my_entity_name.

Example response:


#![allow(unused)]
fn main() {
{
    48c7640e-9287-468a-a07c-2fb00da5eaed:
        {a: 123, b: 43.3, c: "hello",},
    57c7640e-9287-448a-d07c-3db01da5earg:
        {a: 456, b: 73.3, c: "hello",},
    54k6640e-5687-445a-d07c-5hg61da5earg:
        {a: 789, b: 93.3, c: "hello",},
}
}

SELECTing one entity map FROM entity tree key:

Select one entity map (by its ID) from entity tree my_entity. By including the key ID after the FROM entity_name it is possible to select a single entity. The content for ID is the entity id's Uuid. It is equivalent to SQL's Select * From table WHERE id = <uuid>.

Example request SELECT * from my_entity_name ID 48c7640e-9287-468a-a07c-2fb00da5eaed.

Example response:

It will return only the entity map contained inside inside entity id 48c7640e-9287-468a-a07c-2fb00da5eaed. {a: 123, b: 43.3, c: \"hello\", d: \"world\", tx_time: DateTime(\"2014-11-28T12:00:09Z\"),}.

SELECTing a set of entities IDs and maps FROM entity tree key:

Select a few entities maps (by their IDs) from entity tree my_entity. Key IN receives a set of Uuids

Example request:

SELECT #{a, b, c,} 
FROM my_entity_name
IDS IN #{48c7640e-9287-468a-a07c-2fb00da5eaed, 57c7640e-9287-448a-d07c-3db01da5earg}

Example response:


#![allow(unused)]
fn main() {
{
    48c7640e-9287-468a-a07c-2fb00da5eaed:
        {a: 123, b: 43.3, c: "hello",},
    57c7640e-9287-448a-d07c-3db01da5earg:
        {a: 456, b: 73.3, c: "hello",},
}
}

SELECTing the last entity map for entity id at DATETIME FROM entity tree key:

Select an entity on a defined past day using the WHEN AT keys. Key WHEN AT is the date to search. Time will be discarded. The ID field can be used before WHEN to define a specific entity id, IDS IN is not supported. Date format should be "2014-11-28T21:00:09+09:00" or "2014-11-28T21:00:09Z".

Example requests:

  • Select * FROM my_entity ID 0a1b16ed-886c-4c99-97c9-0b977778ec13 WHEN AT 2014-11-28T21:00:09+09:00
  • OR Select #{name,id,} FROM my_entity WHEN AT 2014-11-28T21:00:09Z.

Example response: {a: 34, b: 4.3, c: \"hello\", d: \"Julia\",}

TODOs:

  • Support IDS IN

SELECTing all entities maps BY ID FROM ENTITY between two DATETIME:

Select all occurrences of an entity id from entity tree entity_name in a time range. The time range must be on the same day as WHEN START 2014-11-28T09:00:09Z END 2014-11-28T21:00:09Z.

  • Key WHEN defines it as a temporal query.
  • Key START is the DateTime<Utc> to start the range query.
  • Key END is the DateTime<Utc> to end the range query.
  • Same day validation occurs. Returning the error message "START date and END date should be the same date.".
  • IDS IN will not be supported as the query is too extensive.

Example request:

SELECT #{a, b, c, d}
FROM entity_name
ID 0a1b16ed-886c-4c99-97c9-0b977778ec13
WHEN START 2014-11-28T09:00:09Z END 2014-11-28T21:00:09Z

Example response:


#![allow(unused)]
fn main() {
{
    "2014-11-28T09:00:09Z":
        {a: 34, b: 4.3, c: "hello", d: "Julia",},
    "2014-11-28T13:00:09Z":
        {a: 23, b: -3.3, c: "hello", d: "World",},
    "2014-11-28T19:00:09Z":
        {a: 78, b: 67.3, c: "hello", d: "Julia",},
    "2014-11-28T21:00:09Z":
        {a: 123, b: 43.3, c: "hello", d: "Gasp",},
}
}

SELECTing entities ids and maps FROM entity tree key WHERE conditions are satisfied

This is probably the most different part in relation to SQL as it is inspired by SparQL and Crux/Datomic datalog. Selects entities ids and maps with positive WHERE clauses. Key WHERE receives all clauses inside a {...} block.

To use select with the where clause you can use the following expression:

  • SELECT * FROM my_entity WHERE {<clauses>}

Example 1:

Example Request:

SELECT * FROM test_entity
WHERE {
    ?* test_entity:age ?age,
    ?* test_entity:race ?race,
    (> ?age 25),
    (in ?race "Black" "brown"),
}

Example response:


#![allow(unused)]
fn main() {
{
 "08824242-8098-4253-a384-987ff8d78c7d": {
  "race": String("Black"),
  "origin": String("Africa"),
  "age": Integer(34),
  "name": String("Diego F"),
  "tx_time": DateTime("2014-11-28T12:00:09Z"),
 },
 "ea3228d0-0164-453b-bae2-f726c4a9b979": {
  "origin": String("Bukhara"),
  "race": String("brown"),
  "age": Integer(33),
  "name": String("julia naomi"),
  "tx_time": DateTime("2014-11-28T12:00:09Z"),
 },
}
}

Example 2:

Example request:

SELECT * FROM test_entity
WHERE {
    ?* test_entity:age ?age,
    (between ?age 18 27),
}

Example response:


#![allow(unused)]
fn main() {
{
 "475fab3b-b023-4f6c-a18e-7e8b25b84a28": {
  "age": Integer(25),
  "name": String("Otavio"),
  "origin": String("Brasil"),
  "race": String("Multiracial"),
  "tx_time": DateTime("2014-11-28T12:00:09Z"),
 },
}
}

TODOs:

  • Support temporality for where clause

Entity History

Entity history is one of the main features of WooriDB. You can run it by using the features flag. To execute WooriDB with history feature enable execute:

  • make history, or;
  • cargo run --manifest-path woori-db/Cargo.toml --release --features history

It receives an entity_key name and an entity_id which will return the whole history of (DateTime<Utc>, entity_map) for the entity_id in the entity tree for key entity_key. This is done by sending a POST request to endpoint <ip>:1438/entity-history. An example request would be curl -X POST -H "Content-Type: application/wql" <ip>:1438/entity-history -d '(entity_key: "entity_tree_key", entity_id: "<some-Uuid>",)'. In release mode it is necessary to use header Authorization: Bearer <your session token> for this endpoint.

Example request:

(entity_key: "entity_tree_key", entity_id: "dc3069e7-2a22-4fbc-ae05-f78a807239c0",)

Example response:


#![allow(unused)]
fn main() {
{
"2021-03-02T05:00:19.813514240Z": {
    "a": Integer(123),
    "b": Float(12.3),
    "tx_time": DateTime("2021-03-02T05:00:19.813514240Z"),
},
"2021-03-02T05:00:19.816357939Z": {
    "b": Float(12.3),
    "a": Integer(123),
    "tx_time": DateTime("2021-03-02T05:00:19.816357939Z"),
},
"2021-03-02T05:00:19.817189987Z": {
    "b": Float(12.3),
    "c": Boolean(true),
    "a": Integer(34),
    "tx_time": DateTime("2021-03-02T05:00:19.817189987Z"),
},
"2021-03-02T05:00:19.818031113Z": {
    "b": Float(12.3),
    "a": Integer(321),
    "c": Char('h'),
    "tx_time": DateTime("2021-03-02T05:00:19.818031113Z"),
},}
}
  • This response is considering the following events:
  1. INSERT {a: 123, b: 12.3,} INTO entity_tree_key.
  2. UPDATE entity_tree_key SET {{a: 12, c: Nil,}} INTO dc3069e7-2a22-4fbc-ae05-f78a807239c0. Note that this event does not appear due to DELETE.
  3. Delete dc3069e7-2a22-4fbc-ae05-f78a807239c0 FROM entity_tree_key. Entity map state is the same as the previous state, therefore "2021-03-02T05:00:19.813514240Z" and "2021-03-02T05:00:19.816357939Z" have equal content.
  4. UPDATE entity_tree_key SET {{a: 34, c: true,}} INTO dc3069e7-2a22-4fbc-ae05-f78a807239c0.
  5. UPDATE entity_tree_key SET {{a: 321, c: 'h',}} INTO dc3069e7-2a22-4fbc-ae05-f78a807239c0.

Entity history with time ranges

There are two extra parameters that can be used with entity-history, they are start_datetime and end_datetime. Both parameters are optional and if they are present they will define the time limits of the query. start_datetime is the beginning of the time range query while end_datetime is the ending of the time range query. If we used start_datetime and end_datetime for the previous example as curl -X POST -H "Content-Type: application/wql" <ip>:1438/entity-history -d '(entity_key: "entity_tree_key", entity_id: "<some-Uuid>", start_datetime: Some("2021-03-02T05:00:19.816357937Z"), end_datetime: Some("2021-03-02T05:00:19.817189988Z"),)' we would have the following result:

Example request:

(
    entity_key: "entity_tree_key",
    entity_id: "dc3069e7-2a22-4fbc-ae05-f78a807239c0",
    start_datetime: Some("2021-03-02T05:00:19.816357937Z"),
    end_datetime: Some("2021-03-02T05:00:19.817189988Z"),
)

Example response:


#![allow(unused)]
fn main() {
{
"2021-03-02T05:00:19.816357939Z": {
    "b": Float(12.3),
    "a": Integer(123),
    "tx_time": DateTime("2021-03-02T05:00:19.816357939Z"),
},
"2021-03-02T05:00:19.817189987Z": {
    "b": Float(12.3),
    "c": Boolean(true),
    "a": Integer(34),
    "tx_time": DateTime("2021-03-02T05:00:19.817189987Z"),
},}
}

Relation Algebra Functions

WooriDB has some support to relation algebra functions as well as auxiliary functions to relation algebra. They are:

Functions GROUP BY, ORDER BY, DEDUP LIMIT, OFFSET, COUNT are only supported by the following select queries:

  • SELECT */#{...} FROM tree_key_name
  • SELECT */#{...} FROM tree_key_name WHERE {...}
  • SELECT */#{...} FROM tree_key_name IDS IN #{...}

Functions UNION,INTERSECT,DIFFERENCE are only supported by the following select queries:

  • SELECT */#{...} FROM tree_key_name ID some-uuid
  • SELECT */#{...} FROM tree_key_name ID some-uuid WHEN AT some-date

GROUP BY

This groups the responses of the select query in the following type HashMap<String, BTreeMap<Uuid, HashMap<String, Types>>> (for group by associated with order by the type is HashMap<String, Vec<(Uuid, HashMap<String, Types>)>>). So the query SELECT * FROM key GROUP BY c for the following 6 entities:


#![allow(unused)]
fn main() {
{a: 123, b: 12.3,}
{a: 235, b: 12.3, c: 'c',}
{a: 235, b: 12.3, c: 'd',}
{a: 25, b: 12.3, c: 'c',}
{a: 475, b: 12.3, c: 'd',}
{a: 295, b: 12.3, c: 'r',}
}

Will produce the response:


#![allow(unused)]
fn main() {
{
    "Char(\'r\')": {<Uuid6>: {a: 295, b: 12.3, c: 'r',},} ,
    "Char(\'c\')": {<Uuid2>: {a: 235, b: 12.3, c: 'c',}, <Uuid4>: {a: 25, b: 12.3, c: 'c',},},
    "Char(\'d\')": {<Uuid3>: {a: 235, b: 12.3, c: 'd',}, <Uuid5>: {a: 475, b: 12.3, c: 'd',},},
    "Nil": {<Uuid1>: {a: 123, b: 12.3,},},
}
}
  • Note that the Hash of the type is a String containing a wql::Types.

ORDER BY

This functions orders the response of the query by the value of a key. The key-value can be ordered by :asc or :desc. So the query SELECT * FROM key ORDER BY a :asc will return a Vec<(Uuid, HashMap<String, Types>)> for the following 6 entities:


#![allow(unused)]
fn main() {
{a: 123, b: 12.3,}
{a: 235, b: 12.3, c: 'c',}
{a: 235, b: 12.3, c: 'd',}
{a: 25, b: 12.3, c: 'c',}
{a: 475, b: 12.3, c: 'd',}
{a: 295, b: 12.3, c: 'r',}
}

Will produce the response:


#![allow(unused)]
fn main() {
[
    (<Uuid4>, {a: 25, b: 12.3, c: 'c',}),
    (<Uuid1>, {a: 123, b: 12.3,}),
    (<Uuid2>, {a: 235, b: 12.3, c: 'c',}),
    (<Uuid3>, {a: 235, b: 12.3, c: 'd',}),
    (<Uuid6>, {a: 295, b: 12.3, c: 'r',}),
    (<Uuid5>, {a: 475, b: 12.3, c: 'd',}),
]
}

DEDUP

This functios is capable of removing duplicates key-values in responses. By using SELECT * FROM key DEDUP a for the following 6 entities:


#![allow(unused)]
fn main() {
{a: 123, b: 12.3,}
{a: 235, b: 12.3, c: 'c',}
{a: 235, b: 12.3, c: 'd',}
{a: 25, b: 12.3, c: 'c',}
{a: 475, b: 12.3, c: 'd',}
{a: 295, b: 12.3, c: 'r',}
}

We would have as a result something like:


#![allow(unused)]
fn main() {
{
    <Uuid1>: {a: 123, b: 12.3,},
    <Uuid2>: {a: 235, b: 12.3, c: 'c',},
    <Uuid3>: {a: 25, b: 12.3, c: 'c',},
    <Uuid4>: {a: 475, b: 12.3, c: 'd',},
    <Uuid5>: {a: 295, b: 12.3, c: 'r',},
}
}

Also it is possible to eliminate Nil and Types::Nil values for a DEDUP key. This is done by calling the function NIL() (It needs to be UPPERCASE) with the key used for the DEDUP. So for the previous data the response for the query SELECT * FROM key DEDUP NIL(c) would be:


#![allow(unused)]
fn main() {
{
    <Uuid2>: {a: 235, b: 12.3, c: 'c',},
    <Uuid4>: {a: 475, b: 12.3, c: 'd',},
    <Uuid5>: {a: 295, b: 12.3, c: 'r',},
}
}

LIMIT and OFFSET

The functions LIMIT and OFFSET expect a positive integer as argument, this means that if you define LIMIT 10 and OFFSET 5 you will skip the first 5 elements from the tree and take only the next 10 elements. LIMIT and OFFSET are also appended to the end of the select query such that SELECT * FROM key LIMIT 100 OFFSET 300.

COUNT

This function is appended to the end of a select query and it will return the count for entities found by that select. So a query like SELECT * FROM key WHERE {...} COUNT will return the responses for select where as well as the count of entities found in that select. The aswer will be in the following structure:


#![allow(unused)]
fn main() {
(
    response: { "map containing the response for the query" },
    count: usize,
)
}

UNION

This unites two entities into one entity. There are two strategies for this relation the first one is UNION KEY which will unify 2 entities adding to the first one the missing values from the second, then there is UNION KEY-VALUE that will unite the keys and values from the second and if the value is the different for each key a duplicated sign will be added. The following examples will help you understand considering the following entities:


#![allow(unused)]
fn main() {
{
    "ent1": {<UUID1>: {a: 123, b: 234, c: true,}}
    "ent2": {<UUID2>: {a: 123, b: 432, d: false,}}
}
}

KEY

UNION KEY Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2. Note the | as query separator.

The entity to be returned will be:


#![allow(unused)]
fn main() {
{"a": 123, "b": 234, "c": true, "d": false}
}

KEY-VALUE

UNION KEY-VALUE Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2. Note the | as query separator.

The entity to be returned will be:


#![allow(unused)]
fn main() {
{"a": 123, "b": 234, "b:duplicated": 432, "c": true, "d": false}
}

INTERSECT

This intersects two entities into one entity. There are two strategies for this relation the first one is INTERSECT KEY which will return only the key value pairs from the first entity that have a corresponding key in the second entity, then there is INTERSECT KEY-VALUE which will return only the key value pairs from the first entity that have a corresponding key value pair in the second entity. The following examples will help you understand considering the following entities:


#![allow(unused)]
fn main() {
{
    "ent1": {<UUID1>: {a: 123, b: 234, c: true,}}
    "ent2": {<UUID2>: {a: 123, b: 432, d: false,}}
}
}

KEY

INTERSECT KEY Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2. Note the | as query separator.

The entity to be returned will be:


#![allow(unused)]
fn main() {
{"a": 123, "b": 234}
}

KEY-VALUE

INTERSECT KEY-VALUE Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2. Note the | as query separator.

The entity to be returned will be:


#![allow(unused)]
fn main() {
{"a": 123}
}

DIFFERENCE

This intersects two entities into one entity. There are two strategies for this relation the first one is DIFFERENCE KEY which will return only the key value pairs from the first entity that do not have a corresponding key in the second entity, then there is DIFFERENCE KEY-VALUE which will return only the key value pairs from the first entity that do not have a corresponding key value pair in the second entity. The following examples will help you understand considering the following entities:


#![allow(unused)]
fn main() {
{
    "ent1": {<UUID1>: {a: 123, b: 234, c: true,}}
    "ent2": {<UUID2>: {a: 123, b: 432, d: false,}}
}
}

KEY

DIFFERENCE KEY Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2. Note the | as query separator.

The entity to be returned will be:


#![allow(unused)]
fn main() {
{"c": true,}
}

KEY-VALUE

DIFFERENCE KEY-VALUE Select * FROM ent1 ID uuid1 | Select * FROM ent2 ID uuid2. Note the | as query separator.

The entity to be returned will be:


#![allow(unused)]
fn main() {
{"c": true, "b": 234}
}

JOIN

Join operation is similar to UNION. However, it does this by comparing keys equallity in two different entities, so if we select all elements in entity_a and all elements in entity_b and we join them in key a for entity_a and key b for entity_b whenever entity_a:a == entity_b:b a new entity will be created and appended to the resulting vector. Also all duplciated keys from entity_b will be appended by :entity_b, so a duplicated key dup_key will be dup_key:entity_b.

For the query JOIN (entity_AA:c, entity_BB:o) Select * FROM entity_AA order by c :asc | Select #{{g, f, o, b,}} FROM entity_BB we are checking equallity on entity_AA:c == entity_BB:o and the two queries two to be joined are Select * FROM entity_AA order by c :asc and Select #{{g, f, o, b,}} FROM entity_BB joined by a |.


#![allow(unused)]
fn main() {
{
    "entity_AA": {
        <UUID1>: {a: 123, b: 12.3,},
        <UUID3>: {a: 235, b: 17.3, c: 'c',},
        <UUID5>: {a: 476, b: 312.3, c: 'd',},
        <UUID7>: {a: 857, c: 'd',},}
    "entity_BB": { 
        <UUID2>: {a: 66, b: 66.3, c: 'r',},       
        <UUID4>: {g: 25, f: 12.3, a: 'c',},        
        <UUID6>: {g: 475, b: 12.3, f: 'h', o: 'd',},        
        <UUID8>: {g: 756, b: 142.3, f: 'h', o: 'c',},      
        <UUI10>: {g: 76, b: 12.3, f: 't', o: 'd',},     
        <UUID12>: {t: 295, b: 12.3, o: 'r',},    
        <UUID14>: {t: 295, f: 12.3, o: Nil,},
    }
}
}

The response for this join will be:

Notes

  • tx_time is the tx_time of entity_AA.
  • entities that don't have the field to be matched will be matched with other entities that don't have the field or it is nil.

#![allow(unused)]
fn main() {
[
    {
        "tx_time": DateTime(
            2021-04-01T18:04:30.029549132Z,
        ),
        "a": Integer(
            123,
        ),
        "b": Float(
            12.3,
        ),
        "g": Integer(
            25,
        ),
        "f": Float(
            12.3,
        ),
    },
    {
        "tx_time": DateTime(
            2021-04-01T18:04:30.029549132Z,
        ),
        "a": Integer(
            123,
        ),
        "b": Float(
            12.3,
        ),
        "b:entity_BB": Float(
            66.3,
        ),
    },
    {
        "tx_time": DateTime(
            2021-04-01T18:04:30.029549132Z,
        ),
        "a": Integer(
            123,
        ),
        "b": Float(
            12.3,
        ),
        "f": Float(
            12.3,
        ),
    },
    {
        "g": Integer(
            756,
        ),
        "f": Char(
            'h',
        ),
        "b": Float(
            17.3,
        ),
        "tx_time": DateTime(
            2021-04-01T18:04:30.030481424Z,
        ),
        "a": Integer(
            235,
        ),
        "b:entity_BB": Float(
            142.3,
        ),
        "c": Char(
            'c',
        ),
    },
    {
        "b:entity_BB": Float(
            12.3,
        ),
        "tx_time": DateTime(
            2021-04-01T18:04:30.031485453Z,
        ),
        "f": Char(
            't',
        ),
        "g": Integer(
            76,
        ),
        "a": Integer(
            476,
        ),
        "b": Float(
            312.3,
        ),
        "c": Char(
            'd',
        ),
    },
    {
        "f": Char(
            'h',
        ),
        "tx_time": DateTime(
            2021-04-01T18:04:30.031485453Z,
        ),
        "b:entity_BB": Float(
            12.3,
        ),
        "g": Integer(
            475,
        ),
        "a": Integer(
            476,
        ),
        "b": Float(
            312.3,
        ),
        "c": Char(
            'd',
        ),
    },
    {
        "b": Float(
            12.3,
        ),
        "c": Char(
            'd',
        ),
        "tx_time": DateTime(
            2021-04-01T18:04:30.032730665Z,
        ),
        "g": Integer(
            76,
        ),
        "a": Integer(
            857,
        ),
        "f": Char(
            't',
        ),
    },
    {
        "b": Float(
            12.3,
        ),
        "c": Char(
            'd',
        ),
        "tx_time": DateTime(
            2021-04-01T18:04:30.032730665Z,
        ),
        "g": Integer(
            475,
        ),
        "a": Integer(
            857,
        ),
        "f": Char(
            'h',
        ),
    },
]

}

Error Messages

Woori DB has a variety of Error messages, from WQL when parsing the transaction or the query to some internal database errors. Here are listed expected messages:

WooriDB Errors:

Example errors as response:


#![allow(unused)]
fn main() {
(
 error_type: "CheckNonEncryptedKeys",
 error_message: "CHECK can only verify encrypted keys: [\"ssn\"]",
)
}

Error types:

  • Io(io::Error) - Failed to read or write file.
  • QueryFormat(String) - WQL error.
  • EntityAlreadyCreated(<entity_name>) - Entity entity_name already created in the database.
  • EntityNotCreated(<entity_name>) - tx or query at entity tree does not contain key entity_name.
  • EntityNotCreatedWithUniqueness(<entity_name>) - this error only occurs if a failed write to the bank happened. Migration to fix data inconsistency may be needed.
  • Serialization(ron::Error) - Ron serialization error in wql context.
  • UuidNotCreatedForEntity(<entity_name>, Uuid) - If you try to UPDATE/DELETE/EVICT/SELECT an Uuid that entity_name does not contain.
  • FailedToParseState - Failed to read log file state/entity map.
  • FailedToParseRegistry - Failed to read log file registry information
  • UnknownCondition - MATCH UPDATE has an unknown condition (==, >=, <, etc).
  • FailedMatchCondition - MATCH UPDATE internal service error while processing conditions.
  • DuplicatedUnique(<entity_name>, <entity_map_key>, Types) - This means that Types is already present at entity_map_key for entity tree entity_name.
  • SelectBadRequest - Select operation done at /wql/tx. Select operations are done at /wql/query.
  • NonSelectQuery - Tx operation done at /wql/query. Tx operations are done at /wql/tx.
  • ActixMailbox(MailboxError) - Internal server error meaning that some actor mailbox crashed.
  • LockData - Failed to get a lock on Data
  • Ron(ron::Error) - Ron serialization error that occurred in user creation.
  • InvalidUuid(uuid::Error) - Uuid could not be parsed.
  • UpdateContentEncryptKeys(Vec<keys>) - UPDATE CONTENT cannot update encrypted keys.
  • CheckNonEncryptedKeys(Vec<keys>) - Cannot CHECK non encrypted keys.
  • DateTimeParse(chrono::ParseError) - failed to parse input DateTime<UTC>.
  • FailedToParseDate - failed to parse log file saved date.
  • AdminNotConfigured - Admin is not configured at release mode, please check auth section for more info.
  • AuthBadRequest - Authentication & Authorization error.
  • FailedToCreateUser - Failed to create new user.
  • Unknown - Unknown error.
  • KeyTxTimeNotAllowed - the key tx_time is not allowed in entities map for inserts and updates.

WQL Parsing

  • Query symbol error:

    • "Symbol {symbol_name} not implemented". Symbol name are the start of the query, like SELECT, CHECK, CREATE, ISNERT.
  • Keyword error:

    • CREATE: Keyword ENTITY is required for CREATE"
      • "Correct wording is ENCRYPT" for CREATE ENTITY ENCRYPT
      • "Correct wording is UNIQUES" for CREATE ENTITY UNIQUES.
    • UPDATE: "UPDATE type is required after entity. Keywords are SET or CONTENT"
      • "Keyword INTO is required for UPDATE"
      • "Keyword INTO is required for MATCH UPDATE"
      • "UPDATE keyword is required for MATCH UPDATE"
      • "MATCH UPDATE type is required after entity. Keyword is SET". Use SET as update type in MATCH.
    • SELECT/CHECK: "Keyword FROM is required for CHECK"
      • "Keyword FROM is required for SELECT"
      • "WHEN not allowed after IDS IN"
      • "Keyword AT is required after WHEN"
      • "Keyword IN is required after IDS to define a set of uuids"
      • "Keyword ID/IDS is required to set an uuid in SELECT".
    • EVICT/DELETE/INSERT: "Keyword FROM is required for DELETE"
      • "Keyword INTO is required for INSERT"
      • "Keyword FROM is required to EVICT an UUID".
  • Argument format error:

    • "Arguments set should start with #{ and end with }"
    • "Entity map should start with { and end with }" occurs mostly with INSERT and UPDATE.
    • "Field ID must be a UUID v4"
    • SELECT: "Encrypted arguments cannot be set to UNIQUE"
      • "SELECT expression should be followed by * for ALL keys or #{key_names...} for some keys" and "SELECT arguments set should start with #{ and end with }"
      • "Uuids in IDS IN are reuired to be inside a #{ and }"
      • "START date and END date should be the same date."
      • "WHERE clauses must be contained inside {...}"
  • Required content:

    • "MATCH requires ALL or ANY symbols". It is necessary to include ANY or ALL conditions after MATCH keyword.
    • "Entity UUID is required for DELETE"
    • Entity name:
      • "Entity name is required after FROM"
      • "Entity name is required for SELECT"
      • "Entity name is required after INTO"
      • "Entity name is required for UPDATE"
      • "Entity name is required for EVICT"
      • "Entity name is required for MATCH UPDATE"
  • Parse error:

    • "Couldn't create uuid from {some-text}. Error: {Uuid::Error}"
    • "Couldn't parse UPDATE query". SET or CONTENT were not found after UPDATE
    • "Couldn't parse MATCH UPDATE query"
    • "Entity name cannot contain -". Entity name should contain only alphanumeric and _ chars.
    • "Key must be an alphanumeric value" or contain _.
    • "Hash cannot be hashed" and "Nil cannot be hashed". Types::Hash and Types::Nil cannot be hashed.
    • "Not able to parse match argument". Match condition has wrong argument type.
    • "Unidentified Match Condition". Could not identify match condition.
    • "Entity HashMap could not be created"
    • "Value Type could not be created from {some value}". Could not create Types from some value.
    • "WHERE clause cannot be empty"

using Json

WoorDB is able to support Json requests and responses by using the flag features. To execute WooriDB with json feature enable execute:

  • Make json, or;
  • cargo run --manifest-path woori-db/Cargo.toml --release --features json
  • Remember that Json doesn't have trailing commas while ron has them.

Example request:

For /auth/createUser.

{
    "admin_id": "your_admin", 
    "admin_password": "your_password", 
    "user_info": {
        "user_password": "my_password",
        "role": ["User"]
    }
}

Example response:

For a SELECT * FROM entity.

{
 "38d52c95-b6f6-403a-a0b2-447b8fa15784": {
  "a": Integer(123),
  "tx_time": DateTime("2021-03-24T23:56:45.179008791Z"),
 },
}