diff --git a/Cargo.toml b/Cargo.toml index b60f0105..c04cbcaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver= "2" -members = ["lykiadb-server", "lykiadb-shell", "lykiadb-lang", "lykiadb-playground", "lykiadb-connect"] +members = ["lykiadb-server", "lykiadb-shell", "lykiadb-lang", "lykiadb-connect"] edition = "2021" [profile.release] diff --git a/README.md b/README.md index f7c8f3c0..03f50967 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@

- LykiaDB logo + LykiaDB logo

-Lykia is a toy multi-model database basically written for educational purposes. +Lykia is a document database management system built for educational purposes. The famous book, Crafting Interpreters, was the main source of inspiration for the project. It turned into a database, though. ## Overview -- Written in Rust -- A weird scripting and query language, combination of JavaScript and SQL. Built based on the language "Lox" which is explained in the famous book, Crafting Interpreters. +- 100% safe Rust, #BlazinglyFast +- A weird scripting and query language, combination of JavaScript and SQL. The language is a separate module thus can be used without the database. - A subset of JSON data types in both scripting language itself and storage - In-disk and in-memory storage - ACID compliance @@ -29,19 +29,20 @@ Lykia is a toy multi-model database basically written for educational purposes. ## Roadmap -- [x] Core scripting language +- [x] Core scripting language + DML/DDL SQL - [x] A minimal standard library -- [x] DML/DDL SQL -- [x] Event loop, client-server communication -- [x] Minimal playground app +- [x] Runtime +- [x] Playground app - [ ] Query binding and planning (in progress) -- [ ] Bitcask storage engine +- [ ] LSM storage engine (based on [mini-lsm](https://github.com/lykia-rs/mini-lsm)) - [ ] MVCC for transaction management (based on [mini-lsm](https://github.com/lykia-rs/mini-lsm)) - [ ] Plan optimization ------------------------------------------ -- [ ] LSM storage engine (based on [mini-lsm](https://github.com/lykia-rs/mini-lsm)) + +## More ambitious roadmap: + - [ ] Basic replication with Raft + ## Getting Started To use Lykia, you can download the latest release from the GitHub releases page. @@ -55,12 +56,8 @@ Run the shell: ```shell $ cargo run --release --bin lykiadb-shell lykiadb-shell/examples/fib.ly ``` -Run the playground: -```shell -$ cd lykiadb-playground -$ pnpm dev -``` +For playground, please visit [lykia-rs/playground](https://github.com/lykia-rs/playground) ## License Lykia is licensed under the Apache License, Version 2.0. See LICENSE for the full license text. \ No newline at end of file diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 00000000..b0706f37 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,78 @@ + + + +lykiaDB diff --git a/lykiadb-server/src/engine/interpreter.rs b/lykiadb-server/src/engine/interpreter.rs index f11dec3c..0d7b6964 100644 --- a/lykiadb-server/src/engine/interpreter.rs +++ b/lykiadb-server/src/engine/interpreter.rs @@ -15,8 +15,9 @@ use super::error::ExecutionError; use crate::plan::planner::Planner; use crate::util::{alloc_shared, Shared}; +use crate::value::callable::{Callable, CallableKind, Function, Stateful}; use crate::value::environment::{EnvId, Environment}; -use crate::value::types::{eval_binary, Function, Stateful, RV}; +use crate::value::types::{eval_binary, RV}; use std::sync::Arc; use std::vec; @@ -410,12 +411,12 @@ impl VisitorMut for Interpreter { } => { let eval = self.visit_expr(callee)?; - if let RV::Callable(arity, callable) = eval { - if arity.is_some() && arity.unwrap() != args.len() { + if let RV::Callable(callable) = eval { + if callable.arity.is_some() && callable.arity.unwrap() != args.len() { return Err(HaltReason::Error( InterpretError::ArityMismatch { span: *span, - expected: arity.unwrap(), + expected: callable.arity.unwrap(), found: args.len(), } .into(), @@ -465,7 +466,7 @@ impl VisitorMut for Interpreter { closure: self.env, }; - let callable = RV::Callable(Some(parameters.len()), fun.into()); + let callable = RV::Callable(Callable::new(Some(parameters.len()), CallableKind::Generic, fun.into())); if name.is_some() { // TODO(vck): Callable shouldn't be cloned here diff --git a/lykiadb-server/src/engine/stdlib/mod.rs b/lykiadb-server/src/engine/stdlib/mod.rs index b0e5b9c4..908b7323 100644 --- a/lykiadb-server/src/engine/stdlib/mod.rs +++ b/lykiadb-server/src/engine/stdlib/mod.rs @@ -1,10 +1,8 @@ -use std::sync::Arc; - use rustc_hash::FxHashMap; use crate::{ util::{alloc_shared, Shared}, - value::types::{Function, RV}, + value::{callable::{Callable, CallableKind, Function}, types::RV}, }; use self::{ @@ -30,24 +28,28 @@ pub fn stdlib(out: Option>) -> FxHashMap { benchmark_namespace.insert( "fib".to_owned(), - RV::Callable(Some(1), Arc::new(Function::Lambda { function: nt_fib })), + RV::Callable(Callable::new(Some(1), CallableKind::Generic, Function::Lambda { function: nt_fib })), ); json_namespace.insert( "stringify".to_owned(), RV::Callable( - Some(1), - Arc::new(Function::Lambda { - function: nt_json_encode, - }), + Callable::new( + Some(1), + CallableKind::Generic, + Function::Lambda { + function: nt_json_encode, + } + ) ), ); json_namespace.insert( "parse".to_owned(), - RV::Callable( + RV::Callable(Callable::new( Some(1), - Arc::new(Function::Lambda { + CallableKind::Generic, + Function::Lambda { function: nt_json_decode, }), ), @@ -55,7 +57,11 @@ pub fn stdlib(out: Option>) -> FxHashMap { time_namespace.insert( "clock".to_owned(), - RV::Callable(Some(0), Arc::new(Function::Lambda { function: nt_clock })), + RV::Callable(Callable::new( + Some(0), + CallableKind::Generic, + Function::Lambda { function: nt_clock } + )), ); if out.is_some() { @@ -63,7 +69,7 @@ pub fn stdlib(out: Option>) -> FxHashMap { test_namespace.insert( "out".to_owned(), - RV::Callable(None, Arc::new(Function::Stateful(out.unwrap().clone()))), + RV::Callable(Callable::new(None, CallableKind::Generic, Function::Stateful(out.unwrap().clone()))), ); std.insert( @@ -80,7 +86,7 @@ pub fn stdlib(out: Option>) -> FxHashMap { std.insert("Time".to_owned(), RV::Object(alloc_shared(time_namespace))); std.insert( "print".to_owned(), - RV::Callable(None, Arc::new(Function::Lambda { function: nt_print })), + RV::Callable(Callable::new(None, CallableKind::Generic, Function::Lambda { function: nt_print })), ); std diff --git a/lykiadb-server/src/value/callable.rs b/lykiadb-server/src/value/callable.rs new file mode 100644 index 00000000..65137f31 --- /dev/null +++ b/lykiadb-server/src/value/callable.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; +use std::fmt::{Debug, Display, Formatter}; +use lykiadb_lang::ast::stmt::Stmt; +use crate::{engine::interpreter::{HaltReason, Interpreter}, util::Shared}; +use super::environment::EnvId; +use super::types::RV; + +#[derive(Debug, Clone)] +pub enum CallableKind { + Generic, + Aggregator, +} + +#[derive(Clone, Debug)] +pub struct Callable { + pub arity: Option, + pub kind: CallableKind, + pub function: Arc, +} + +impl Callable { + pub fn new(arity: Option, call_type: CallableKind, function: Function) -> Self { + Callable { + arity, + kind: call_type, + function: Arc::new(function), + } + } + + pub fn call(&self, interpreter: &mut Interpreter, arguments: &[RV]) -> Result { + match self.function.as_ref() { + Function::Stateful(stateful) => stateful.write().unwrap().call(interpreter, arguments), + Function::Lambda { function } => function(interpreter, arguments), + Function::UserDefined { + name: _, + parameters, + closure, + body, + } => interpreter.user_fn_call(body, *closure, parameters, arguments), + } + } +} + +pub trait Stateful { + fn call(&mut self, interpreter: &mut Interpreter, rv: &[RV]) -> Result; +} + +#[derive(Clone)] +pub enum Function { + Lambda { + function: fn(&mut Interpreter, &[RV]) -> Result, + }, + Stateful(Shared), + UserDefined { + name: String, + parameters: Vec, + closure: EnvId, + body: Arc>, + }, +} + +impl Function { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Function::Stateful(_) | Function::Lambda { function: _ } => write!(f, ""), + Function::UserDefined { + name, + parameters: _, + closure: _, + body: _, + } => write!(f, "{}", name), + } + } +} + +impl Debug for Function { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.fmt(f) + } +} + +impl Display for Function { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.fmt(f) + } +} diff --git a/lykiadb-server/src/value/mod.rs b/lykiadb-server/src/value/mod.rs index 33c5e78d..bf7d3a44 100644 --- a/lykiadb-server/src/value/mod.rs +++ b/lykiadb-server/src/value/mod.rs @@ -1,2 +1,3 @@ pub mod environment; pub mod types; +pub mod callable; \ No newline at end of file diff --git a/lykiadb-server/src/value/types.rs b/lykiadb-server/src/value/types.rs index f03e4aa9..ffe5ce38 100644 --- a/lykiadb-server/src/value/types.rs +++ b/lykiadb-server/src/value/types.rs @@ -1,16 +1,15 @@ -use super::environment::EnvId; -use crate::engine::interpreter::{HaltReason, Interpreter}; -use crate::util::{alloc_shared, Shared}; use lykiadb_lang::ast::expr::Operation; -use lykiadb_lang::ast::stmt::Stmt; use rustc_hash::FxHashMap; use serde::ser::{SerializeMap, SerializeSeq}; use serde::{Deserialize, Serialize}; -use std::borrow::Borrow; -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::Debug; use std::ops; use std::sync::{Arc, RwLock}; +use crate::util::{alloc_shared, Shared}; + +use super::callable::Callable; + #[derive(Debug, Clone)] pub enum RV { Str(Arc), @@ -18,69 +17,12 @@ pub enum RV { Bool(bool), Object(Shared>), Array(Shared>), - Callable(Option, Arc), + Callable(Callable), Undefined, NaN, Null, } -pub trait Stateful { - fn call(&mut self, interpreter: &mut Interpreter, rv: &[RV]) -> Result; -} - -#[derive(Clone)] -pub enum Function { - Lambda { - function: fn(&mut Interpreter, &[RV]) -> Result, - }, - Stateful(Shared), - UserDefined { - name: String, - parameters: Vec, - closure: EnvId, - body: Arc>, - }, -} - -impl Function { - pub fn call(&self, interpreter: &mut Interpreter, arguments: &[RV]) -> Result { - match self { - Function::Stateful(stateful) => stateful.write().unwrap().call(interpreter, arguments), - Function::Lambda { function } => function(interpreter, arguments), - Function::UserDefined { - name: _, - parameters, - closure, - body, - } => interpreter.user_fn_call(body, *closure, parameters, arguments), - } - } - - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Function::Stateful(_) | Function::Lambda { function: _ } => write!(f, ""), - Function::UserDefined { - name, - parameters: _, - closure: _, - body: _, - } => write!(f, "{}", name), - } - } -} - -impl Debug for Function { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.fmt(f) - } -} - -impl Display for Function { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.fmt(f) - } -} - impl Serialize for RV { fn serialize(&self, serializer: S) -> Result where @@ -93,10 +35,9 @@ impl Serialize for RV { RV::Undefined => serializer.serialize_none(), RV::NaN => serializer.serialize_none(), RV::Null => serializer.serialize_none(), - RV::Callable(_, _) => serializer.serialize_none(), RV::Array(arr) => { let mut seq = serializer.serialize_seq(None).unwrap(); - let arr = (arr.borrow() as &RwLock>).read().unwrap(); + let arr = (arr as &RwLock>).read().unwrap(); for item in arr.iter() { seq.serialize_element(&item)?; } @@ -104,14 +45,15 @@ impl Serialize for RV { } RV::Object(obj) => { let mut map = serializer.serialize_map(None).unwrap(); - let arr = (obj.borrow() as &RwLock>) + let arr = (obj as &RwLock>) .read() .unwrap(); for (key, value) in arr.iter() { map.serialize_entry(key, value)?; } map.end() - } + }, + _ => serializer.serialize_none(), } } } @@ -216,9 +158,6 @@ impl PartialEq for RV { fn eq(&self, other: &Self) -> bool { match (self, other) { (RV::Array(_), RV::Array(_)) | (RV::Object(_), RV::Object(_)) => false, - // - (RV::Callable(_, _), RV::Callable(_, _)) => false, - // (RV::Null, RV::Null) => true, (RV::Undefined, RV::Undefined) => true, (RV::NaN, RV::NaN) => true, @@ -251,9 +190,6 @@ impl PartialOrd for RV { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (RV::Array(_), RV::Array(_)) | (RV::Object(_), RV::Object(_)) => None, - // - (RV::Callable(_, _), RV::Callable(_, _)) => None, - // (RV::Null, RV::Null) => Some(std::cmp::Ordering::Equal), (RV::Undefined, RV::Undefined) => Some(std::cmp::Ordering::Equal), (RV::NaN, RV::NaN) => Some(std::cmp::Ordering::Equal), @@ -414,7 +350,7 @@ mod test { use crate::{ util::alloc_shared, - value::types::{eval_binary, Function, RV}, + value::types::{eval_binary, RV}, }; #[test] @@ -436,13 +372,6 @@ mod test { assert!((RV::Str(Arc::new("foo".to_owned()))).as_bool()); assert!((RV::Array(alloc_shared(vec![]))).as_bool()); assert!((RV::Object(alloc_shared(FxHashMap::default()))).as_bool()); - assert!((RV::Callable( - Some(1), - Arc::new(Function::Lambda { - function: |_, _| Ok(RV::Undefined) - }) - )) - .as_bool()); } #[test]