Skip to content

Commit

Permalink
Merge pull request #38 from lykia-rs/feature/planner
Browse files Browse the repository at this point in the history
Planning additions
  • Loading branch information
can-keklik authored Sep 23, 2024
2 parents 74df77a + b2e2b14 commit 95dc0a8
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 44 deletions.
50 changes: 49 additions & 1 deletion lykiadb-lang/src/ast/expr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::{fmt::Display, sync::Arc};

use crate::{Identifier, Span, Spanned};

Expand Down Expand Up @@ -396,3 +396,51 @@ impl AstNode for Expr {
}
}
}

impl Display for Expr {
// A basic display function for Expr
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expr::Select { .. } => write!(f, "<SqlSelect>"),
Expr::Insert { .. } => write!(f, "<SqlInsert>"),
Expr::Update { .. } => write!(f, "<SqlUpdate>"),
Expr::Delete { .. } => write!(f, "<SqlDelete>"),
Expr::Variable { name, .. } => write!(f, "{}", name),
Expr::Grouping { expr, .. } => write!(f, "({})", expr),
Expr::Literal { value, .. } => write!(f, "{:?}", value),
Expr::Function { name, parameters, .. } => {
write!(f, "fn {}({})", name.as_ref().unwrap(), parameters.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "))
}
Expr::Between {
lower,
upper,
subject,
kind,
..
} => write!(
f,
"{} {} {} AND {}",
subject,
match kind {
RangeKind::Between => "BETWEEN",
RangeKind::NotBetween => "NOT BETWEEN",
},
lower,
upper
),
Expr::Binary { left, operation, right, .. } => {
write!(f, "({} {:?} {})", left, operation, right)
}
Expr::Unary { operation, expr, .. } => write!(f, "{:?}{}", operation, expr),
Expr::Assignment { dst, expr, .. } => write!(f, "{} = {}", dst, expr),
Expr::Logical { left, operation, right, .. } => {
write!(f, "{} {:?} {}", left, operation, right)
}
Expr::Call { callee, args, .. } => {
write!(f, "{}({})", callee, args.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "))
}
Expr::Get { object, name, .. } => write!(f, "{}.{}", object, name),
Expr::Set { object, name, value, .. } => write!(f, "{}.{} = {}", object, name, value),
}
}
}
8 changes: 7 additions & 1 deletion lykiadb-lang/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{fmt::{Display, Formatter, Result}, sync::Arc};

use ast::expr::Expr;
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -71,3 +71,9 @@ pub struct Identifier {
#[serde(skip)]
pub span: Span,
}

impl Display for Identifier {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}", self.name)
}
}
24 changes: 22 additions & 2 deletions lykiadb-lang/src/parser/program.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::str::FromStr;

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
ast::{expr::Expr, stmt::Stmt},
Locals,
ast::{expr::Expr, stmt::Stmt}, tokenizer::scanner::Scanner, Locals, Scopes
};

use super::{resolver::Resolver, ParseError, ParseResult, Parser};
#[derive(Serialize, Deserialize)]
pub struct Program {
root: Box<Stmt>,
Expand Down Expand Up @@ -47,3 +50,20 @@ impl Program {
serde_json::to_value(self.root.clone()).unwrap()
}
}

impl FromStr for Program {
type Err = ParseError;

fn from_str(s: &str) -> ParseResult<Program> {
let tokens = Scanner::scan(s).unwrap();
let parse_result = Parser::parse(&tokens);

if let Ok(mut program) = parse_result {
let mut resolver = Resolver::new(Scopes::default(), &program, None);
let (_, locals) = resolver.resolve().unwrap();
program.set_locals(locals);
return Ok(program);
}
panic!("Failed to parse program.");
}
}
39 changes: 11 additions & 28 deletions lykiadb-playground/app/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,12 @@ await init();

