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 @@
-
+
-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 @@
+
+
+
+
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]