const EditorView = () => {
const [code, setCode] = React.useState(
`var $calc = {
add: function ($a, $b) {
return $a + $b;
},
sub: function ($a, $b) {
return $a - $b;
},
mul: function ($a, $b) {
return $a * $b;
},
div: function ($a, $b) {
return $a / $b;
},
};
print($calc.add(4, 5));
print($calc.sub(4, 5));
print($calc.mul(4, 5));
print($calc.div(4, 5));
`SELECT { 'name': user.name } from users;
`);

const [ast, setAst] = React.useState({});

const [sizes, setSizes] = React.useState([100, '30%', 'auto']);
const [sizes, setSizes] = React.useState(['50%', '50%']);

function updateCode(code: string) {
setCode(code)
Expand All @@ -49,25 +32,25 @@ print($calc.div(4, 5));
}

return (
<SplitPane sizes={sizes} onChange={setSizes} className={defaultFont.className}>
<Pane minSize={300} className="h-full p-1">
<div className="p-2 text-xs text-white bg-zinc-900 rounded-t-md">Script</div>
<SplitPane sizes={sizes} onChange={setSizes} className="border-y border-l border-zinc-500">
<Pane minSize={600} className="h-full">
<div className="p-4 text-md text-white bg-zinc-800 border-y border-l border-zinc-500"></div>
<div>
<CodeMirror
className={defaultFont.className}
value={code}
height="400px"
extensions={[lyql(tokenize)]}
onChange={(value: string) => updateCode(value)}
/>
</div>
<div className="p-2 text-white bg-zinc-900 rounded-b-md"></div>
<div className="p-2 text-white bg-zinc-800"></div>
</Pane>
<Pane minSize={600} className="h-full p-1">
<div className="p-2 text-xs text-white bg-zinc-900 rounded-t-md">Syntax tree</div>
<div className="overflow-y-auto h-full">
<Pane minSize={600} className="h-full border-l border-zinc-500">
<div className="p-2 text-md text-white bg-zinc-800 border-y border-r border-zinc-500">AST</div>
<div className="overflow-y-auto">
<div className="p-3 bg-white"><JsonView value={ast} /></div>
</div>
<div className="p-2 text-white bg-zinc-900 rounded-b-md"></div>
<div className="p-2 text-white bg-zinc-800"></div>
</Pane>
</SplitPane>
);
Expand Down
4 changes: 2 additions & 2 deletions lykiadb-playground/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--background-start-rgb: 56, 66, 96;
--background-end-rgb: 56, 66, 96;
}
}

Expand Down
10 changes: 5 additions & 5 deletions lykiadb-playground/app/lyqlSyntax.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
.cm-{
$font-size: 14px;
$font-size: 16px;
$font-family: 'JetBrains Mono', monospace;
&gutters, &editor {
font-size: $font-size;
font-family: $font-family;
background-color: #222226!important;
background-color: #1c2130!important;
border: none!important;
}

Expand All @@ -26,15 +26,15 @@
color:rgb(94, 145, 255)
}
&identifier {
color:#FB773C;
color:#bbdf06;
text-decoration: underline
}
&keyword{
color: rgb(74, 216, 255);
color: rgb(122, 255, 228);
font-weight: bold;
}
&sqlkeyword{
color: #EB3678;
color: #027e44;
font-weight: bold;
}
&symbol {
Expand Down
3 changes: 1 addition & 2 deletions lykiadb-playground/styles/fonts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Inter, JetBrains_Mono } from 'next/font/google'
import { JetBrains_Mono, Inter } from 'next/font/google'

const defaultFont = Inter({ weight: '400', subsets: ['latin'] })
const jetBrainsMono = JetBrains_Mono({ weight: '400', subsets: ['latin'] })


export { defaultFont, jetBrainsMono }
12 changes: 12 additions & 0 deletions lykiadb-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,15 @@ pub mod engine;
pub mod plan;
pub mod util;
pub mod value;

#[macro_export]
macro_rules! assert_plan {
($($name:ident: {$field:literal => $value:literal}),*) => {
$(
#[test]
fn $name() {
expect_plan($field, $value);
}
)*
};
}
45 changes: 45 additions & 0 deletions lykiadb-server/src/plan/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use lykiadb_lang::{
ast::{
expr::Expr,
Expand Down Expand Up @@ -84,3 +86,46 @@ pub enum Node {

Nothing,
}

impl Display for Plan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Plan::Select(node) => write!(f, "{}", node),
}
}
}

impl Node {
const TAB: &'static str = " ";
const NEWLINE: &'static str = "\n";

fn _fmt_recursive(&self, f: &mut std::fmt::Formatter<'_>, indent: usize) -> std::fmt::Result {
let indent_str = Self::TAB.repeat(indent);
match self {
Node::Filter { source, predicate } => {
write!(f, "{}- filter {}:{}", indent_str, predicate, Self::NEWLINE)?;
source._fmt_recursive(f, indent + 1)
}
Node::Scan { source, filter } => {
write!(f, "{}- scan [{} as {}]{}", indent_str, source.name, source.alias.as_ref().unwrap_or(&source.name), Self::NEWLINE)
}
Node::Join {
left,
join_type,
right,
constraint,
} => {
write!(f, "{}- join [{:?}, {}]:{}", indent_str, join_type, constraint.as_ref().unwrap(), Self::NEWLINE)?;
left._fmt_recursive(f, indent + 1)?;
right._fmt_recursive(f, indent + 1)
}
_ => "<NotImplementedYet>".fmt(f),
}
}
}

impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._fmt_recursive(f, 0)
}
}
26 changes: 23 additions & 3 deletions lykiadb-server/src/plan/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ impl Planner {
id: _,
} => {
let plan = Plan::Select(self.build_select(query)?);
println!("{}", serde_json::to_value(&plan).unwrap());
Ok(plan)
}
_ => panic!("Not implemented yet."),
Expand All @@ -40,11 +39,11 @@ impl Planner {
node = self.build_from(from)?;
}
// WHERE
if let Some(where_clause) = &query.core.r#where {
if let Some(predicate) = &query.core.r#where {
// TODO: Traverse expression
node = Node::Filter {
source: Box::new(node),
predicate: *where_clause.clone(),
predicate: *predicate.clone(),
}
}
// GROUP BY
Expand Down Expand Up @@ -95,3 +94,24 @@ impl Planner {
}
}
}


pub mod test_helpers {
use lykiadb_lang::{ast::stmt::Stmt, parser::program::Program};

use super::Planner;

pub fn expect_plan(query: &str, expected_plan: &str) {
let mut planner = Planner::new();
let program = query.parse::<Program>().unwrap();
match *program.get_root() {
Stmt::Program { body, .. } if matches!(body.get(0), Some(Stmt::Expression { .. })) => {
if let Some(Stmt::Expression { expr, .. }) = body.get(0) {
let generated_plan = planner.build(&expr).unwrap();
assert_eq!(expected_plan, generated_plan.to_string());
}
}
_ => panic!("Expected expression statement."),
}
}
}
19 changes: 19 additions & 0 deletions lykiadb-server/tests/planner/join.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use lykiadb_server::{assert_plan, plan::planner::test_helpers::expect_plan};


assert_plan! {
three_way_simple: {
"SELECT * FROM books b
INNER JOIN categories c ON b.category_id = c.id
INNER JOIN publishers AS p ON b.publisher_id = p.id
WHERE p.name = 'Springer';" =>

"- filter (p.name IsEqual Str(\"Springer\")):
- join [Inner, (b.publisher_id IsEqual p.id)]:
- join [Inner, (b.category_id IsEqual c.id)]:
- scan [books as b]
- scan [categories as c]
- scan [publishers as p]
"
}
}
1 change: 1 addition & 0 deletions lykiadb-server/tests/planner/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod join;
1 change: 1 addition & 0 deletions lykiadb-server/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![recursion_limit = "192"]

mod runtime;
mod planner;

0 comments on commit 95dc0a8

Please sign in to comment.