├── src ├── engine │ ├── ast │ │ ├── dcl │ │ │ ├── mod.rs │ │ │ └── README.md │ │ ├── commands │ │ │ ├── mod.rs │ │ │ └── connect.rs │ │ ├── dml │ │ │ ├── plan │ │ │ │ ├── delete │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── from.rs │ │ │ │ │ └── delete_plan.rs │ │ │ │ ├── update │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── from.rs │ │ │ │ │ └── update_plan.rs │ │ │ │ ├── mod.rs │ │ │ │ └── select │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── limit_offset.rs │ │ │ │ │ ├── scan.rs │ │ │ │ │ ├── filter.rs │ │ │ │ │ ├── no_from.rs │ │ │ │ │ ├── from.rs │ │ │ │ │ ├── subquery.rs │ │ │ │ │ └── join.rs │ │ │ ├── expressions │ │ │ │ ├── boolean.rs │ │ │ │ ├── string.rs │ │ │ │ ├── identifier.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── float.rs │ │ │ │ ├── parentheses.rs │ │ │ │ ├── call.rs │ │ │ │ ├── integer.rs │ │ │ │ ├── list.rs │ │ │ │ ├── subquery.rs │ │ │ │ ├── not_between.rs │ │ │ │ ├── unary.rs │ │ │ │ ├── binary.rs │ │ │ │ ├── operators.rs │ │ │ │ └── between.rs │ │ │ ├── mod.rs │ │ │ ├── README.md │ │ │ ├── parts │ │ │ │ ├── insert_values.rs │ │ │ │ ├── update_item.rs │ │ │ │ ├── where.rs │ │ │ │ ├── having.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── group_by.rs │ │ │ │ ├── target.rs │ │ │ │ ├── from.rs │ │ │ │ ├── join.rs │ │ │ │ ├── order_by.rs │ │ │ │ └── select_item.rs │ │ │ ├── insert.rs │ │ │ ├── delete.rs │ │ │ └── update.rs │ │ ├── types │ │ │ ├── README.md │ │ │ ├── table_options.rs │ │ │ ├── index.rs │ │ │ ├── unique_key.rs │ │ │ ├── foreign_key.rs │ │ │ ├── mod.rs │ │ │ ├── select_column.rs │ │ │ ├── table.rs │ │ │ ├── data_types.rs │ │ │ └── column.rs │ │ ├── ddl │ │ │ ├── README.md │ │ │ ├── mod.rs │ │ │ ├── drop_table.rs │ │ │ ├── drop_database.rs │ │ │ ├── create_database.rs │ │ │ ├── alter_database.rs │ │ │ ├── create_index.rs │ │ │ └── create_table.rs │ │ ├── other │ │ │ ├── mod.rs │ │ │ ├── show_databases.rs │ │ │ ├── show_tables.rs │ │ │ ├── use_database.rs │ │ │ └── desc_table.rs │ │ ├── tcl │ │ │ ├── mod.rs │ │ │ ├── commit.rs │ │ │ ├── rollback.rs │ │ │ └── begin_transaction.rs │ │ ├── README.md │ │ └── mod.rs │ ├── encoder │ │ ├── mod.rs │ │ └── schema_encoder.rs │ ├── wal │ │ ├── endec │ │ │ ├── implements │ │ │ │ ├── mod.rs │ │ │ │ └── bitcode.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── types.rs │ │ └── manager │ │ │ └── builder.rs │ ├── optimizer │ │ ├── predule.rs │ │ ├── README.md │ │ └── mod.rs │ ├── actions │ │ ├── mod.rs │ │ ├── dml │ │ │ ├── mod.rs │ │ │ └── scan.rs │ │ └── ddl │ │ │ ├── mod.rs │ │ │ ├── drop_database.rs │ │ │ ├── drop_table.rs │ │ │ ├── create_table.rs │ │ │ ├── create_database.rs │ │ │ └── alter_database.rs │ ├── schema │ │ ├── README.md │ │ ├── mod.rs │ │ ├── database.rs │ │ ├── table.rs │ │ └── row.rs │ ├── lexer │ │ ├── test │ │ │ ├── mod.rs │ │ │ └── comment.rs │ │ ├── predule.rs │ │ ├── mod.rs │ │ └── README.md │ ├── parser │ │ ├── implements │ │ │ ├── ddl │ │ │ │ ├── mod.rs │ │ │ │ └── top_level.rs │ │ │ ├── tcl │ │ │ │ ├── mod.rs │ │ │ │ ├── commit.rs │ │ │ │ ├── rollback.rs │ │ │ │ └── begin.rs │ │ │ ├── dml │ │ │ │ ├── mod.rs │ │ │ │ ├── delete.rs │ │ │ │ └── update.rs │ │ │ ├── other │ │ │ │ ├── mod.rs │ │ │ │ ├── desc.rs │ │ │ │ ├── use.rs │ │ │ │ ├── show.rs │ │ │ │ └── backslash_command.rs │ │ │ ├── mod.rs │ │ │ ├── debug.rs │ │ │ └── utils.rs │ │ ├── predule.rs │ │ ├── mod.rs │ │ ├── README.md │ │ ├── test │ │ │ ├── mod.rs │ │ │ ├── drop_table.rs │ │ │ ├── create_table.rs │ │ │ ├── drop_database.rs │ │ │ ├── create_database.rs │ │ │ └── tcl.rs │ │ ├── context.rs │ │ └── parser.rs │ ├── server │ │ ├── client.rs │ │ ├── shared_state.rs │ │ └── channel.rs │ └── types.rs ├── config │ ├── mod.rs │ └── launch_config.rs ├── common │ ├── mod.rs │ ├── command.rs │ └── fs.rs ├── errors │ ├── README.md │ ├── wal_errors.rs │ ├── type_error.rs │ ├── execute_error.rs │ ├── lexing_error.rs │ └── parsing_error.rs ├── command │ ├── README.md │ ├── init.rs │ ├── run.rs │ └── mod.rs ├── pgwire │ ├── protocol │ │ ├── message │ │ │ ├── mod.rs │ │ │ ├── client │ │ │ │ ├── mod.rs │ │ │ │ ├── types │ │ │ │ │ ├── describe.rs │ │ │ │ │ ├── execute.rs │ │ │ │ │ ├── bind_format.rs │ │ │ │ │ ├── bind.rs │ │ │ │ │ ├── startup.rs │ │ │ │ │ ├── parse.rs │ │ │ │ │ └── mod.rs │ │ │ │ └── client_message.rs │ │ │ └── backend │ │ │ │ ├── mod.rs │ │ │ │ ├── backend_message.rs │ │ │ │ └── types │ │ │ │ ├── field_description.rs │ │ │ │ ├── no_data.rs │ │ │ │ ├── bind_complete.rs │ │ │ │ ├── parse_complete.rs │ │ │ │ ├── empty_query_response.rs │ │ │ │ ├── ok.rs │ │ │ │ ├── ready_for_query.rs │ │ │ │ ├── parameter_description.rs │ │ │ │ ├── command_complete.rs │ │ │ │ ├── parameter_status.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── row_description.rs │ │ │ │ └── error_response.rs │ │ ├── extension │ │ │ ├── mod.rs │ │ │ ├── data_row_batch.rs │ │ │ └── data_row_writer.rs │ │ ├── severity.rs │ │ ├── constants.rs │ │ ├── protocol_error.rs │ │ ├── mod.rs │ │ ├── format_code.rs │ │ ├── sql_state.rs │ │ └── data_types.rs │ ├── predule.rs │ ├── connection │ │ ├── error │ │ │ ├── mod.rs │ │ │ └── connection_error.rs │ │ ├── state.rs │ │ ├── engine_func.rs │ │ ├── bound_portal.rs │ │ ├── prepared_statement.rs │ │ └── mod.rs │ ├── mod.rs │ ├── README.md │ └── engine │ │ ├── mod.rs │ │ ├── portal.rs │ │ ├── engine.rs │ │ └── rrdb.rs ├── utils │ ├── predule.rs │ ├── mod.rs │ ├── macos.rs │ └── collection.rs ├── test.rs ├── main.rs ├── constants.rs └── errors.rs ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── review_stats.yml │ ├── pr_test.yml │ ├── release.yml │ └── coverage.yml ├── .gitignore ├── LICENSE ├── Cargo.toml └── README.md /src/engine/ast/dcl/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod launch_config; 2 | -------------------------------------------------------------------------------- /src/engine/ast/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod connect; 2 | -------------------------------------------------------------------------------- /src/engine/encoder/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod schema_encoder; 2 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod fs; 3 | -------------------------------------------------------------------------------- /src/errors/README.md: -------------------------------------------------------------------------------- 1 | ## error 2 | 3 | - 오류 타입 정의입니다. 4 | -------------------------------------------------------------------------------- /src/engine/wal/endec/implements/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bitcode; 2 | -------------------------------------------------------------------------------- /src/engine/optimizer/predule.rs: -------------------------------------------------------------------------------- 1 | pub use super::optimizer::*; 2 | -------------------------------------------------------------------------------- /src/command/README.md: -------------------------------------------------------------------------------- 1 | ## Command 2 | 3 | command line 명령어에 대한 정의입니다. 4 | -------------------------------------------------------------------------------- /src/engine/actions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ddl; 2 | pub mod dml; 3 | pub mod etc; 4 | -------------------------------------------------------------------------------- /src/engine/schema/README.md: -------------------------------------------------------------------------------- 1 | ## Config 2 | 3 | 데이터베이스 내부 설정파일에 대한 정의입니다. 4 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod backend; 2 | pub mod client; 3 | -------------------------------------------------------------------------------- /src/utils/predule.rs: -------------------------------------------------------------------------------- 1 | pub use super::float::*; 2 | pub use super::macos::*; 3 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/delete/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod delete_plan; 2 | pub mod from; 3 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/update/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod from; 2 | pub mod update_plan; 3 | -------------------------------------------------------------------------------- /src/engine/ast/types/README.md: -------------------------------------------------------------------------------- 1 | ## 공용 타입 2 | 3 | - AST에 대한 공용 타입 정의가 존재합니다. 4 | -------------------------------------------------------------------------------- /src/engine/lexer/test/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod comment; 2 | pub(crate) mod select; 3 | -------------------------------------------------------------------------------- /src/engine/schema/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod database; 2 | pub mod row; 3 | pub mod table; 4 | -------------------------------------------------------------------------------- /src/engine/wal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod endec; 2 | pub mod manager; 3 | pub mod types; 4 | -------------------------------------------------------------------------------- /src/pgwire/predule.rs: -------------------------------------------------------------------------------- 1 | pub use super::connection::*; 2 | pub use super::engine::*; 3 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod delete; 2 | pub mod select; 3 | pub mod update; 4 | -------------------------------------------------------------------------------- /src/engine/ast/dcl/README.md: -------------------------------------------------------------------------------- 1 | ## DCL (Data Control Language) 2 | 3 | - DCL에 대한 AST 정의 모듈입니다. 4 | -------------------------------------------------------------------------------- /src/pgwire/connection/error/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod connection_error; 2 | pub use connection_error::*; 3 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/README.md: -------------------------------------------------------------------------------- 1 | ## DDL (Data Definition Language) 2 | 3 | - DDL에 대한 AST 정의 모듈입니다. 4 | -------------------------------------------------------------------------------- /src/engine/parser/implements/ddl/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod database; 2 | pub mod table; 3 | pub mod top_level; 4 | -------------------------------------------------------------------------------- /src/engine/parser/implements/tcl/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod begin; 2 | pub mod commit; 3 | pub mod rollback; 4 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod collection; 2 | pub mod float; 3 | pub mod macos; 4 | pub mod predule; 5 | -------------------------------------------------------------------------------- /src/pgwire/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod connection; 2 | pub mod engine; 3 | pub mod predule; 4 | pub mod protocol; 5 | -------------------------------------------------------------------------------- /src/engine/ast/types/table_options.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct TableOptions {} 3 | -------------------------------------------------------------------------------- /src/engine/optimizer/README.md: -------------------------------------------------------------------------------- 1 | ## Optimizer 2 | 3 | 쿼리 옵티마이저입니다. 4 | 쿼리 AST를 executor에 넘기기 전에 효율적으로 최적화합니다. 5 | -------------------------------------------------------------------------------- /src/engine/optimizer/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::module_inception)] 2 | pub mod optimizer; 3 | pub mod predule; 4 | -------------------------------------------------------------------------------- /src/engine/parser/predule.rs: -------------------------------------------------------------------------------- 1 | pub use super::context::*; 2 | pub use super::implements::*; 3 | pub use super::parser::*; 4 | -------------------------------------------------------------------------------- /src/pgwire/connection/state.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum ConnectionState { 3 | Startup, 4 | Idle, 5 | } 6 | -------------------------------------------------------------------------------- /src/engine/actions/dml/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod delete; 2 | pub mod insert; 3 | pub mod scan; 4 | pub mod select; 5 | pub mod update; 6 | -------------------------------------------------------------------------------- /src/engine/lexer/predule.rs: -------------------------------------------------------------------------------- 1 | pub use super::operator_token::*; 2 | pub use super::tokenizer::*; 3 | pub use super::tokens::*; 4 | -------------------------------------------------------------------------------- /src/engine/ast/other/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod desc_table; 2 | pub mod show_databases; 3 | pub mod show_tables; 4 | pub mod use_database; 5 | -------------------------------------------------------------------------------- /src/engine/parser/implements/dml/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod delete; 2 | pub mod expression; 3 | pub mod insert; 4 | pub mod select; 5 | pub mod update; 6 | -------------------------------------------------------------------------------- /src/engine/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod operator_token; 2 | pub mod predule; 3 | pub mod tokenizer; 4 | pub mod tokens; 5 | 6 | pub(crate) mod test; 7 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client_message; 2 | pub use client_message::*; 3 | 4 | pub mod types; 5 | pub use types::*; 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | resolves: #issue 2 | 3 | ## 설명 4 | 간단하게 해당 PR이 어떤 내용인지 작성합니다. 이슈를 통해서 충분히 설명이 가능 하다고 생각하면 resolves만 작성하시면 됩 5 | 니다. 6 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/boolean.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct BooleanExpression { 3 | pub value: bool, 4 | } 5 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/string.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct StringExpression { 3 | pub value: String, 4 | } 5 | -------------------------------------------------------------------------------- /src/engine/parser/implements/other/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod backslash_command; 2 | pub mod desc; 3 | pub mod show; 4 | #[path = "./use.rs"] 5 | pub mod use_; 6 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod backend_message; 2 | pub use backend_message::*; 3 | 4 | pub mod types; 5 | pub use types::*; 6 | -------------------------------------------------------------------------------- /src/engine/ast/commands/connect.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct ConnectCommand { 3 | pub database_name: Option, 4 | } 5 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/identifier.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct IdentifierExpression { 3 | pub idendifier: String, 4 | } 5 | -------------------------------------------------------------------------------- /src/engine/parser/implements/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod ddl; 3 | pub mod debug; 4 | pub mod dml; 5 | pub mod other; 6 | pub mod tcl; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /src/pgwire/protocol/extension/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data_row_batch; 2 | pub use data_row_batch::*; 3 | 4 | pub mod data_row_writer; 5 | pub use data_row_writer::*; 6 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/describe.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Describe { 3 | Portal(String), 4 | PreparedStatement(String), 5 | } 6 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/execute.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Execute { 3 | pub portal: String, 4 | pub max_rows: Option, 5 | } 6 | -------------------------------------------------------------------------------- /src/engine/ast/tcl/mod.rs: -------------------------------------------------------------------------------- 1 | mod begin_transaction; 2 | mod commit; 3 | mod rollback; 4 | 5 | pub use begin_transaction::*; 6 | pub use commit::*; 7 | pub use rollback::*; 8 | -------------------------------------------------------------------------------- /src/engine/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod context; 2 | pub mod implements; 3 | #[allow(clippy::module_inception)] 4 | pub mod parser; 5 | pub mod predule; 6 | 7 | pub(crate) mod test; 8 | -------------------------------------------------------------------------------- /src/engine/actions/ddl/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alter_database; 2 | pub mod alter_table; 3 | pub mod create_database; 4 | pub mod create_table; 5 | pub mod drop_database; 6 | pub mod drop_table; 7 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod filter; 2 | pub mod from; 3 | pub mod join; 4 | pub mod limit_offset; 5 | pub mod scan; 6 | pub mod select_plan; 7 | pub mod subquery; 8 | -------------------------------------------------------------------------------- /src/pgwire/README.md: -------------------------------------------------------------------------------- 1 | # pgwire 2 | 3 | PostgreSQL의 통신 프로토콜 Wire을 맞추기 위한 프로토콜 구현부입니다. 4 | 5 | [convergence](https://github.com/returnString/convergence)에서 많은 부분을 가져오거나 변형해서 사용했습니다. 6 | -------------------------------------------------------------------------------- /src/engine/ast/dml/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod delete; 2 | pub mod insert; 3 | pub mod select; 4 | pub mod update; 5 | 6 | pub mod parts; 7 | 8 | pub mod expressions; 9 | 10 | pub mod plan; 11 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/limit_offset.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct LimitOffsetPlan { 3 | pub limit: Option, 4 | pub offset: Option, 5 | } 6 | -------------------------------------------------------------------------------- /src/pgwire/connection/engine_func.rs: -------------------------------------------------------------------------------- 1 | use std::{pin::Pin, sync::Arc}; 2 | 3 | pub type EngineFunc = 4 | Arc Pin + Send>> + Send + Sync>; 5 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/scan.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::Index; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub enum ScanType { 5 | FullScan, 6 | IndexScan(Index), 7 | } 8 | -------------------------------------------------------------------------------- /src/engine/schema/database.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Deserialize, Serialize, Debug, Clone)] 4 | pub struct DatabaseSchema { 5 | pub database_name: String, 6 | } 7 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct FilterPlan { 5 | pub expression: SQLExpression, 6 | } 7 | -------------------------------------------------------------------------------- /src/pgwire/engine/mod.rs: -------------------------------------------------------------------------------- 1 | #[path = "./engine.rs"] 2 | pub mod engine_impl; 3 | pub use engine_impl::*; 4 | 5 | pub mod portal; 6 | pub use portal::*; 7 | 8 | pub mod rrdb; 9 | pub use rrdb::*; 10 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alter_database; 2 | pub mod alter_table; 3 | pub mod create_database; 4 | pub mod create_index; 5 | pub mod create_table; 6 | pub mod drop_database; 7 | pub mod drop_table; 8 | -------------------------------------------------------------------------------- /src/engine/ast/types/index.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct Index { 3 | pub index_name: String, 4 | pub database_name: Option, 5 | pub columns: Vec, 6 | } 7 | -------------------------------------------------------------------------------- /src/engine/server/client.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct ClientInfo { 5 | pub ip: IpAddr, 6 | pub connection_id: String, 7 | pub database: String, 8 | } 9 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/backend_message.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | 3 | pub trait BackendMessage: std::fmt::Debug { 4 | const TAG: u8; 5 | 6 | fn encode(&self, dst: &mut BytesMut); 7 | } 8 | -------------------------------------------------------------------------------- /src/engine/ast/dml/README.md: -------------------------------------------------------------------------------- 1 | ## DML (Data Manipulation Language) 2 | 3 | - DML에 대한 AST 정의 모듈입니다. 4 | - 기본적인 [SELECT](./select.rs), [UPDATE](./update.rs), [INSERT](./insert.rs), [DELETE](./delete.rs) 명령 등이 이에 속합니다. 5 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/insert_values.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct InsertValue { 5 | pub list: Vec>, 6 | } 7 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/no_from.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::predule::{Index, TableName}; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct SelectNoFromPlan { 5 | select_columns: Vec, 6 | } 7 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/bind_format.rs: -------------------------------------------------------------------------------- 1 | use crate::pgwire::protocol::FormatCode; 2 | 3 | #[derive(Debug)] 4 | pub enum BindFormat { 5 | All(FormatCode), 6 | PerColumn(Vec), 7 | } 8 | -------------------------------------------------------------------------------- /src/engine/parser/README.md: -------------------------------------------------------------------------------- 1 | ## parser 2 | 3 | 구문 분석기입니다. 4 | [어휘 분석기(lexer)](./../lexer/README.md)에서 분석한 어휘 단위를 토대로 [AST](./../ast/README.md)를 구성합니다. 5 | 6 | ### 소스코드 7 | 8 | 구문 분석 로직은 [parser.rs](./parser.rs) 에 있습니다. 9 | -------------------------------------------------------------------------------- /src/engine/parser/implements/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::parser::predule::Parser; 2 | 3 | impl Parser { 4 | #[allow(dead_code)] 5 | pub(crate) fn show_tokens(&self) { 6 | println!("{:?}", self); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/pgwire/connection/bound_portal.rs: -------------------------------------------------------------------------------- 1 | use crate::pgwire::{engine::Engine, protocol::backend::RowDescription}; 2 | 3 | pub struct BoundPortal { 4 | pub portal: E::PortalType, 5 | pub row_desc: RowDescription, 6 | } 7 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/field_description.rs: -------------------------------------------------------------------------------- 1 | use crate::pgwire::protocol::DataTypeOid; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct FieldDescription { 5 | pub name: String, 6 | pub data_type: DataTypeOid, 7 | } 8 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/bind.rs: -------------------------------------------------------------------------------- 1 | use super::BindFormat; 2 | 3 | #[derive(Debug)] 4 | pub struct Bind { 5 | pub portal: String, 6 | pub prepared_statement_name: String, 7 | pub result_format: BindFormat, 8 | } 9 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/startup.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug)] 4 | pub struct Startup { 5 | pub requested_protocol_version: (i16, i16), 6 | pub parameters: HashMap, 7 | } 8 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/update_item.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct UpdateItem { 5 | pub column: String, // update할 컬럼 6 | pub value: SQLExpression, // 수정할 값 7 | } 8 | -------------------------------------------------------------------------------- /src/pgwire/protocol/severity.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, Eq)] 2 | pub struct Severity(pub &'static str); 3 | 4 | impl Severity { 5 | pub const ERROR: Severity = Severity("ERROR"); 6 | pub const FATAL: Severity = Severity("FATAL"); 7 | } 8 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::pgwire::protocol::DataTypeOid; 2 | 3 | #[derive(Debug)] 4 | pub struct Parse { 5 | pub prepared_statement_name: String, 6 | pub query: String, 7 | pub parameter_types: Vec, 8 | } 9 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/where.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] 6 | pub struct WhereClause { 7 | pub expression: SQLExpression, 8 | } 9 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/having.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] 6 | pub struct HavingClause { 7 | pub expression: Box, 8 | } 9 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/mod.rs: -------------------------------------------------------------------------------- 1 | #[path = "./where.rs"] 2 | pub mod _where; 3 | pub mod from; 4 | pub mod group_by; 5 | pub mod having; 6 | pub mod insert_values; 7 | pub mod join; 8 | pub mod order_by; 9 | pub mod select_item; 10 | pub mod target; 11 | pub mod update_item; 12 | -------------------------------------------------------------------------------- /src/engine/ast/types/unique_key.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 4 | pub struct UniqueKey { 5 | pub key_name: String, 6 | pub database_name: Option, 7 | pub columns: Vec, 8 | } 9 | -------------------------------------------------------------------------------- /src/engine/server/shared_state.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::mpsc::Sender; 2 | 3 | use super::{channel::ChannelRequest, client::ClientInfo}; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct SharedState { 7 | pub sender: Sender, 8 | pub client_info: ClientInfo, 9 | } 10 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/delete/from.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{dml::plan::select::scan::ScanType, types::TableName}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct DeleteFromPlan { 5 | pub table_name: TableName, 6 | pub alias: Option, 7 | pub scan: ScanType, 8 | } 9 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/update/from.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{dml::plan::select::scan::ScanType, types::TableName}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct UpdateFromPlan { 5 | pub table_name: TableName, 6 | pub alias: Option, 7 | pub scan: ScanType, 8 | } 9 | -------------------------------------------------------------------------------- /src/pgwire/connection/prepared_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::pgwire::protocol::backend::FieldDescription; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct PreparedStatement { 6 | pub statement: Option, 7 | pub fields: Vec, 8 | } 9 | -------------------------------------------------------------------------------- /src/pgwire/protocol/constants.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | // length prefix, two version components 4 | pub const STARTUP_HEADER_SIZE: usize = size_of::() + (size_of::() * 2); 5 | // message tag, length prefix 6 | pub const MESSAGE_HEADER_SIZE: usize = size_of::() + size_of::(); 7 | -------------------------------------------------------------------------------- /src/engine/wal/endec/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod implements; 2 | 3 | use crate::errors; 4 | 5 | pub trait WALEncoder: Clone { 6 | fn encode(&self, entry: &T) -> errors::Result>; 7 | } 8 | 9 | pub trait WALDecoder: Clone { 10 | fn decode(&self, data: &[u8]) -> errors::Result; 11 | } 12 | -------------------------------------------------------------------------------- /src/engine/ast/README.md: -------------------------------------------------------------------------------- 1 | ## AST (Abstract Syntax Tree) 2 | 3 | - SQL 쿼리에 대한 추상 구문 트리(AST) 정의입니다. 4 | - [구문 분석기(Parser)](./../parser/README.md)에 의해서 생성됩니다. 5 | 6 | ### 소스코드 7 | 8 | - DCL, DDL, DML별로 분리가 되어있습니다. 9 | - 공용 트레잇은 [traits](./traits/READ) 모듈에, 공용 타입은 [types](./types/README.md) 모듈에 존재합니다. 10 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/from.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::TableName; 2 | 3 | use super::scan::ScanType; 4 | 5 | #[derive(Clone, Debug, PartialEq, Eq)] 6 | pub struct SelectFromPlan { 7 | pub table_name: TableName, 8 | pub alias: Option, 9 | pub scan: ScanType, 10 | } 11 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/no_data.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct NoData; 7 | 8 | impl BackendMessage for NoData { 9 | const TAG: u8 = b'n'; 10 | 11 | fn encode(&self, _dst: &mut BytesMut) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod between; 2 | pub mod binary; 3 | pub mod boolean; 4 | pub mod call; 5 | pub mod float; 6 | pub mod identifier; 7 | pub mod integer; 8 | pub mod list; 9 | pub mod not_between; 10 | pub mod operators; 11 | pub mod parentheses; 12 | pub mod string; 13 | pub mod subquery; 14 | pub mod unary; 15 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/bind_complete.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct BindComplete; 7 | 8 | impl BackendMessage for BindComplete { 9 | const TAG: u8 = b'2'; 10 | 11 | fn encode(&self, _dst: &mut BytesMut) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/engine/ast/tcl/commit.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{SQLStatement, TCLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct CommitQuery {} 5 | 6 | impl From for SQLStatement { 7 | fn from(value: CommitQuery) -> SQLStatement { 8 | SQLStatement::TCL(TCLStatement::Commit(value)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/parse_complete.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct ParseComplete; 7 | 8 | impl BackendMessage for ParseComplete { 9 | const TAG: u8 = b'1'; 10 | 11 | fn encode(&self, _dst: &mut BytesMut) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/subquery.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::{Index, SQLExpression, TableName}; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct SelectSubqueryPlan { 5 | pub table_name: TableName, 6 | pub select_columns: Vec, 7 | pub index: Option, 8 | pub filter: Option, 9 | } 10 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod startup; 2 | pub use startup::*; 3 | 4 | pub mod describe; 5 | pub use describe::*; 6 | 7 | pub mod parse; 8 | pub use parse::*; 9 | 10 | pub mod bind_format; 11 | pub use bind_format::*; 12 | 13 | pub mod bind; 14 | pub use bind::*; 15 | 16 | pub mod execute; 17 | pub use execute::*; 18 | -------------------------------------------------------------------------------- /src/engine/ast/tcl/rollback.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{SQLStatement, TCLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct RollbackQuery {} 5 | 6 | impl From for SQLStatement { 7 | fn from(value: RollbackQuery) -> SQLStatement { 8 | SQLStatement::TCL(TCLStatement::Rollback(value)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/engine/lexer/README.md: -------------------------------------------------------------------------------- 1 | ## lexer 2 | 3 | 어휘(토큰) 및 어휘 분석기입니다. 4 | 텍스트를 유효한 단어로 구분합니다. 5 | 6 | 예를 들어 "select age+1 from person"와 같은 문장은 7 | ["select", "age", "+", "1", "from", "person"]으로 분리되어야 합니다. 8 | lexer는 이에 대한 처리를 주관합니다. 9 | 10 | ### 소스코드 11 | 12 | 어휘에 대한 정의는 [tokens.rs](./tokens.rs)에, 13 | 어휘 분석 로직은 [tokenizer.rs](./tokenizer.rs)에 있습니다. 14 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/empty_query_response.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct EmptyQueryResponse; 7 | 8 | impl BackendMessage for EmptyQueryResponse { 9 | const TAG: u8 = b'I'; 10 | 11 | fn encode(&self, _dst: &mut BytesMut) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/ok.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct AuthenticationOk; 7 | 8 | impl BackendMessage for AuthenticationOk { 9 | const TAG: u8 = b'R'; 10 | 11 | fn encode(&self, dst: &mut BytesMut) { 12 | dst.put_i32(0); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/engine/ast/other/show_databases.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{OtherStatement, SQLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ShowDatabasesQuery {} 5 | 6 | impl From for SQLStatement { 7 | fn from(value: ShowDatabasesQuery) -> SQLStatement { 8 | SQLStatement::Other(OtherStatement::ShowDatabases(value)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/engine/ast/tcl/begin_transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{SQLStatement, TCLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct BeginTransactionQuery {} 5 | 6 | impl From for SQLStatement { 7 | fn from(value: BeginTransactionQuery) -> SQLStatement { 8 | SQLStatement::TCL(TCLStatement::BeginTransaction(value)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/ready_for_query.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct ReadyForQuery; 7 | 8 | impl BackendMessage for ReadyForQuery { 9 | const TAG: u8 = b'Z'; 10 | 11 | fn encode(&self, dst: &mut BytesMut) { 12 | dst.put_u8(b'I'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/client/client_message.rs: -------------------------------------------------------------------------------- 1 | use super::{Bind, Describe, Execute, Parse, Startup}; 2 | 3 | #[derive(Debug)] 4 | pub enum ClientMessage { 5 | SSLRequest, // for SSL negotiation 6 | Startup(Startup), 7 | Parse(Parse), 8 | Describe(Describe), 9 | Bind(Bind), 10 | Sync, 11 | Execute(Execute), 12 | Query(String), 13 | Terminate, 14 | } 15 | -------------------------------------------------------------------------------- /src/engine/ast/other/show_tables.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{OtherStatement, SQLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ShowTablesQuery { 5 | pub database: String, 6 | } 7 | 8 | impl From for SQLStatement { 9 | fn from(value: ShowTablesQuery) -> SQLStatement { 10 | SQLStatement::Other(OtherStatement::ShowTables(value)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/engine/ast/types/foreign_key.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::TableName; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 5 | pub struct ForeignKey { 6 | pub key_name: String, 7 | pub table: TableName, 8 | pub columns: Vec, 9 | pub referenced_table: TableName, 10 | pub referenced_columns: Vec, 11 | } 12 | -------------------------------------------------------------------------------- /src/pgwire/connection/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub use error::*; 3 | 4 | #[path = "./connection.rs"] 5 | pub mod connection_impl; 6 | pub use connection_impl::*; 7 | 8 | pub mod state; 9 | pub use state::*; 10 | 11 | pub mod prepared_statement; 12 | pub use prepared_statement::*; 13 | 14 | pub mod bound_portal; 15 | pub use bound_portal::*; 16 | 17 | pub mod engine_func; 18 | pub use engine_func::*; 19 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/parameter_description.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct ParameterDescription {} 7 | 8 | impl BackendMessage for ParameterDescription { 9 | const TAG: u8 = b't'; 10 | 11 | fn encode(&self, dst: &mut BytesMut) { 12 | dst.put_i16(0); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/engine/ast/other/use_database.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{OtherStatement, SQLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct UseDatabaseQuery { 5 | pub database_name: String, 6 | } 7 | 8 | impl From for SQLStatement { 9 | fn from(value: UseDatabaseQuery) -> SQLStatement { 10 | SQLStatement::Other(OtherStatement::UseDatabase(value)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/engine/ast/other/desc_table.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{types::TableName, OtherStatement, SQLStatement}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct DescTableQuery { 5 | pub table_name: TableName, 6 | } 7 | 8 | impl From for SQLStatement { 9 | fn from(value: DescTableQuery) -> SQLStatement { 10 | SQLStatement::Other(OtherStatement::DescTable(value)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/common/command.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | process::{Command, Output}, 4 | }; 5 | 6 | #[mockall::automock] 7 | pub trait CommandRunner { 8 | fn run(&self, command: &mut Command) -> io::Result; 9 | } 10 | 11 | pub struct RealCommandRunner; 12 | 13 | impl CommandRunner for RealCommandRunner { 14 | fn run(&self, command: &mut Command) -> io::Result { 15 | command.output() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/group_by.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SelectColumn; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 6 | pub struct GroupByClause { 7 | pub group_by_items: Vec, 8 | } 9 | 10 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)] 11 | pub struct GroupByItem { 12 | pub item: SelectColumn, 13 | } 14 | -------------------------------------------------------------------------------- /src/engine/parser/implements/tcl/commit.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::tcl::CommitQuery; 3 | use crate::engine::parser::predule::{Parser, ParserContext}; 4 | use crate::errors; 5 | 6 | impl Parser { 7 | pub(crate) fn parse_commit_query( 8 | &mut self, 9 | _context: ParserContext, 10 | ) -> errors::Result { 11 | Ok(CommitQuery {}.into()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/engine/parser/implements/tcl/rollback.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::tcl::RollbackQuery; 3 | use crate::engine::parser::predule::{Parser, ParserContext}; 4 | use crate::errors; 5 | 6 | impl Parser { 7 | pub(crate) fn parse_rollback_query( 8 | &mut self, 9 | _context: ParserContext, 10 | ) -> errors::Result { 11 | Ok(RollbackQuery {}.into()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/command/init.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use clap::Args; 4 | 5 | /// Config options for the build system. 6 | #[derive(Clone, Debug, Default, Deserialize, Args)] 7 | pub struct ConfigOptions { 8 | /// 파일이 세팅될 경로 9 | #[clap(long, short)] 10 | pub config_path: Option, 11 | } 12 | 13 | #[derive(Clone, Debug, Args)] 14 | #[clap(name = "init")] 15 | pub struct Command { 16 | #[clap(flatten)] 17 | pub init: ConfigOptions, 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/review_stats.yml: -------------------------------------------------------------------------------- 1 | name: Review/Comment 14 day Stats 2 | 3 | on: 4 | pull_request: 5 | types: [opened] 6 | 7 | jobs: 8 | stats: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Run pull request stats 12 | uses: flowwer-dev/pull-request-stats@master 13 | with: 14 | token: ${{ secrets.GH_TOKEN }} 15 | period: 14 16 | limit: 7 17 | charts: true 18 | disable-links: true -------------------------------------------------------------------------------- /src/command/run.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use clap::Args; 4 | 5 | /// Config options for the build system. 6 | #[derive(Clone, Debug, Default, Deserialize, Args)] 7 | pub struct ConfigOptions { 8 | #[clap(name = "config", long, help = "config file path")] 9 | pub config: Option, 10 | } 11 | 12 | #[derive(Clone, Debug, Args)] 13 | #[clap(name = "run")] 14 | pub struct Command { 15 | #[clap(flatten)] 16 | pub value: ConfigOptions, 17 | } 18 | -------------------------------------------------------------------------------- /src/pgwire/protocol/protocol_error.rs: -------------------------------------------------------------------------------- 1 | #[derive(thiserror::Error, Debug)] 2 | pub enum ProtocolError { 3 | #[error("io error: {0}")] 4 | Io(#[from] std::io::Error), 5 | #[error("utf8 error: {0}")] 6 | Utf8(#[from] std::string::FromUtf8Error), 7 | #[error("parsing error")] 8 | ParserError, 9 | #[error("invalid message type: {0}")] 10 | InvalidMessageType(u8), 11 | #[error("invalid format code: {0}")] 12 | InvalidFormatCode(i16), 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | .rrdb.config 18 | 19 | .vscode 20 | 21 | tarpaulin-report.html -------------------------------------------------------------------------------- /src/command/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod init; 2 | pub mod run; 3 | 4 | use clap::Parser; 5 | 6 | #[derive(Parser, Debug)] 7 | #[clap(author, version, about, long_about = None)] 8 | pub struct Command { 9 | #[clap(subcommand)] 10 | pub action: SubCommand, 11 | } 12 | 13 | #[derive(clap::Subcommand, Debug)] 14 | pub enum SubCommand { 15 | /// 설정 파일 및 저장공간 초기화 16 | Init(init::Command), 17 | /// DB 서버 실행 18 | Run(run::Command), 19 | /// DB 클라이언트 실행 20 | Client, 21 | } 22 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/float.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq)] 2 | pub struct FloatExpression { 3 | pub value: f64, 4 | } 5 | 6 | impl FloatExpression { 7 | pub fn new(value: f64) -> Self { 8 | Self { value } 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::FloatExpression; 15 | 16 | #[test] 17 | fn test_new() { 18 | let float = FloatExpression::new(1.0); 19 | assert_eq!(float.value, 1.0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/parentheses.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub struct ParenthesesExpression { 7 | pub expression: SQLExpression, 8 | } 9 | 10 | impl From for SQLExpression { 11 | fn from(value: ParenthesesExpression) -> SQLExpression { 12 | SQLExpression::Parentheses(Box::new(value)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/engine/server/channel.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::oneshot::Sender; 2 | 3 | use crate::engine::ast::SQLStatement; 4 | use crate::engine::types::ExecuteResult; 5 | use crate::errors; 6 | 7 | #[derive(Debug)] 8 | pub struct ChannelRequest { 9 | pub statement: SQLStatement, 10 | pub connection_id: String, 11 | pub response_sender: Sender, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct ChannelResponse { 16 | pub result: errors::Result, 17 | } 18 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/command_complete.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct CommandComplete { 7 | pub command_tag: String, 8 | } 9 | 10 | impl BackendMessage for CommandComplete { 11 | const TAG: u8 = b'C'; 12 | 13 | fn encode(&self, dst: &mut BytesMut) { 14 | dst.put_slice(self.command_tag.as_bytes()); 15 | dst.put_u8(0); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/call.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::{Function, SQLExpression}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub struct CallExpression { 7 | pub function: Function, 8 | pub arguments: Vec, 9 | } 10 | 11 | impl From for SQLExpression { 12 | fn from(value: CallExpression) -> SQLExpression { 13 | SQLExpression::FunctionCall(value) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/integer.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub struct IntegerExpression { 3 | pub value: i64, 4 | } 5 | 6 | impl IntegerExpression { 7 | pub fn new(value: i64) -> Self { 8 | Self { value } 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::IntegerExpression; 15 | 16 | #[test] 17 | fn test_new() { 18 | let integer = IntegerExpression::new(1); 19 | assert_eq!(integer.value, 1); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/engine/parser/test/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod alter_database; 2 | pub(crate) mod create_database; 3 | pub(crate) mod drop_database; 4 | 5 | pub(crate) mod alter_table; 6 | pub(crate) mod create_table; 7 | pub(crate) mod drop_table; 8 | pub(crate) mod expressions; 9 | 10 | pub(crate) mod delete; 11 | pub(crate) mod insert; 12 | pub(crate) mod select; 13 | pub(crate) mod update; 14 | 15 | pub(crate) mod other; 16 | 17 | pub(crate) mod tcl; 18 | 19 | pub(crate) mod common; 20 | 21 | pub(crate) mod ddl; 22 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/target.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::TableName; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 6 | pub struct UpdateTarget { 7 | pub table: TableName, 8 | pub alias: Option, 9 | } 10 | 11 | impl From for UpdateTarget { 12 | fn from(value: TableName) -> UpdateTarget { 13 | UpdateTarget { 14 | table: value, 15 | alias: None, 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/pgwire/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data_types; 2 | pub use data_types::*; 3 | 4 | pub mod format_code; 5 | pub use format_code::*; 6 | 7 | pub mod sql_state; 8 | pub use sql_state::*; 9 | 10 | pub mod severity; 11 | pub use severity::*; 12 | 13 | pub mod message; 14 | pub use message::*; 15 | 16 | pub mod connection_codec; 17 | pub use connection_codec::*; 18 | 19 | pub mod protocol_error; 20 | pub use protocol_error::*; 21 | 22 | pub mod constants; 23 | pub use constants::*; 24 | 25 | pub mod extension; 26 | pub use extension::*; 27 | -------------------------------------------------------------------------------- /src/engine/encoder/schema_encoder.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub struct StorageEncoder {} 4 | 5 | #[allow(clippy::new_without_default)] 6 | impl StorageEncoder { 7 | pub fn new() -> Self { 8 | StorageEncoder {} 9 | } 10 | 11 | pub fn encode(&self, data: impl Serialize) -> Vec { 12 | bson::to_vec(&data).unwrap() 13 | } 14 | 15 | pub fn decode<'a, T>(&self, data: &'a [u8]) -> Option 16 | where 17 | T: Deserialize<'a>, 18 | { 19 | bson::from_slice(data).ok() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/engine/ast/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod column; 2 | pub use column::*; 3 | 4 | pub mod table; 5 | pub use table::*; 6 | 7 | pub mod foreign_key; 8 | pub use foreign_key::*; 9 | 10 | pub mod unique_key; 11 | pub use unique_key::*; 12 | 13 | pub mod table_options; 14 | pub use table_options::*; 15 | 16 | pub mod data_types; 17 | pub use data_types::*; 18 | 19 | pub mod select_column; 20 | pub use select_column::*; 21 | 22 | pub mod function; 23 | pub use function::*; 24 | 25 | pub mod expression; 26 | pub use expression::*; 27 | 28 | pub mod index; 29 | pub use index::*; 30 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/from.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{dml::expressions::subquery::SubqueryExpression, types::TableName}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub struct FromClause { 7 | pub from: FromTarget, 8 | pub alias: Option, 9 | } 10 | 11 | impl FromClause {} 12 | 13 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 14 | pub enum FromTarget { 15 | Table(TableName), // 일반 테이블 참조 16 | Subquery(SubqueryExpression), // 서브쿼리 참조 17 | } 18 | 19 | impl FromTarget {} 20 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/select/join.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{ 2 | dml::parts::join::JoinType, 3 | types::{Index, SQLExpression, TableName}, 4 | }; 5 | 6 | #[derive(Clone, Debug, PartialEq)] 7 | pub struct JoinPlan { 8 | pub left: TableName, 9 | pub right: TableName, 10 | pub join_type: JoinType, 11 | pub join_scan_type: JoinScanType, 12 | pub select_columns: Vec, 13 | pub index: Option, 14 | pub filter: Option, 15 | } 16 | 17 | #[derive(Clone, Debug, PartialEq, Eq)] 18 | pub enum JoinScanType { 19 | NestedLoop, 20 | Hash, 21 | Merge, 22 | } 23 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/list.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub struct ListExpression { 7 | pub value: Vec, 8 | } 9 | 10 | impl From for SQLExpression { 11 | fn from(value: ListExpression) -> SQLExpression { 12 | SQLExpression::List(value) 13 | } 14 | } 15 | 16 | impl From> for ListExpression { 17 | fn from(value: Vec) -> ListExpression { 18 | ListExpression { value } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/subquery.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{dml::select::SelectQuery, types::SQLExpression}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub enum SubqueryExpression { 7 | Select(Box), 8 | } 9 | 10 | impl Default for SubqueryExpression { 11 | fn default() -> Self { 12 | SubqueryExpression::Select(Box::new(SelectQuery::builder())) 13 | } 14 | } 15 | 16 | impl From for SQLExpression { 17 | fn from(value: SubqueryExpression) -> SQLExpression { 18 | SQLExpression::Subquery(value) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pgwire/engine/portal.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use crate::pgwire::protocol::{backend::ErrorResponse, DataRowBatch}; 4 | 5 | /// A Postgres portal. Portals represent a prepared statement with all parameters specified. 6 | /// 7 | /// See Postgres' protocol docs regarding the [extended query overview](https://www.postgresql.org/docs/current/protocol-overview.html#PROTOCOL-QUERY-CONCEPTS) 8 | /// for more details. 9 | #[async_trait] 10 | pub trait Portal: Send + Sync { 11 | /// Fetches the contents of the portal into a [DataRowBatch]. 12 | async fn fetch(&mut self, batch: &mut DataRowBatch) -> Result<(), ErrorResponse>; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/macos.rs: -------------------------------------------------------------------------------- 1 | // MacOS에서 고정 환경변수를 가져옵니다. 2 | pub fn get_profile_path() -> String { 3 | let username = whoami::username(); 4 | let user_path = format!("/Users/{}", username); 5 | let profile_path = format!("{}/.zshenv", user_path); 6 | 7 | profile_path 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use super::*; 13 | 14 | #[test] 15 | fn test_get_profile_path() { 16 | let username = whoami::username(); 17 | let user_path = format!("/Users/{}", username); 18 | let profile_path = format!("{}/.zshenv", user_path); 19 | 20 | assert_eq!(get_profile_path(), profile_path); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/join.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::{SQLExpression, TableName}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] 6 | pub struct JoinClause { 7 | pub join_type: JoinType, 8 | pub right: TableName, 9 | pub right_alias: Option, 10 | pub on: Option, 11 | } 12 | 13 | impl JoinClause {} 14 | 15 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 16 | #[derive(Default)] 17 | pub enum JoinType { 18 | #[default] 19 | InnerJoin, 20 | LeftOuterJoin, 21 | RightOuterJoin, 22 | FullOuterJoin, 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/common/fs.rs: -------------------------------------------------------------------------------- 1 | use futures::io; 2 | 3 | #[mockall::automock] 4 | #[async_trait::async_trait] 5 | pub trait FileSystem { 6 | async fn create_dir(&self, path: &str) -> io::Result<()>; 7 | async fn write_file(&self, path: &str, content: &[u8]) -> io::Result<()>; 8 | } 9 | 10 | pub struct RealFileSystem; 11 | 12 | #[async_trait::async_trait] 13 | impl FileSystem for RealFileSystem { 14 | async fn create_dir(&self, path: &str) -> io::Result<()> { 15 | tokio::fs::create_dir(path).await 16 | } 17 | 18 | async fn write_file(&self, path: &str, content: &[u8]) -> io::Result<()> { 19 | tokio::fs::write(path, content).await 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(dead_code)] 3 | 4 | pub mod command; 5 | // pub mod lib; 6 | 7 | // use parser::context::ParserContext; 8 | 9 | // use crate::ast::predule::{ 10 | // BinaryOperator, BinaryOperatorExpression, SQLExpression, SelectItem, SelectQuery, 11 | // }; 12 | // use crate::parser::predule::Parser; 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<(), Box> { 16 | // let text = r#" 17 | // create database asdf; 18 | // "# 19 | // .to_owned(); 20 | 21 | // let mut parser = Parser::new(text).unwrap(); 22 | 23 | // parser.parse(ParserContext::default()).unwrap(); 24 | 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /src/engine/parser/implements/other/desc.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::other::desc_table::DescTableQuery; 3 | use crate::engine::parser::predule::{Parser, ParserContext}; 4 | use crate::errors::parsing_error::ParsingError; 5 | use crate::errors::{self}; 6 | 7 | impl Parser { 8 | pub(crate) fn parse_desc_query( 9 | &mut self, 10 | context: ParserContext, 11 | ) -> errors::Result { 12 | if !self.has_next_token() { 13 | return Err(ParsingError::wrap("need more tokens")); 14 | } 15 | 16 | let table_name = self.parse_table_name(context)?; 17 | 18 | Ok(DescTableQuery { table_name }.into()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pgwire/protocol/format_code.rs: -------------------------------------------------------------------------------- 1 | use crate::pgwire::protocol::ProtocolError; 2 | 3 | /// Describes how to format a given value or set of values. 4 | #[derive(Debug, Copy, Clone)] 5 | pub enum FormatCode { 6 | /// Use the stable text representation. 7 | Text = 0, 8 | /// Use the less-stable binary representation. 9 | Binary = 1, 10 | } 11 | 12 | impl TryFrom for FormatCode { 13 | type Error = ProtocolError; 14 | 15 | fn try_from(value: i16) -> Result { 16 | match value { 17 | 0 => Ok(FormatCode::Text), 18 | 1 => Ok(FormatCode::Binary), 19 | other => Err(ProtocolError::InvalidFormatCode(other)), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/collection.rs: -------------------------------------------------------------------------------- 1 | macro_rules! join_vec { 2 | ($x:expr) => { 3 | $x 4 | }; 5 | ($x:expr, $($y:expr),+) => { 6 | { 7 | let join_fn = |mut lhs: Vec<_>, mut rhs: Vec<_>| -> Vec<_> { 8 | lhs.append(&mut rhs); 9 | lhs 10 | }; 11 | 12 | join_fn($x, join_vec!($($y),+)) 13 | } 14 | 15 | }; 16 | } 17 | 18 | pub(crate) use join_vec; 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | #[test] 23 | fn test_join_vec() { 24 | let v1 = vec![1, 2, 3]; 25 | let v2 = vec![4, 5, 6]; 26 | 27 | assert_eq!(join_vec!(v1), vec![1, 2, 3]); 28 | assert_eq!(join_vec!(v1, v2), vec![1, 2, 3, 4, 5, 6]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/engine/parser/context.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default, Clone)] 2 | pub struct ParserContext { 3 | pub in_between_clause: bool, 4 | pub in_parentheses: bool, 5 | pub default_database: Option, 6 | } 7 | 8 | impl ParserContext { 9 | pub fn set_in_between_clause(mut self, in_between_clause: bool) -> Self { 10 | self.in_between_clause = in_between_clause; 11 | self 12 | } 13 | 14 | pub fn set_in_parentheses(mut self, in_parentheses: bool) -> Self { 15 | self.in_parentheses = in_parentheses; 16 | self 17 | } 18 | 19 | pub fn set_default_database(mut self, default_database: String) -> Self { 20 | self.default_database = Some(default_database); 21 | self 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/engine/parser/implements/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::parser::predule::Parser; 2 | 3 | use crate::engine::lexer::predule::Token; 4 | 5 | impl Parser { 6 | // 다음 토큰 획득 7 | pub(crate) fn get_next_token(&mut self) -> Token { 8 | self.tokens.pop_front().unwrap() 9 | } 10 | 11 | // 다음 토큰 미리보기 12 | pub(crate) fn pick_next_token(&mut self) -> Token { 13 | self.tokens.front().unwrap().to_owned() 14 | } 15 | 16 | // 토큰 획득 롤백 17 | pub(crate) fn unget_next_token(&mut self, token: Token) { 18 | self.tokens.push_front(token) 19 | } 20 | 21 | // 다음 토큰 유무 확인 22 | pub(crate) fn has_next_token(&self) -> bool { 23 | !self.tokens.is_empty() && !self.tokens.front().unwrap().is_eof() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/errors/wal_errors.rs: -------------------------------------------------------------------------------- 1 | use super::{ErrorKind, Errors}; 2 | 3 | #[derive(Debug)] 4 | pub struct WALError { 5 | pub message: String, 6 | pub backtrace: std::backtrace::Backtrace, 7 | } 8 | 9 | impl PartialEq for WALError { 10 | fn eq(&self, other: &Self) -> bool { 11 | self.message == other.message 12 | } 13 | } 14 | 15 | impl WALError { 16 | pub fn wrap(message: T) -> Errors { 17 | Errors::new(ErrorKind::WALError(message.to_string())) 18 | } 19 | } 20 | 21 | impl std::error::Error for WALError {} 22 | 23 | impl std::fmt::Display for WALError { 24 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 25 | write!(formatter, "wal error: {}", self.message) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/pgwire/protocol/sql_state.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, Eq)] 2 | pub struct SqlState(pub &'static str); 3 | 4 | impl SqlState { 5 | pub const SUCCESSFUL_COMPLETION: SqlState = SqlState("00000"); 6 | pub const FEATURE_NOT_SUPPORTED: SqlState = SqlState("0A000"); 7 | pub const INVALID_CURSOR_NAME: SqlState = SqlState("34000"); 8 | pub const CONNECTION_EXCEPTION: SqlState = SqlState("08000"); 9 | pub const INVALID_SQL_STATEMENT_NAME: SqlState = SqlState("26000"); 10 | pub const DATA_EXCEPTION: SqlState = SqlState("22000"); 11 | pub const PROTOCOL_VIOLATION: SqlState = SqlState("08P01"); 12 | pub const SYNTAX_ERROR: SqlState = SqlState("42601"); 13 | pub const INVALID_DATETIME_FORMAT: SqlState = SqlState("22007"); 14 | } 15 | -------------------------------------------------------------------------------- /src/errors/type_error.rs: -------------------------------------------------------------------------------- 1 | use super::{ErrorKind, Errors}; 2 | 3 | #[derive(Debug)] 4 | pub struct TypeError { 5 | pub message: String, 6 | pub backtrace: std::backtrace::Backtrace, 7 | } 8 | 9 | impl PartialEq for TypeError { 10 | fn eq(&self, other: &Self) -> bool { 11 | self.message == other.message 12 | } 13 | } 14 | 15 | impl TypeError { 16 | pub fn wrap(message: T) -> Errors { 17 | Errors::new(ErrorKind::TypeError(message.to_string())) 18 | } 19 | } 20 | 21 | impl std::error::Error for TypeError {} 22 | 23 | impl std::fmt::Display for TypeError { 24 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 25 | write!(formatter, "type error: {}", self.message) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/errors/execute_error.rs: -------------------------------------------------------------------------------- 1 | use super::{ErrorKind, Errors}; 2 | 3 | #[derive(Debug)] 4 | pub struct ExecuteError { 5 | pub message: String, 6 | pub backtrace: std::backtrace::Backtrace, 7 | } 8 | 9 | impl PartialEq for ExecuteError { 10 | fn eq(&self, other: &Self) -> bool { 11 | self.message == other.message 12 | } 13 | } 14 | 15 | impl ExecuteError { 16 | pub fn wrap(message: T) -> Errors { 17 | Errors::new(ErrorKind::ExecuteError(message.to_string())) 18 | } 19 | } 20 | 21 | impl std::error::Error for ExecuteError {} 22 | 23 | impl std::fmt::Display for ExecuteError { 24 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 25 | write!(formatter, "{}", self.message) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/errors/lexing_error.rs: -------------------------------------------------------------------------------- 1 | use super::{ErrorKind, Errors}; 2 | 3 | #[derive(Debug)] 4 | pub struct LexingError { 5 | pub message: String, 6 | pub backtrace: std::backtrace::Backtrace, 7 | } 8 | 9 | impl PartialEq for LexingError { 10 | fn eq(&self, other: &Self) -> bool { 11 | self.message == other.message 12 | } 13 | } 14 | 15 | impl LexingError { 16 | pub fn wrap(message: T) -> Errors { 17 | Errors::new(ErrorKind::LexingError(message.to_string())) 18 | } 19 | } 20 | 21 | impl std::error::Error for LexingError {} 22 | 23 | impl std::fmt::Display for LexingError { 24 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 25 | write!(formatter, "lexing error: {}", self.message) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/engine/ast/types/select_column.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | // [table_alias.]column_name 5 | // SELECT시 컬럼 지정을 가리키는 값입니다. 6 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)] 7 | pub struct SelectColumn { 8 | pub table_name: Option, 9 | pub column_name: String, 10 | } 11 | 12 | impl SelectColumn { 13 | pub fn new(table_name: Option, column_name: String) -> Self { 14 | SelectColumn { 15 | column_name, 16 | table_name, 17 | } 18 | } 19 | } 20 | 21 | impl From for SQLExpression { 22 | fn from(value: SelectColumn) -> SQLExpression { 23 | SQLExpression::SelectColumn(value) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/errors/parsing_error.rs: -------------------------------------------------------------------------------- 1 | use super::{ErrorKind, Errors}; 2 | 3 | #[derive(Debug)] 4 | pub struct ParsingError { 5 | pub message: String, 6 | pub backtrace: std::backtrace::Backtrace, 7 | } 8 | 9 | impl PartialEq for ParsingError { 10 | fn eq(&self, other: &Self) -> bool { 11 | self.message == other.message 12 | } 13 | } 14 | 15 | impl ParsingError { 16 | pub fn wrap(message: T) -> Errors { 17 | Errors::new(ErrorKind::ParsingError(message.to_string())) 18 | } 19 | } 20 | 21 | impl std::error::Error for ParsingError {} 22 | 23 | impl std::fmt::Display for ParsingError { 24 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 25 | write!(formatter, "parsing error: {}", self.message) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/engine/parser/test/drop_table.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use crate::engine::ast::ddl::drop_table::DropTableQuery; 4 | use crate::engine::ast::types::TableName; 5 | use crate::engine::parser::context::ParserContext; 6 | use crate::engine::parser::predule::Parser; 7 | 8 | #[test] 9 | pub fn drop_table() { 10 | let text = r#" 11 | drop table if exists "foo_db".foo; 12 | "# 13 | .to_owned(); 14 | 15 | let mut parser = Parser::with_string(text).unwrap(); 16 | 17 | let expected = DropTableQuery::builder() 18 | .set_table(TableName::new(Some("foo_db".to_owned()), "foo".to_owned())) 19 | .set_if_exists(true) 20 | .build(); 21 | 22 | assert_eq!( 23 | parser.parse(ParserContext::default()).unwrap(), 24 | vec![expected], 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/parameter_status.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | use crate::pgwire::protocol::backend::BackendMessage; 4 | 5 | #[derive(Debug)] 6 | pub struct ParameterStatus { 7 | name: String, 8 | value: String, 9 | } 10 | 11 | impl BackendMessage for ParameterStatus { 12 | const TAG: u8 = b'S'; 13 | 14 | fn encode(&self, dst: &mut BytesMut) { 15 | dst.put_slice(self.name.as_bytes()); 16 | dst.put_u8(0); 17 | dst.put_slice(self.value.as_bytes()); 18 | dst.put_u8(0); 19 | } 20 | } 21 | 22 | impl ParameterStatus { 23 | pub fn new(name: impl Into, value: impl Into) -> Self { 24 | Self { 25 | name: name.into(), 26 | value: value.into(), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parameter_description; 2 | pub use parameter_description::*; 3 | 4 | pub mod field_description; 5 | pub use field_description::*; 6 | 7 | pub mod row_description; 8 | pub use row_description::*; 9 | 10 | pub mod ok; 11 | pub use ok::*; 12 | 13 | pub mod ready_for_query; 14 | pub use ready_for_query::*; 15 | 16 | pub mod parse_complete; 17 | pub use parse_complete::*; 18 | 19 | pub mod bind_complete; 20 | pub use bind_complete::*; 21 | 22 | pub mod no_data; 23 | pub use no_data::*; 24 | 25 | pub mod empty_query_response; 26 | pub use empty_query_response::*; 27 | 28 | pub mod command_complete; 29 | pub use command_complete::*; 30 | 31 | pub mod parameter_status; 32 | pub use parameter_status::*; 33 | 34 | pub mod error_response; 35 | pub use error_response::*; 36 | -------------------------------------------------------------------------------- /src/engine/ast/types/table.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::engine::ast::dml::parts::from::{FromClause, FromTarget}; 4 | 5 | // [database_name.]table_name 6 | // 테이블명을 가리키는 값입니다. 7 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)] 8 | pub struct TableName { 9 | pub database_name: Option, 10 | pub table_name: String, 11 | } 12 | 13 | impl TableName { 14 | pub fn new(database_name: Option, table_name: String) -> Self { 15 | TableName { 16 | database_name, 17 | table_name, 18 | } 19 | } 20 | } 21 | 22 | impl From for FromClause { 23 | fn from(value: TableName) -> FromClause { 24 | FromClause { 25 | from: FromTarget::Table(value), 26 | alias: None, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/order_by.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub struct OrderByClause { 7 | pub order_by_items: Vec, 8 | } 9 | 10 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] 11 | pub struct OrderByItem { 12 | pub item: SQLExpression, 13 | pub order_type: OrderByType, 14 | pub nulls: OrderByNulls, 15 | } 16 | 17 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 18 | #[derive(Default)] 19 | pub enum OrderByType { 20 | #[default] 21 | Asc, 22 | Desc, 23 | } 24 | 25 | 26 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 27 | #[derive(Default)] 28 | pub enum OrderByNulls { 29 | First, 30 | #[default] 31 | Last, 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/pgwire/connection/error/connection_error.rs: -------------------------------------------------------------------------------- 1 | use crate::pgwire::protocol::{backend::ErrorResponse, ProtocolError}; 2 | 3 | /// Describes an error that may or may not result in the termination of a connection. 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum ConnectionError { 6 | /// A protocol error was encountered, e.g. an invalid message for a connection's current state. 7 | #[error("protocol error: {0}")] 8 | Protocol(#[from] ProtocolError), 9 | /// A Postgres error containing a SqlState code and message occurred. 10 | /// May result in connection termination depending on the severity. 11 | #[error("error response: {0}")] 12 | ErrorResponse(#[from] ErrorResponse), 13 | /// The connection was closed. 14 | /// This always implies connection termination. 15 | #[error("connection closed")] 16 | ConnectionClosed, 17 | } 18 | -------------------------------------------------------------------------------- /src/engine/lexer/test/comment.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use crate::engine::lexer::predule::{Token, Tokenizer}; 3 | 4 | #[test] 5 | pub fn comment_1() { 6 | let text = r#"SELECT 1 -- asdf"#.to_owned(); 7 | 8 | let tokens = Tokenizer::string_to_tokens(text).unwrap(); 9 | 10 | assert_eq!( 11 | tokens, 12 | vec![ 13 | Token::Select, 14 | Token::Integer(1), 15 | Token::CodeComment(" asdf".to_owned()) 16 | ] 17 | ); 18 | } 19 | 20 | #[test] 21 | pub fn comment_2() { 22 | let text = r#"SELECT /*asdf*/1"#.to_owned(); 23 | 24 | let tokens = Tokenizer::string_to_tokens(text).unwrap(); 25 | 26 | assert_eq!( 27 | tokens, 28 | vec![ 29 | Token::Select, 30 | Token::CodeComment("asdf".to_owned()), 31 | Token::Integer(1), 32 | ] 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/drop_table.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{types::TableName, DDLStatement, SQLStatement}; 2 | 3 | /* 4 | DROP TABLE [IF EXISTS] [database_name.]table_name; 5 | */ 6 | #[derive(Clone, Debug, PartialEq, Eq)] 7 | pub struct DropTableQuery { 8 | pub table: Option, 9 | pub if_exists: bool, 10 | } 11 | 12 | impl DropTableQuery { 13 | pub fn builder() -> Self { 14 | DropTableQuery { 15 | table: None, 16 | if_exists: false, 17 | } 18 | } 19 | 20 | pub fn set_table(mut self, table: TableName) -> Self { 21 | self.table = Some(table); 22 | self 23 | } 24 | 25 | pub fn set_if_exists(mut self, set_if_exists: bool) -> Self { 26 | self.if_exists = set_if_exists; 27 | self 28 | } 29 | 30 | pub fn build(self) -> SQLStatement { 31 | SQLStatement::DDL(DDLStatement::DropTableQuery(self)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/engine/wal/types.rs: -------------------------------------------------------------------------------- 1 | use bitcode::{Decode, Encode}; 2 | 3 | #[derive(Default, Clone, Debug, Encode, Decode)] 4 | pub struct WALEntry { 5 | pub entry_type: EntryType, 6 | pub data: Option>, 7 | pub timestamp: u128, 8 | pub transaction_id: Option, 9 | pub is_continuation: bool, 10 | } 11 | 12 | impl WALEntry { 13 | pub fn size(&self) -> usize { 14 | let data_size = self.data.as_ref().map_or(0, |data| data.len()); 15 | 16 | size_of::() 17 | + size_of::() 18 | + size_of::>() 19 | + size_of::() 20 | + data_size 21 | } 22 | } 23 | 24 | #[derive(Default, Clone, Debug, Encode, Decode)] 25 | pub enum EntryType { 26 | #[default] 27 | Insert, 28 | Set, 29 | Delete, 30 | Checkpoint, 31 | 32 | TransactionBegin, 33 | TransactionCommit, 34 | TransactionRollback, 35 | } 36 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/drop_database.rs: -------------------------------------------------------------------------------- 1 | pub use crate::engine::ast::{DDLStatement, SQLStatement}; 2 | 3 | /* 4 | DROP DATABASE [IF EXISTS] database_name; 5 | */ 6 | #[derive(Clone, Debug, PartialEq, Eq)] 7 | pub struct DropDatabaseQuery { 8 | pub database_name: Option, 9 | pub if_exists: bool, 10 | } 11 | 12 | impl DropDatabaseQuery { 13 | pub fn builder() -> Self { 14 | DropDatabaseQuery { 15 | database_name: None, 16 | if_exists: false, 17 | } 18 | } 19 | 20 | pub fn set_name(mut self, name: String) -> Self { 21 | self.database_name = Some(name); 22 | self 23 | } 24 | 25 | pub fn set_if_exists(mut self, set_if_exists: bool) -> Self { 26 | self.if_exists = set_if_exists; 27 | self 28 | } 29 | 30 | pub fn build(self) -> SQLStatement { 31 | SQLStatement::DDL(DDLStatement::DropDatabaseQuery(self)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/engine/parser/implements/tcl/begin.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::tcl::BeginTransactionQuery; 3 | use crate::engine::lexer::tokens::Token; 4 | use crate::engine::parser::predule::{Parser, ParserContext}; 5 | use crate::errors::parsing_error::ParsingError; 6 | use crate::errors; 7 | 8 | impl Parser { 9 | pub(crate) fn parse_begin_query( 10 | &mut self, 11 | _context: ParserContext, 12 | ) -> errors::Result { 13 | if !self.has_next_token() { 14 | return Err(ParsingError::wrap("need more tokens")); 15 | } 16 | 17 | let current_token = self.get_next_token(); 18 | 19 | if current_token != Token::Transaction { 20 | return Err(ParsingError::wrap( 21 | "Expected BEGIN".to_string(), 22 | )); 23 | } 24 | 25 | Ok(BeginTransactionQuery {}.into()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/create_database.rs: -------------------------------------------------------------------------------- 1 | pub use crate::engine::ast::{DDLStatement, SQLStatement}; 2 | 3 | /* 4 | CREATE DATABASE [IF NOT EXISTS] database_name; 5 | */ 6 | #[derive(Debug, Clone, PartialEq, Eq)] 7 | pub struct CreateDatabaseQuery { 8 | pub database_name: Option, 9 | pub if_not_exists: bool, 10 | } 11 | 12 | impl CreateDatabaseQuery { 13 | pub fn builder() -> Self { 14 | CreateDatabaseQuery { 15 | database_name: None, 16 | if_not_exists: false, 17 | } 18 | } 19 | 20 | pub fn set_name(mut self, name: String) -> Self { 21 | self.database_name = Some(name); 22 | self 23 | } 24 | 25 | pub fn set_if_not_exists(mut self, if_not_exists: bool) -> Self { 26 | self.if_not_exists = if_not_exists; 27 | self 28 | } 29 | 30 | pub fn build(self) -> SQLStatement { 31 | SQLStatement::DDL(DDLStatement::CreateDatabaseQuery(self)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/row_description.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | use crate::pgwire::protocol::{backend::BackendMessage, FormatCode}; 4 | 5 | use super::field_description::FieldDescription; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct RowDescription { 9 | pub fields: Vec, 10 | pub format_code: FormatCode, 11 | } 12 | 13 | impl BackendMessage for RowDescription { 14 | const TAG: u8 = b'T'; 15 | 16 | fn encode(&self, dst: &mut BytesMut) { 17 | dst.put_i16(self.fields.len() as i16); 18 | for field in &self.fields { 19 | dst.put_slice(field.name.as_bytes()); 20 | dst.put_u8(0); 21 | dst.put_i32(0); // table oid 22 | dst.put_i16(0); // column attr number 23 | dst.put_u32(field.data_type.into()); 24 | dst.put_i16(field.data_type.size_bytes()); 25 | dst.put_i32(-1); // data type modifier 26 | dst.put_i16(self.format_code as i16); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/engine/parser/implements/other/use.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::other::use_database::UseDatabaseQuery; 3 | use crate::engine::lexer::predule::Token; 4 | use crate::engine::parser::predule::{Parser, ParserContext}; 5 | use crate::errors::parsing_error::ParsingError; 6 | use crate::errors::{self}; 7 | 8 | impl Parser { 9 | pub(crate) fn parse_use_query( 10 | &mut self, 11 | _context: ParserContext, 12 | ) -> errors::Result { 13 | if !self.has_next_token() { 14 | return Err(ParsingError::wrap("need more tokens")); 15 | } 16 | 17 | let current_token = self.get_next_token(); 18 | 19 | match current_token { 20 | Token::Identifier(identifier) => Ok(UseDatabaseQuery { 21 | database_name: identifier, 22 | } 23 | .into()), 24 | _ => Err(ParsingError::wrap(format!( 25 | "unexpected token '{:?}'", 26 | current_token 27 | ))), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/pgwire/engine/engine.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use crate::engine::ast::SQLStatement; 4 | use crate::pgwire::protocol::backend::{ErrorResponse, FieldDescription}; 5 | 6 | use super::Portal; 7 | 8 | /// The engine trait is the core of the `convergence` crate, and is responsible for dispatching most SQL operations. 9 | /// 10 | /// Each connection is allocated an [Engine] instance, which it uses to prepare statements, create portals, etc. 11 | #[async_trait] 12 | pub trait Engine: Send + Sync + 'static { 13 | /// The [Portal] implementation used by [Engine::create_portal]. 14 | type PortalType: Portal; 15 | 16 | /// Prepares a statement, returning a vector of field descriptions for the final statement result. 17 | async fn prepare( 18 | &mut self, 19 | statement: &SQLStatement, 20 | ) -> Result, ErrorResponse>; 21 | 22 | /// Creates a new portal for the given statement. 23 | async fn create_portal( 24 | &mut self, 25 | stmt: &SQLStatement, 26 | ) -> Result; 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 myyrakle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/engine/wal/endec/implements/bitcode.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::wal::endec::{WALDecoder, WALEncoder}; 2 | use crate::engine::wal::types::WALEntry; 3 | use crate::errors; 4 | use crate::errors::wal_errors::WALError; 5 | 6 | #[derive(Clone)] 7 | pub struct BitcodeEncoder {} 8 | impl Default for BitcodeEncoder { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl BitcodeEncoder { 15 | pub fn new() -> Self { 16 | Self {} 17 | } 18 | } 19 | 20 | impl WALEncoder> for BitcodeEncoder { 21 | fn encode(&self, entry: &Vec) -> errors::Result> { 22 | Ok(bitcode::encode(entry)) 23 | } 24 | } 25 | 26 | #[derive(Clone)] 27 | pub struct BitcodeDecoder {} 28 | impl Default for BitcodeDecoder { 29 | fn default() -> Self { 30 | Self::new() 31 | } 32 | } 33 | 34 | impl BitcodeDecoder { 35 | pub fn new() -> Self { 36 | Self {} 37 | } 38 | } 39 | 40 | impl WALDecoder> for BitcodeDecoder { 41 | fn decode(&self, data: &[u8]) -> errors::Result> { 42 | bitcode::decode(data).map_err(|e| WALError::wrap(e.to_string())) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/engine/ast/dml/parts/select_item.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 6 | pub enum SelectKind { 7 | WildCard(SelectWildCard), 8 | SelectItem(SelectItem), 9 | } 10 | 11 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 12 | pub struct SelectWildCard { 13 | pub alias: Option, 14 | } 15 | 16 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] 17 | pub struct SelectItem { 18 | pub item: Option, // select 요소 19 | pub alias: Option, // as 절이 있을 경우 alias 정보 20 | } 21 | 22 | impl SelectItem { 23 | pub fn builder() -> Self { 24 | Self { 25 | item: None, 26 | alias: None, 27 | } 28 | } 29 | 30 | pub fn set_item(mut self, item: SQLExpression) -> Self { 31 | self.item = Some(item); 32 | self 33 | } 34 | 35 | pub fn set_alias(mut self, alias: String) -> Self { 36 | self.alias = Some(alias); 37 | self 38 | } 39 | 40 | pub fn build(self) -> Self { 41 | self 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/alter_database.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{DDLStatement, SQLStatement}; 2 | 3 | /* 4 | ALTER DATABASE database_name 5 | { 6 | RENAME TO new_database_name 7 | }; 8 | */ 9 | #[derive(Debug, Clone, PartialEq, Eq)] 10 | pub struct AlterDatabaseQuery { 11 | pub database_name: Option, 12 | pub action: Option, 13 | } 14 | 15 | impl AlterDatabaseQuery { 16 | pub fn builder() -> Self { 17 | AlterDatabaseQuery { 18 | database_name: None, 19 | action: None, 20 | } 21 | } 22 | 23 | pub fn set_name(mut self, name: String) -> Self { 24 | self.database_name = Some(name); 25 | self 26 | } 27 | 28 | pub fn set_action(mut self, action: AlterDatabaseAction) -> Self { 29 | self.action = Some(action); 30 | self 31 | } 32 | 33 | pub fn build(self) -> SQLStatement { 34 | SQLStatement::DDL(DDLStatement::AlterDatabase(self)) 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone, PartialEq, Eq)] 39 | pub enum AlterDatabaseAction { 40 | RenameTo(AlterDatabaseRenameTo), 41 | } 42 | 43 | #[derive(Debug, Clone, PartialEq, Eq)] 44 | pub struct AlterDatabaseRenameTo { 45 | pub name: String, 46 | } 47 | -------------------------------------------------------------------------------- /src/engine/parser/implements/other/show.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::other::show_databases::ShowDatabasesQuery; 3 | use crate::engine::ast::other::show_tables::ShowTablesQuery; 4 | use crate::engine::lexer::predule::Token; 5 | use crate::engine::parser::predule::{Parser, ParserContext}; 6 | use crate::errors::parsing_error::ParsingError; 7 | use crate::errors::{self}; 8 | 9 | impl Parser { 10 | pub(crate) fn parse_show_query( 11 | &mut self, 12 | context: ParserContext, 13 | ) -> errors::Result { 14 | if !self.has_next_token() { 15 | return Err(ParsingError::wrap("need more tokens")); 16 | } 17 | 18 | let current_token = self.get_next_token(); 19 | 20 | match current_token { 21 | Token::Databases => Ok(ShowDatabasesQuery {}.into()), 22 | Token::Tables => Ok(ShowTablesQuery { 23 | database: context.default_database.unwrap_or_else(|| "None".into()), 24 | } 25 | .into()), 26 | _ => Err(ParsingError::wrap(format!( 27 | "unexpected token '{:?}'", 28 | current_token 29 | ))), 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/pr_test.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Build & Test 2 | 3 | on: 4 | pull_request: 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | build_and_test: 11 | name: Rust project - latest 12 | strategy: 13 | max-parallel: 3 14 | matrix: 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | toolchain: 17 | - stable 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - uses: actions/cache@v3 23 | with: 24 | path: | 25 | ~/.cargo/bin 26 | ~/.cargo/registry 27 | ~/.cargo/git 28 | target 29 | key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }} 30 | 31 | - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 32 | 33 | - run: cargo build --verbose 34 | - run: cargo test --verbose 35 | 36 | - name: Upload binary as artifact 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: ${{ runner.os }}-rrdb${{ runner.os == 'Windows' && '.exe' || ''}} 40 | path: target/debug/rrdb${{ runner.os == 'Windows' && '.exe' || ''}} 41 | if-no-files-found: error 42 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod common; 3 | pub mod config; 4 | pub mod constants; 5 | pub mod engine; 6 | pub mod errors; 7 | pub mod pgwire; 8 | pub mod utils; 9 | 10 | use command::{Command, SubCommand}; 11 | 12 | use clap::Parser; 13 | 14 | use crate::{ 15 | config::launch_config::LaunchConfig, 16 | engine::{DBEngine, server::Server}, 17 | }; 18 | 19 | #[tokio::main] 20 | async fn main() -> errors::Result<()> { 21 | env_logger::init(); 22 | 23 | let args = Command::parse(); 24 | 25 | match args.action { 26 | SubCommand::Init(init) => { 27 | let config = LaunchConfig::load_from_path(None).unwrap_or_default(); 28 | 29 | let _init_option = init.init; 30 | 31 | let engine = DBEngine::new(config); 32 | 33 | engine.initialize().await?; 34 | } 35 | SubCommand::Run(run) => { 36 | let config = LaunchConfig::load_from_path(run.value.config).expect("config load error"); 37 | 38 | let server = Server::new(config); 39 | 40 | server.run().await?; 41 | } 42 | SubCommand::Client => { 43 | println!("Client"); 44 | unimplemented!(); 45 | } 46 | } 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /src/engine/parser/implements/other/backslash_command.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::ast::other::show_databases::ShowDatabasesQuery; 3 | use crate::engine::lexer::predule::Token; 4 | use crate::engine::parser::predule::{Parser, ParserContext}; 5 | use crate::errors::parsing_error::ParsingError; 6 | use crate::errors::{self}; 7 | 8 | impl Parser { 9 | pub(crate) fn parse_backslash_query( 10 | &mut self, 11 | _context: ParserContext, 12 | ) -> errors::Result { 13 | if !self.has_next_token() { 14 | return Err(ParsingError::wrap("need more tokens")); 15 | } 16 | 17 | let current_token = self.get_next_token(); 18 | 19 | match current_token { 20 | Token::Identifier(identifier) => match identifier.as_str() { 21 | "l" => Ok(ShowDatabasesQuery {}.into()), 22 | _ => Err(ParsingError::wrap(format!( 23 | "unexpected identifier '{:?}'", 24 | identifier 25 | ))), 26 | }, 27 | _ => Err(ParsingError::wrap(format!( 28 | "unexpected token '{:?}'", 29 | current_token 30 | ))), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/engine/schema/table.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::engine::ast::{ 6 | ddl::create_table::CreateTableQuery, 7 | types::{Column, ForeignKey, TableName, UniqueKey}, 8 | }; 9 | 10 | #[derive(Deserialize, Serialize, Debug, Clone)] 11 | pub struct TableSchema { 12 | pub table: TableName, 13 | pub columns: Vec, 14 | pub primary_key: Vec, 15 | pub foreign_keys: Vec, 16 | pub unique_keys: Vec, 17 | } 18 | 19 | impl TableSchema { 20 | pub fn get_columns_map(&self) -> HashMap { 21 | HashMap::from_iter(self.columns.iter().cloned().map(|e| (e.name.clone(), e))) 22 | } 23 | 24 | pub fn get_required_columns(&self) -> Vec { 25 | self.columns 26 | .iter() 27 | .filter(|e| e.not_null && e.default.is_none()) 28 | .cloned() 29 | .collect() 30 | } 31 | } 32 | 33 | impl From for TableSchema { 34 | fn from(query: CreateTableQuery) -> Self { 35 | Self { 36 | table: query.table.unwrap(), 37 | columns: query.columns, 38 | primary_key: query.primary_key, 39 | foreign_keys: query.foreign_keys, 40 | unique_keys: query.unique_keys, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/pgwire/protocol/data_types.rs: -------------------------------------------------------------------------------- 1 | macro_rules! data_types { 2 | ($($name:ident = $oid:expr, $size: expr)*) => { 3 | #[derive(Debug, Copy, Clone)] 4 | /// Describes a Postgres data type. 5 | pub enum DataTypeOid { 6 | $( 7 | #[allow(missing_docs)] 8 | $name, 9 | )* 10 | /// A type which is not known to this crate. 11 | Unknown(u32), 12 | } 13 | 14 | impl DataTypeOid { 15 | /// Fetch the size in bytes for this data type. 16 | /// Variably-sized types return -1. 17 | pub fn size_bytes(&self) -> i16 { 18 | match self { 19 | $( 20 | Self::$name => $size, 21 | )* 22 | Self::Unknown(_) => unimplemented!(), 23 | } 24 | } 25 | } 26 | 27 | impl From for DataTypeOid { 28 | fn from(value: u32) -> Self { 29 | match value { 30 | $( 31 | $oid => Self::$name, 32 | )* 33 | other => Self::Unknown(other), 34 | } 35 | } 36 | } 37 | 38 | impl From for u32 { 39 | fn from(value: DataTypeOid) -> Self { 40 | match value { 41 | $( 42 | DataTypeOid::$name => $oid, 43 | )* 44 | DataTypeOid::Unknown(other) => other, 45 | } 46 | } 47 | } 48 | }; 49 | } 50 | 51 | // For oid see: 52 | // https://github.com/sfackler/rust-postgres/blob/master/postgres-types/src/type_gen.rs 53 | data_types! { 54 | Unspecified = 0, 0 55 | 56 | Bool = 16, 1 57 | 58 | Int2 = 21, 2 59 | Int4 = 23, 4 60 | Int8 = 20, 8 61 | 62 | Float4 = 700, 4 63 | Float8 = 701, 8 64 | 65 | Date = 1082, 4 66 | Timestamp = 1114, 8 67 | 68 | Text = 25, -1 69 | } 70 | -------------------------------------------------------------------------------- /src/engine/parser/test/create_table.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use crate::engine::ast::ddl::create_table::CreateTableQuery; 4 | use crate::engine::ast::types::{Column, DataType, TableName}; 5 | use crate::engine::parser::context::ParserContext; 6 | use crate::engine::parser::predule::Parser; 7 | 8 | #[test] 9 | pub fn create_table() { 10 | let text = r#" 11 | CREATE TABLE "test_db".person 12 | ( 13 | id INTEGER PRIMARY KEY, 14 | name varchar(100), 15 | age INTEGER 16 | ); 17 | "# 18 | .to_owned(); 19 | 20 | let mut parser = Parser::with_string(text).unwrap(); 21 | 22 | let expected = CreateTableQuery::builder() 23 | .set_table(TableName::new( 24 | Some("test_db".to_owned()), 25 | "person".to_owned(), 26 | )) 27 | .add_column( 28 | Column::builder() 29 | .set_name("id".to_owned()) 30 | .set_data_type(DataType::Int) 31 | .set_primary_key(true) 32 | .build(), 33 | ) 34 | .add_column( 35 | Column::builder() 36 | .set_name("name".to_owned()) 37 | .set_data_type(DataType::Varchar(100)) 38 | .build(), 39 | ) 40 | .add_column( 41 | Column::builder() 42 | .set_name("age".to_owned()) 43 | .set_data_type(DataType::Int) 44 | .build(), 45 | ) 46 | .build(); 47 | 48 | assert_eq!( 49 | parser.parse(ParserContext::default()).unwrap(), 50 | vec![expected], 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v**' 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | create-release: 13 | name: Create release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - run: gh release create ${{ github.ref_name }} 19 | continue-on-error: true 20 | env: 21 | GH_TOKEN: ${{ github.token }} 22 | upload-binaries-to-release: 23 | name: Upload 24 | strategy: 25 | max-parallel: 3 26 | matrix: 27 | os: [ubuntu-latest, macos-latest, windows-latest] 28 | toolchain: 29 | - stable 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: actions/cache@v3 35 | with: 36 | path: | 37 | ~/.cargo/bin 38 | ~/.cargo/registry 39 | ~/.cargo/git 40 | target 41 | key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }} 42 | 43 | - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 44 | 45 | - run: cargo build --verbose --release 46 | 47 | - name: Upload binary to release 48 | continue-on-error: true 49 | run: > 50 | mv $file $name && 51 | gh release upload ${{ github.ref_name }} $name 52 | shell: 53 | bash 54 | env: 55 | GH_TOKEN: ${{ github.token }} 56 | name: ${{ runner.os }}-rrdb${{ runner.os == 'Windows' && '.exe' || ''}} 57 | file: target/release/rrdb${{ runner.os == 'Windows' && '.exe' || ''}} 58 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/not_between.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | // a NOT BETWEEN x AND y 6 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 7 | pub struct NotBetweenExpression { 8 | pub a: SQLExpression, 9 | pub x: SQLExpression, 10 | pub y: SQLExpression, 11 | } 12 | 13 | impl From for SQLExpression { 14 | fn from(value: NotBetweenExpression) -> SQLExpression { 15 | SQLExpression::NotBetween(Box::new(value)) 16 | } 17 | } 18 | 19 | impl From> for SQLExpression { 20 | fn from(value: Box) -> SQLExpression { 21 | SQLExpression::NotBetween(value) 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | #[allow(non_snake_case)] 27 | mod tests { 28 | use super::NotBetweenExpression; 29 | use crate::engine::ast::types::SQLExpression; 30 | 31 | #[test] 32 | fn test_From_Box_NotBetweenExpression_for_SQLExpression() { 33 | let not_between = NotBetweenExpression { 34 | a: SQLExpression::String("a".into()), 35 | x: SQLExpression::String("x".into()), 36 | y: SQLExpression::String("y".into()), 37 | }; 38 | let sql_expression: SQLExpression = Box::new(not_between).into(); 39 | assert_eq!( 40 | sql_expression, 41 | SQLExpression::NotBetween(Box::new(NotBetweenExpression { 42 | a: SQLExpression::String("a".into()), 43 | x: SQLExpression::String("x".into()), 44 | y: SQLExpression::String("y".into()), 45 | })) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/engine/ast/types/data_types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | // SQL 데이터 타입 4 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 5 | #[derive(Default)] 6 | pub enum DataType { 7 | #[default] 8 | Int, 9 | Float, 10 | Boolean, 11 | Varchar(i64), 12 | } 13 | 14 | 15 | impl DataType { 16 | pub fn type_code(&self) -> isize { 17 | match self { 18 | DataType::Int => 1, 19 | DataType::Float => 2, 20 | DataType::Boolean => 3, 21 | DataType::Varchar(_) => 4, 22 | } 23 | } 24 | } 25 | 26 | impl From for String { 27 | fn from(value: DataType) -> Self { 28 | match value { 29 | DataType::Int => "integer".into(), 30 | DataType::Float => "float".into(), 31 | DataType::Boolean => "boolean".into(), 32 | DataType::Varchar(number) => format!("varchar({})", number), 33 | } 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn test_data_type_type_code() { 43 | assert_eq!(DataType::Int.type_code(), 1); 44 | assert_eq!(DataType::Float.type_code(), 2); 45 | assert_eq!(DataType::Boolean.type_code(), 3); 46 | assert_eq!(DataType::Varchar(255).type_code(), 4); 47 | } 48 | 49 | #[test] 50 | fn test_data_type_into_string() { 51 | assert_eq!(String::from(DataType::Int), "integer"); 52 | assert_eq!(String::from(DataType::Float), "float"); 53 | assert_eq!(String::from(DataType::Boolean), "boolean"); 54 | assert_eq!(String::from(DataType::Varchar(255)), "varchar(255)"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rrdb" 3 | version = "0.0.2" 4 | authors = ["myyrakle "] 5 | description = "ready" 6 | keywords = ["db", "database", "sql"] 7 | edition = "2024" 8 | license = "MIT" 9 | include = ["src/*", "Cargo.toml"] 10 | exclude = [] 11 | readme = "README.md" 12 | repository = "https://github.com/myyrakle/rrdb" 13 | documentation = "https://github.com/myyrakle/rrdb/blob/master/README.md" 14 | homepage = "https://github.com/myyrakle/rrdb/blob/master/README.md" 15 | 16 | [dependencies] 17 | tokio = { version = "1.21.0", features = ["full"] } 18 | clap = { version = "3.1.3", features = ["derive"] } 19 | atty = { version = "0.2.14", optional = true } 20 | structopt = { version = "0.3.18", optional = true } 21 | serde = { version = "1", features = ["derive"] } 22 | toml = "0.5.9" 23 | dyn-clone = "1.0.4" 24 | derive_builder = "0.10.2" 25 | path-absolutize = "3.0.13" 26 | whoami = "1.2.1" 27 | thiserror = "1.0.32" 28 | async-trait = "0.1.80" 29 | bytes = "1.2.1" 30 | tokio-util = { version = "0.7.4", features = [ "codec" ] } 31 | futures = "0.3.23" 32 | chrono = "0.4.22" 33 | bson = "2.11.0" 34 | time = "0.3.36" 35 | colored = "2.0.0" 36 | uuid = "1.1.2" 37 | itertools = "0.10.5" 38 | anyhow = "1.0.86" 39 | mockall = "0.12.1" 40 | bitcode = "0.6.3" 41 | env_logger = "0.11.8" 42 | log = "0.4.28" 43 | 44 | [target.'cfg(windows)'.dependencies] 45 | winreg = "0.10.1" 46 | 47 | [[bin]] 48 | name = "rrdb" 49 | path = "./src/main.rs" 50 | required-features = ["rrdb"] 51 | 52 | [[bin]] 53 | name = "test" 54 | path = "./src/test.rs" 55 | 56 | [features] 57 | default = ["rrdb"] 58 | rrdb = ["cli"] 59 | cli = ["atty", "structopt"] 60 | 61 | [lints.clippy] 62 | to_string_trait_impl = "allow" 63 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/delete/delete_plan.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::dml::plan::select::filter::FilterPlan; 2 | 3 | use super::from::DeleteFromPlan; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct DeletePlan { 7 | pub list: Vec, 8 | } 9 | 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub enum DeletePlanItem { 12 | DeleteFrom(DeleteFromPlan), 13 | Filter(FilterPlan), 14 | } 15 | 16 | impl From for DeletePlanItem { 17 | fn from(value: FilterPlan) -> DeletePlanItem { 18 | DeletePlanItem::Filter(value) 19 | } 20 | } 21 | 22 | impl From for DeletePlanItem { 23 | fn from(value: DeleteFromPlan) -> DeletePlanItem { 24 | DeletePlanItem::DeleteFrom(value) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | #[allow(non_snake_case)] 30 | mod tests { 31 | use crate::engine::ast::{ 32 | dml::plan::select::scan::ScanType, 33 | types::{SQLExpression, TableName}, 34 | }; 35 | 36 | use super::*; 37 | 38 | #[test] 39 | fn From_FilterPlan_for_DeletePlanItem() { 40 | use super::DeletePlanItem; 41 | 42 | let filter = FilterPlan { 43 | expression: SQLExpression::String("a".into()), 44 | }; 45 | let delete_plan_item: DeletePlanItem = filter.clone().into(); 46 | assert_eq!(delete_plan_item, DeletePlanItem::Filter(filter)); 47 | } 48 | 49 | #[test] 50 | fn From_DeleteFromPlan_for_DeletePlanItem() { 51 | use super::DeleteFromPlan; 52 | 53 | let delete_from = DeleteFromPlan { 54 | table_name: TableName::new(None, "table".into()), 55 | alias: None, 56 | scan: ScanType::FullScan, 57 | }; 58 | let delete_plan_item: DeletePlanItem = delete_from.clone().into(); 59 | assert_eq!(delete_plan_item, DeletePlanItem::DeleteFrom(delete_from)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/engine/ast/dml/plan/update/update_plan.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::dml::plan::select::filter::FilterPlan; 2 | 3 | use super::from::UpdateFromPlan; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct UpdatePlan { 7 | pub list: Vec, 8 | } 9 | 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub enum UpdatePlanItem { 12 | UpdateFrom(UpdateFromPlan), 13 | Filter(FilterPlan), 14 | } 15 | 16 | impl From for UpdatePlanItem { 17 | fn from(value: FilterPlan) -> UpdatePlanItem { 18 | UpdatePlanItem::Filter(value) 19 | } 20 | } 21 | 22 | impl From for UpdatePlanItem { 23 | fn from(value: UpdateFromPlan) -> UpdatePlanItem { 24 | UpdatePlanItem::UpdateFrom(value) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | #[allow(non_snake_case)] 30 | mod tests { 31 | use crate::engine::ast::{ 32 | dml::plan::select::scan::ScanType, 33 | types::{SQLExpression, TableName}, 34 | }; 35 | 36 | use super::*; 37 | 38 | #[test] 39 | fn From_FilterPlan_for_UpdatePlanItem() { 40 | use super::UpdatePlanItem; 41 | 42 | let filter = FilterPlan { 43 | expression: SQLExpression::String("a".into()), 44 | }; 45 | let update_plan_item: UpdatePlanItem = filter.clone().into(); 46 | assert_eq!(update_plan_item, UpdatePlanItem::Filter(filter)); 47 | } 48 | 49 | #[test] 50 | fn From_UpdateFromPlan_for_UpdatePlanItem() { 51 | use super::UpdateFromPlan; 52 | 53 | let update_from = UpdateFromPlan { 54 | table_name: TableName::new(None, "table".into()), 55 | alias: None, 56 | scan: ScanType::FullScan, 57 | }; 58 | let update_plan_item: UpdatePlanItem = update_from.clone().into(); 59 | assert_eq!(update_plan_item, UpdatePlanItem::UpdateFrom(update_from)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/engine/actions/ddl/drop_database.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind as IOErrorKind; 2 | 3 | use crate::engine::DBEngine; 4 | 5 | use crate::engine::ast::ddl::drop_database::DropDatabaseQuery; 6 | use crate::engine::types::{ 7 | ExecuteColumn, ExecuteColumnType, ExecuteField, ExecuteResult, ExecuteRow, 8 | }; 9 | use crate::errors::execute_error::ExecuteError; 10 | use crate::errors; 11 | 12 | impl DBEngine { 13 | pub async fn drop_database(&self, query: DropDatabaseQuery) -> errors::Result { 14 | let base_path = self.get_data_directory(); 15 | let mut database_path = base_path.clone(); 16 | 17 | let database_name = query 18 | .database_name 19 | .clone() 20 | .ok_or_else(|| ExecuteError::wrap("no database name".to_string()))?; 21 | 22 | database_path.push(&database_name); 23 | 24 | if let Err(error) = tokio::fs::remove_dir_all(database_path.clone()).await { 25 | match error.kind() { 26 | IOErrorKind::NotFound => { 27 | return Err(ExecuteError::wrap( 28 | "database not found".to_string(), 29 | )); 30 | } 31 | _ => { 32 | return Err(ExecuteError::wrap( 33 | "database drop failed".to_string(), 34 | )); 35 | } 36 | } 37 | } 38 | 39 | Ok(ExecuteResult { 40 | columns: (vec![ExecuteColumn { 41 | name: "desc".into(), 42 | data_type: ExecuteColumnType::String, 43 | }]), 44 | rows: (vec![ExecuteRow { 45 | fields: vec![ExecuteField::String(format!( 46 | "database dropped: {}", 47 | database_name 48 | ))], 49 | }]), 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/engine/actions/ddl/drop_table.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind as IOErrorKind; 2 | 3 | use crate::engine::DBEngine; 4 | use crate::engine::ast::ddl::drop_table::DropTableQuery; 5 | use crate::engine::ast::types::TableName; 6 | use crate::engine::types::{ 7 | ExecuteColumn, ExecuteColumnType, ExecuteField, ExecuteResult, ExecuteRow, 8 | }; 9 | use crate::errors::execute_error::ExecuteError; 10 | use crate::errors; 11 | 12 | impl DBEngine { 13 | pub async fn drop_table(&self, query: DropTableQuery) -> errors::Result { 14 | let base_path = self.get_data_directory(); 15 | 16 | let TableName { 17 | database_name, 18 | table_name, 19 | } = query.table.unwrap(); 20 | 21 | let table_path = base_path 22 | .clone() 23 | .join(database_name.unwrap()) 24 | .join("tables") 25 | .join(&table_name); 26 | 27 | if let Err(error) = tokio::fs::remove_dir_all(table_path).await { 28 | match error.kind() { 29 | IOErrorKind::NotFound => { 30 | return Err(ExecuteError::wrap( 31 | "table not found".to_string(), 32 | )); 33 | } 34 | _ => { 35 | return Err(ExecuteError::wrap( 36 | "table drop failed".to_string(), 37 | )); 38 | } 39 | } 40 | } 41 | 42 | Ok(ExecuteResult { 43 | columns: (vec![ExecuteColumn { 44 | name: "desc".into(), 45 | data_type: ExecuteColumnType::String, 46 | }]), 47 | rows: (vec![ExecuteRow { 48 | fields: vec![ExecuteField::String(format!( 49 | "table dropped: {}", 50 | table_name 51 | ))], 52 | }]), 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pgwire/protocol/message/backend/types/error_response.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use bytes::{BufMut, BytesMut}; 4 | 5 | use crate::pgwire::protocol::{backend::BackendMessage, Severity, SqlState}; 6 | 7 | #[derive(thiserror::Error, Debug, Clone)] 8 | pub struct ErrorResponse { 9 | pub sql_state: SqlState, 10 | pub severity: Severity, 11 | pub message: String, 12 | } 13 | 14 | impl ErrorResponse { 15 | pub fn new(sql_state: SqlState, severity: Severity, message: impl Into) -> Self { 16 | ErrorResponse { 17 | sql_state, 18 | severity, 19 | message: message.into(), 20 | } 21 | } 22 | 23 | pub fn error(sql_state: SqlState, message: impl Into) -> Self { 24 | Self::new(sql_state, Severity::ERROR, message) 25 | } 26 | 27 | pub fn fatal(sql_state: SqlState, message: impl Into) -> Self { 28 | Self::new(sql_state, Severity::FATAL, message) 29 | } 30 | } 31 | 32 | impl Display for ErrorResponse { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | write!(f, "error") 35 | } 36 | } 37 | 38 | impl From> for ErrorResponse { 39 | fn from(value: Box) -> ErrorResponse { 40 | ErrorResponse { 41 | sql_state: SqlState::SYNTAX_ERROR, 42 | severity: Severity::ERROR, 43 | message: value.to_string(), 44 | } 45 | } 46 | } 47 | 48 | impl BackendMessage for ErrorResponse { 49 | const TAG: u8 = b'E'; 50 | 51 | fn encode(&self, dst: &mut BytesMut) { 52 | dst.put_u8(b'C'); 53 | dst.put_slice(self.sql_state.0.as_bytes()); 54 | dst.put_u8(0); 55 | dst.put_u8(b'S'); 56 | dst.put_slice(self.severity.0.as_bytes()); 57 | dst.put_u8(0); 58 | dst.put_u8(b'M'); 59 | dst.put_slice(self.message.as_bytes()); 60 | dst.put_u8(0); 61 | 62 | dst.put_u8(0); // tag 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/pgwire/protocol/extension/data_row_batch.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use tokio_util::codec::Encoder; 3 | 4 | use crate::pgwire::protocol::{ 5 | ConnectionCodec, FormatCode, ProtocolError, backend::RowDescription, 6 | }; 7 | 8 | use super::data_row_writer::DataRowWriter; 9 | 10 | /// Supports batched rows for e.g. returning portal result sets. 11 | /// 12 | /// NB: this struct only performs limited validation of column consistency across rows. 13 | pub struct DataRowBatch { 14 | pub(crate) format_code: FormatCode, 15 | pub(crate) num_cols: usize, 16 | pub(crate) num_rows: usize, 17 | pub(crate) data: BytesMut, 18 | pub(crate) row: BytesMut, 19 | } 20 | 21 | impl DataRowBatch { 22 | /// Creates a new row batch using the given format code, requiring a certain number of columns per row. 23 | pub fn new(format_code: FormatCode, num_cols: usize) -> Self { 24 | Self { 25 | format_code, 26 | num_cols, 27 | num_rows: 0, 28 | data: BytesMut::new(), 29 | row: BytesMut::new(), 30 | } 31 | } 32 | 33 | /// Creates a [DataRowBatch] from the given [RowDescription]. 34 | pub fn from_row_desc(desc: &RowDescription) -> Self { 35 | Self::new(desc.format_code, desc.fields.len()) 36 | } 37 | 38 | /// Starts writing a new row. 39 | /// 40 | /// Returns a [DataRowWriter] that is responsible for the actual value encoding. 41 | pub fn create_row(&mut self) -> DataRowWriter<'_> { 42 | self.num_rows += 1; 43 | DataRowWriter::new(self) 44 | } 45 | 46 | /// Returns the number of rows currently written to this batch. 47 | pub fn num_rows(&self) -> usize { 48 | self.num_rows 49 | } 50 | } 51 | 52 | impl Encoder for ConnectionCodec { 53 | type Error = ProtocolError; 54 | 55 | fn encode(&mut self, item: DataRowBatch, dst: &mut BytesMut) -> Result<(), Self::Error> { 56 | dst.extend(item.data); 57 | Ok(()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/config/launch_config.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::constants::{ 6 | DEFAULT_CONFIG_BASEPATH, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIRNAME, DEFAULT_WAL_DIRNAME, 7 | DEFAULT_WAL_EXTENSION, 8 | }; 9 | 10 | #[derive(Deserialize, Serialize, Debug, Clone)] 11 | pub struct LaunchConfig { 12 | pub port: u32, 13 | pub host: String, 14 | pub data_directory: String, 15 | 16 | pub wal_enabled: bool, 17 | pub wal_directory: String, 18 | pub wal_segment_size: u32, 19 | pub wal_extension: String, 20 | } 21 | 22 | #[allow(clippy::derivable_impls)] 23 | impl std::default::Default for LaunchConfig { 24 | fn default() -> Self { 25 | let base_path = PathBuf::from(DEFAULT_CONFIG_BASEPATH); 26 | 27 | Self { 28 | port: 22208, 29 | host: "0.0.0.0".to_string(), 30 | data_directory: base_path 31 | .join(DEFAULT_DATA_DIRNAME) 32 | .to_str() 33 | .unwrap() 34 | .to_string(), 35 | wal_enabled: true, 36 | wal_directory: base_path 37 | .join(DEFAULT_WAL_DIRNAME) 38 | .to_str() 39 | .unwrap() 40 | .to_string(), 41 | wal_segment_size: 1024 * 1024 * 16, // 16MB 세그먼트 사이즈 42 | wal_extension: DEFAULT_WAL_EXTENSION.to_string(), 43 | } 44 | } 45 | } 46 | 47 | impl LaunchConfig { 48 | pub fn default_config_path() -> PathBuf { 49 | let base_path = PathBuf::from(DEFAULT_CONFIG_BASEPATH); 50 | base_path.join(DEFAULT_CONFIG_FILENAME) 51 | } 52 | 53 | pub fn load_from_path(filepath: Option) -> anyhow::Result { 54 | let filepath = match filepath { 55 | Some(path) => PathBuf::from(path), 56 | None => Self::default_config_path(), 57 | }; 58 | 59 | let config = std::fs::read_to_string(filepath)?; 60 | let decoded = toml::from_str(&config)?; 61 | 62 | Ok(decoded) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | // 기본 데이터베이스 이름 2 | pub const DEFAULT_DATABASE_NAME: &str = "rrdb"; 3 | 4 | // 기본 설정파일 이름. 5 | pub const DEFAULT_CONFIG_FILENAME: &str = "rrdb.config"; 6 | 7 | // 기본 Data 디렉터리 이름 8 | pub const DEFAULT_DATA_DIRNAME: &str = "data"; 9 | 10 | // 기본 WAL 디렉터리 이름 11 | pub const DEFAULT_WAL_DIRNAME: &str = "wal"; 12 | 13 | // 기본 WAL 확장자 14 | pub const DEFAULT_WAL_EXTENSION: &str = "log"; 15 | 16 | // 운영체제별 기본 저장 경로를 반환합니다. 17 | #[cfg(target_os = "linux")] 18 | pub const DEFAULT_CONFIG_BASEPATH: &str = "/var/lib/rrdb"; 19 | 20 | #[cfg(target_os = "windows")] 21 | pub const DEFAULT_CONFIG_BASEPATH: &str = r"C:\Program Files\rrdb"; 22 | 23 | #[cfg(target_os = "macos")] 24 | pub const DEFAULT_CONFIG_BASEPATH: &str = "/var/lib/rrdb"; 25 | 26 | pub const LAUNCHD_PLIST_PATH: &str = "/Library/LaunchDaemons/io.github.myyrakle.rrdb.plist"; 27 | 28 | #[cfg(target_os = "linux")] 29 | pub const SYSTEMD_DAEMON_SCRIPT: &str = r#"[Unit] 30 | Description=RRDB 31 | 32 | [Service] 33 | Type=simple 34 | Restart=on-failure 35 | ExecStart=/usr/bin/rrdb run 36 | RemainAfterExit=on 37 | User=root 38 | StandardOutput=file:/var/log/rrdb.stdout.log 39 | StandardError=file:/var/log/rrdb.stderr.log 40 | 41 | [Install] 42 | WantedBy=multi-user.target"#; 43 | 44 | #[cfg(target_os = "macos")] 45 | pub const LAUNCHD_DAEMON_SCRIPT: &str = r#" 46 | 47 | 48 | 49 | Label 50 | myyrakle.github.io.rrdb 51 | UserName 52 | root 53 | Program 54 | /usr/local/bin/rrdb 55 | ProgramArguments 56 | 57 | run 58 | 59 | RunAtLoad 60 | 61 | StandardOutPath 62 | /var/log/rrdb.stdout.log 63 | StandardErrorPath 64 | /var/log/rrdb.stderr.log 65 | 66 | "#; 67 | -------------------------------------------------------------------------------- /src/engine/ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commands; 2 | pub mod dcl; 3 | pub mod ddl; 4 | pub mod dml; 5 | pub mod other; 6 | pub mod tcl; 7 | pub mod types; 8 | 9 | use crate::engine::ast::{ 10 | ddl::{ 11 | alter_database::AlterDatabaseQuery, alter_table::AlterTableQuery, 12 | create_database::CreateDatabaseQuery, create_index::CreateIndexQuery, 13 | create_table::CreateTableQuery, drop_database::DropDatabaseQuery, 14 | drop_table::DropTableQuery, 15 | }, 16 | dml::{delete::DeleteQuery, insert::InsertQuery, select::SelectQuery, update::UpdateQuery}, 17 | other::{ 18 | desc_table::DescTableQuery, show_databases::ShowDatabasesQuery, 19 | show_tables::ShowTablesQuery, use_database::UseDatabaseQuery, 20 | }, 21 | }; 22 | 23 | use self::tcl::{BeginTransactionQuery, CommitQuery, RollbackQuery}; 24 | 25 | #[derive(Clone, Debug, PartialEq, Default)] 26 | pub enum SQLStatement { 27 | DDL(DDLStatement), 28 | DML(DMLStatement), 29 | DCL(DCLStatement), 30 | TCL(TCLStatement), 31 | Other(OtherStatement), 32 | #[default] 33 | None, 34 | } 35 | 36 | #[derive(Clone, Debug, PartialEq)] 37 | pub enum DDLStatement { 38 | CreateDatabaseQuery(CreateDatabaseQuery), 39 | AlterDatabase(AlterDatabaseQuery), 40 | DropDatabaseQuery(DropDatabaseQuery), 41 | CreateTableQuery(CreateTableQuery), 42 | AlterTableQuery(AlterTableQuery), 43 | DropTableQuery(DropTableQuery), 44 | CreateIndexQuery(CreateIndexQuery), 45 | } 46 | 47 | #[derive(Clone, Debug, PartialEq)] 48 | pub enum DMLStatement { 49 | InsertQuery(InsertQuery), 50 | UpdateQuery(UpdateQuery), 51 | DeleteQuery(DeleteQuery), 52 | SelectQuery(SelectQuery), 53 | } 54 | 55 | #[derive(Clone, Debug, PartialEq, Eq)] 56 | pub enum DCLStatement {} 57 | 58 | #[derive(Clone, Debug, PartialEq, Eq)] 59 | pub enum OtherStatement { 60 | ShowDatabases(ShowDatabasesQuery), 61 | UseDatabase(UseDatabaseQuery), 62 | ShowTables(ShowTablesQuery), 63 | DescTable(DescTableQuery), 64 | } 65 | 66 | #[derive(Clone, Debug, PartialEq, Eq)] 67 | pub enum TCLStatement { 68 | BeginTransaction(BeginTransactionQuery), 69 | Commit(CommitQuery), 70 | Rollback(RollbackQuery), 71 | } 72 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/unary.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::operators::UnaryOperator; 5 | 6 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 7 | pub struct UnaryOperatorExpression { 8 | pub operator: UnaryOperator, 9 | pub operand: SQLExpression, 10 | } 11 | 12 | impl From for SQLExpression { 13 | fn from(value: UnaryOperatorExpression) -> SQLExpression { 14 | SQLExpression::Unary(Box::new(value)) 15 | } 16 | } 17 | 18 | impl From for Option> { 19 | fn from(value: UnaryOperatorExpression) -> Option> { 20 | Some(Box::new(SQLExpression::Unary(Box::new(value)))) 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | #[allow(non_snake_case)] 26 | mod tests { 27 | use super::UnaryOperatorExpression; 28 | use crate::engine::ast::dml::expressions::operators::UnaryOperator; 29 | use crate::engine::ast::types::SQLExpression; 30 | 31 | #[test] 32 | fn test_From_UnaryOperatorExpression_for_SQLExpression() { 33 | let unary = UnaryOperatorExpression { 34 | operator: UnaryOperator::Neg, 35 | operand: SQLExpression::Integer(1), 36 | }; 37 | let sql_expression: SQLExpression = unary.into(); 38 | assert_eq!( 39 | sql_expression, 40 | SQLExpression::Unary(Box::new(UnaryOperatorExpression { 41 | operator: UnaryOperator::Neg, 42 | operand: SQLExpression::Integer(1), 43 | })) 44 | ); 45 | } 46 | 47 | #[test] 48 | fn test_From_UnaryOperatorExpression_for_Option_Box_SQLExpression() { 49 | let unary = UnaryOperatorExpression { 50 | operator: UnaryOperator::Neg, 51 | operand: SQLExpression::Integer(1), 52 | }; 53 | let sql_expression: Option> = unary.into(); 54 | 55 | assert_eq!( 56 | sql_expression, 57 | Some(Box::new(SQLExpression::Unary(Box::new( 58 | UnaryOperatorExpression { 59 | operator: UnaryOperator::Neg, 60 | operand: SQLExpression::Integer(1), 61 | } 62 | )))) 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/engine/parser/implements/dml/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::dml::delete::DeleteQuery; 2 | use crate::engine::lexer::predule::Token; 3 | use crate::engine::parser::predule::{Parser, ParserContext}; 4 | use crate::errors::parsing_error::ParsingError; 5 | use crate::errors; 6 | 7 | impl Parser { 8 | pub(crate) fn handle_delete_query( 9 | &mut self, 10 | context: ParserContext, 11 | ) -> errors::Result { 12 | if !self.has_next_token() { 13 | return Err(ParsingError::wrap( 14 | "need more tokens".to_string(), 15 | )); 16 | } 17 | 18 | // DELETE 토큰 삼키기 19 | let current_token = self.get_next_token(); 20 | 21 | if current_token != Token::Delete { 22 | return Err(ParsingError::wrap(format!( 23 | "expected 'DELETE'. but your input word is '{:?}'", 24 | current_token 25 | ))); 26 | } 27 | 28 | if !self.has_next_token() { 29 | return Err(ParsingError::wrap( 30 | "need more tokens".to_string(), 31 | )); 32 | } 33 | 34 | // FROM 토큰 삼키기 35 | let current_token = self.get_next_token(); 36 | 37 | if current_token != Token::From { 38 | return Err(ParsingError::wrap(format!( 39 | "expected 'FROM'. but your input word is '{:?}'", 40 | current_token 41 | ))); 42 | } 43 | 44 | let mut query_builder = DeleteQuery::builder(); 45 | 46 | if !self.has_next_token() { 47 | return Err(ParsingError::wrap( 48 | "need more tokens".to_string(), 49 | )); 50 | } 51 | 52 | // 테이블명 파싱 53 | let table_name = self.parse_table_name(context.clone())?; 54 | query_builder = query_builder.set_from_table(table_name); 55 | 56 | // 테이블 alias 파싱 57 | if self.next_token_is_table_alias() { 58 | let alias = self.parse_table_alias()?; 59 | query_builder = query_builder.set_from_alias(alias); 60 | } 61 | 62 | // WHERE 절 파싱 63 | if self.next_token_is_where() { 64 | let where_clause = self.parse_where(context)?; 65 | query_builder = query_builder.set_where(where_clause); 66 | } 67 | 68 | Ok(query_builder.build()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/binary.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::SQLExpression; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::operators::BinaryOperator; 5 | 6 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 7 | pub struct BinaryOperatorExpression { 8 | pub operator: BinaryOperator, 9 | pub lhs: SQLExpression, 10 | pub rhs: SQLExpression, 11 | } 12 | 13 | impl From for SQLExpression { 14 | fn from(value: BinaryOperatorExpression) -> SQLExpression { 15 | SQLExpression::Binary(Box::new(value)) 16 | } 17 | } 18 | 19 | impl From> for SQLExpression { 20 | fn from(value: Box) -> SQLExpression { 21 | SQLExpression::Binary(value) 22 | } 23 | } 24 | 25 | impl From for Option { 26 | fn from(value: BinaryOperatorExpression) -> Option { 27 | Some(SQLExpression::Binary(Box::new(value))) 28 | } 29 | } 30 | 31 | impl From for Box { 32 | fn from(value: BinaryOperatorExpression) -> Box { 33 | Box::new(SQLExpression::Binary(Box::new(value))) 34 | } 35 | } 36 | 37 | impl From for Option> { 38 | fn from(value: BinaryOperatorExpression) -> Option> { 39 | Some(Box::new(SQLExpression::Binary(Box::new(value)))) 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | #[allow(non_snake_case)] 45 | mod tests { 46 | use crate::engine::ast::dml::expressions::operators::BinaryOperator; 47 | 48 | #[test] 49 | fn test_From_BinaryOperatorExpression_for_Option_Box_SQLExpression() { 50 | use crate::engine::ast::dml::expressions::binary::BinaryOperatorExpression; 51 | use crate::engine::ast::types::SQLExpression; 52 | use std::convert::From; 53 | 54 | let binary_operator_expression = BinaryOperatorExpression { 55 | operator: BinaryOperator::Add, 56 | lhs: SQLExpression::Integer(1), 57 | rhs: SQLExpression::Integer(2), 58 | }; 59 | let res: Option> = From::from(binary_operator_expression.clone()); 60 | 61 | assert_eq!( 62 | res, 63 | Some(Box::new(SQLExpression::Binary(Box::new( 64 | binary_operator_expression 65 | )))) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/engine/parser/implements/ddl/top_level.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::SQLStatement; 2 | use crate::engine::lexer::predule::Token; 3 | use crate::engine::parser::context::ParserContext; 4 | use crate::engine::parser::predule::Parser; 5 | use crate::errors; 6 | use crate::errors::parsing_error::ParsingError; 7 | 8 | impl Parser { 9 | // CREATE...로 시작되는 쿼리 분석 10 | pub(crate) fn handle_create_query( 11 | &mut self, 12 | context: ParserContext, 13 | ) -> errors::Result { 14 | if !self.has_next_token() { 15 | return Err(ParsingError::wrap("need more tokens".to_string())); 16 | } 17 | 18 | let current_token = self.get_next_token(); 19 | 20 | match current_token { 21 | Token::Table => self.handle_create_table_query(context), 22 | Token::Database => self.handle_create_database_query(), 23 | _ => Err(ParsingError::wrap(format!( 24 | "not supported command. possible commands: (create table, create database). but your input is {:?}", 25 | current_token 26 | ))), 27 | } 28 | } 29 | 30 | // ALTER TABLE... 31 | pub(crate) fn handle_alter_query( 32 | &mut self, 33 | context: ParserContext, 34 | ) -> errors::Result { 35 | if !self.has_next_token() { 36 | return Err(ParsingError::wrap("need more tokens".to_string())); 37 | } 38 | 39 | let current_token = self.get_next_token(); 40 | 41 | match current_token { 42 | Token::Table => self.handle_alter_table_query(context), 43 | Token::Database => self.handle_alter_database_query(), 44 | _ => Err(ParsingError::wrap( 45 | "not supported command. possible commands: (alter table, alter database)", 46 | )), 47 | } 48 | } 49 | 50 | pub(crate) fn handle_drop_query( 51 | &mut self, 52 | context: ParserContext, 53 | ) -> errors::Result { 54 | if !self.has_next_token() { 55 | return Err(ParsingError::wrap("need more tokens".to_string())); 56 | } 57 | 58 | let current_token = self.get_next_token(); 59 | 60 | match current_token { 61 | Token::Table => self.handle_drop_table_query(context), 62 | Token::Database => self.handle_drop_database_query(), 63 | _ => Err(ParsingError::wrap( 64 | "not supported command. possible commands: (drop table, drop database)", 65 | )), 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/engine/ast/dml/insert.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{types::TableName, DMLStatement, SQLStatement}; 2 | 3 | use super::{parts::insert_values::InsertValue, select::SelectQuery}; 4 | 5 | #[derive(Clone, Debug, PartialEq, Default)] 6 | pub struct InsertQuery { 7 | pub into_table: Option, 8 | pub columns: Vec, 9 | pub data: InsertData, 10 | } 11 | 12 | #[derive(Clone, Debug, PartialEq)] 13 | pub enum InsertData { 14 | Select(Box), 15 | Values(Vec), 16 | None, 17 | } 18 | 19 | impl Default for InsertData { 20 | fn default() -> Self { 21 | Self::None 22 | } 23 | } 24 | 25 | impl InsertQuery { 26 | pub fn builder() -> Self { 27 | Self { 28 | columns: vec![], 29 | into_table: None, 30 | data: InsertData::None, 31 | } 32 | } 33 | 34 | pub fn set_into_table(mut self, from: TableName) -> Self { 35 | self.into_table = Some(from); 36 | self 37 | } 38 | 39 | pub fn set_columns(mut self, columns: Vec) -> Self { 40 | self.columns = columns; 41 | self 42 | } 43 | 44 | pub fn set_values(mut self, values: Vec) -> Self { 45 | self.data = InsertData::Values(values); 46 | self 47 | } 48 | 49 | pub fn set_select(mut self, select: SelectQuery) -> Self { 50 | self.data = InsertData::Select(Box::new(select)); 51 | self 52 | } 53 | 54 | pub fn build(self) -> Self { 55 | self 56 | } 57 | } 58 | 59 | impl From for SQLStatement { 60 | fn from(value: InsertQuery) -> SQLStatement { 61 | SQLStatement::DML(DMLStatement::InsertQuery(value)) 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | #[allow(non_snake_case)] 67 | mod tests { 68 | use crate::engine::ast::types::SQLExpression; 69 | 70 | use super::*; 71 | 72 | #[test] 73 | fn test_From_InsertQuery_for_SQLStatement() { 74 | let insert_query = InsertQuery::builder() 75 | .set_into_table(TableName::new(None, "table".into())) 76 | .set_columns(vec!["a".into(), "b".into()]) 77 | .set_values(vec![InsertValue { 78 | list: vec![Some(SQLExpression::String("a".into()))], 79 | }]) 80 | .build(); 81 | 82 | assert_eq!( 83 | SQLStatement::from(insert_query), 84 | SQLStatement::DML(DMLStatement::InsertQuery(InsertQuery { 85 | into_table: Some(TableName::new(None, "table".into())), 86 | columns: vec!["a".into(), "b".into()], 87 | data: InsertData::Values(vec![InsertValue { 88 | list: vec![Some(SQLExpression::String("a".into()))], 89 | }]), 90 | })) 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::backtrace::Backtrace; 2 | 3 | pub mod execute_error; 4 | // pub mod into_error; 5 | pub mod lexing_error; 6 | pub mod parsing_error; 7 | // pub mod predule; 8 | // pub mod server_error; 9 | pub mod type_error; 10 | pub mod wal_errors; 11 | 12 | pub struct Errors { 13 | pub kind: ErrorKind, 14 | pub backtrace: Backtrace, 15 | pub message: Option, 16 | } 17 | 18 | impl Errors { 19 | pub fn new(kind: ErrorKind) -> Self { 20 | Errors { 21 | kind, 22 | backtrace: Backtrace::capture(), 23 | message: None, 24 | } 25 | } 26 | 27 | pub fn with_message(mut self, message: String) -> Self { 28 | self.message = Some(message); 29 | self 30 | } 31 | } 32 | 33 | #[derive(Debug, PartialEq)] 34 | pub enum ErrorKind { 35 | LexingError(String), 36 | TypeError(String), 37 | ExecuteError(String), 38 | IntoError(String), 39 | ParsingError(String), 40 | ServerError(String), 41 | WALError(String), 42 | } 43 | 44 | impl std::fmt::Display for ErrorKind { 45 | fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 46 | match self { 47 | ErrorKind::ExecuteError(msg) => write!(formatter, "{}", msg), 48 | ErrorKind::IntoError(msg) => write!(formatter, "parsing error(into error): {}", msg), 49 | ErrorKind::LexingError(msg) => write!(formatter, "lexing error: {}", msg), 50 | ErrorKind::ParsingError(msg) => write!(formatter, "parsing error: {}", msg), 51 | ErrorKind::ServerError(msg) => write!(formatter, "server error: {}", msg), 52 | ErrorKind::TypeError(msg) => write!(formatter, "type error: {}", msg), 53 | ErrorKind::WALError(msg) => write!(formatter, "wal error: {}", msg), 54 | } 55 | } 56 | } 57 | 58 | impl std::fmt::Display for Errors { 59 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 60 | if let Some(msg) = &self.message { 61 | write!(f, "{}: {}", self.kind, msg) 62 | } else { 63 | write!(f, "{}", self.kind) 64 | } 65 | } 66 | } 67 | 68 | impl std::fmt::Debug for Errors { 69 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 | if let Some(msg) = &self.message { 71 | write!(f, "{:?} = {}\n{}", self.kind, msg, self.backtrace) 72 | } else { 73 | write!(f, "{:?}\n{}", self.kind, self.backtrace) 74 | } 75 | } 76 | } 77 | 78 | impl std::error::Error for ErrorKind {} 79 | impl std::error::Error for Errors {} 80 | 81 | impl From for Errors { 82 | fn from(error_code: ErrorKind) -> Self { 83 | Errors::new(error_code) 84 | } 85 | } 86 | 87 | pub type Result = std::result::Result; 88 | -------------------------------------------------------------------------------- /src/engine/types.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | use crate::engine::ast::types::DataType; 4 | use crate::engine::schema::row::TableDataFieldType; 5 | use crate::pgwire::protocol::DataTypeOid; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct ExecuteResult { 9 | pub rows: Vec, // 데이터 행 -> 실 데이터 10 | pub columns: Vec, // 데이터 열에 대한 메타데이터 11 | } 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct ExecuteColumn { 15 | pub data_type: ExecuteColumnType, 16 | pub name: String, 17 | } 18 | 19 | #[derive(Clone, Debug, PartialEq)] 20 | pub struct ExecuteRow { 21 | pub fields: Vec, 22 | } 23 | 24 | #[derive(Clone, Debug, PartialEq, Eq)] 25 | pub enum ExecuteColumnType { 26 | Bool, 27 | Integer, 28 | Float, 29 | String, 30 | Null, 31 | } 32 | 33 | impl From for DataTypeOid { 34 | fn from(value: ExecuteColumnType) -> DataTypeOid { 35 | match value { 36 | ExecuteColumnType::Bool => DataTypeOid::Bool, 37 | ExecuteColumnType::Integer => DataTypeOid::Int8, 38 | ExecuteColumnType::Float => DataTypeOid::Float8, 39 | ExecuteColumnType::String => DataTypeOid::Text, 40 | ExecuteColumnType::Null => DataTypeOid::Unspecified, 41 | } 42 | } 43 | } 44 | 45 | impl From for ExecuteColumnType { 46 | fn from(value: DataType) -> ExecuteColumnType { 47 | match value { 48 | DataType::Boolean => ExecuteColumnType::Bool, 49 | DataType::Int => ExecuteColumnType::Integer, 50 | DataType::Float => ExecuteColumnType::Float, 51 | DataType::Varchar(_) => ExecuteColumnType::String, 52 | } 53 | } 54 | } 55 | 56 | #[derive(Clone, Debug, PartialEq)] 57 | pub enum ExecuteField { 58 | Bool(bool), 59 | Integer(i64), 60 | Float(f64), 61 | String(String), 62 | Null, 63 | } 64 | 65 | impl From for ExecuteField { 66 | fn from(value: TableDataFieldType) -> ExecuteField { 67 | #[allow(unstable_name_collisions)] 68 | match value { 69 | TableDataFieldType::Boolean(value) => ExecuteField::Bool(value), 70 | TableDataFieldType::Integer(value) => ExecuteField::Integer(value), 71 | TableDataFieldType::Float(value) => ExecuteField::Float(value.into()), 72 | TableDataFieldType::String(value) => ExecuteField::String(value), 73 | TableDataFieldType::Array(value) => ExecuteField::String( 74 | value 75 | .iter() 76 | .map(|e| e.to_string()) 77 | .intersperse(", ".to_owned()) 78 | .collect(), 79 | ), 80 | TableDataFieldType::Null => ExecuteField::Null, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/create_index.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{ 2 | types::{Column, TableName}, 3 | DDLStatement, SQLStatement, 4 | }; 5 | 6 | /* 7 | CREATE [ UNIQUE ] INDEX [ IF NOT EXISTS ] name ON table_name 8 | ( column_name [, ...] ) 9 | */ 10 | 11 | #[derive(Clone, Debug, PartialEq)] 12 | pub struct CreateIndexQuery { 13 | pub index_name: String, 14 | pub table: TableName, 15 | pub columns: Vec, 16 | pub is_unique: bool, 17 | pub if_not_exists: bool, 18 | } 19 | 20 | impl CreateIndexQuery { 21 | pub fn builder() -> Self { 22 | Self { 23 | table: Default::default(), 24 | columns: vec![], 25 | is_unique: false, 26 | if_not_exists: false, 27 | index_name: "".into(), 28 | } 29 | } 30 | 31 | pub fn set_table(mut self, table: TableName) -> Self { 32 | self.table = table; 33 | self 34 | } 35 | 36 | pub fn set_index_name(mut self, index_name: String) -> Self { 37 | self.index_name = index_name; 38 | self 39 | } 40 | 41 | pub fn add_column(mut self, column: Column) -> Self { 42 | self.columns.push(column); 43 | self 44 | } 45 | 46 | pub fn set_unique(mut self, unique: bool) -> Self { 47 | self.is_unique = unique; 48 | self 49 | } 50 | 51 | pub fn set_if_not_exists(mut self, if_not_exists: bool) -> Self { 52 | self.if_not_exists = if_not_exists; 53 | self 54 | } 55 | 56 | pub fn build(self) -> SQLStatement { 57 | SQLStatement::DDL(DDLStatement::CreateIndexQuery(self)) 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use crate::engine::ast::types::DataType; 64 | 65 | use super::*; 66 | 67 | #[test] 68 | fn test_create_index() { 69 | let query = CreateIndexQuery::builder() 70 | .set_table(TableName::new(None, "table_name".into())) 71 | .set_index_name("index_name".into()) 72 | .add_column( 73 | Column::builder() 74 | .set_name("column_name".into()) 75 | .set_data_type(DataType::Boolean) 76 | .build(), 77 | ) 78 | .set_unique(true) 79 | .set_if_not_exists(true) 80 | .build(); 81 | 82 | let expected = SQLStatement::DDL(DDLStatement::CreateIndexQuery(CreateIndexQuery { 83 | table: TableName::new(None, "table_name".into()), 84 | index_name: "index_name".into(), 85 | columns: vec![Column::builder() 86 | .set_name("column_name".into()) 87 | .set_data_type(DataType::Boolean) 88 | .build()], 89 | is_unique: true, 90 | if_not_exists: true, 91 | })); 92 | 93 | assert_eq!(query, expected); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/engine/ast/dml/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{types::TableName, DMLStatement, SQLStatement}; 2 | 3 | use super::parts::{_where::WhereClause, target::UpdateTarget}; 4 | 5 | #[derive(Clone, Debug, PartialEq, Default)] 6 | pub struct DeleteQuery { 7 | pub from_table: Option, 8 | pub where_clause: Option, 9 | } 10 | 11 | impl DeleteQuery { 12 | pub fn builder() -> Self { 13 | Self { 14 | from_table: None, 15 | where_clause: None, 16 | } 17 | } 18 | 19 | pub fn set_from_table(mut self, from: TableName) -> Self { 20 | self.from_table = Some(from.into()); 21 | self 22 | } 23 | 24 | pub fn set_from_alias(mut self, alias: String) -> Self { 25 | if self.from_table.is_some() { 26 | self.from_table = self.from_table.map(|mut e| { 27 | e.alias = Some(alias); 28 | e 29 | }); 30 | } 31 | self 32 | } 33 | 34 | pub fn set_where(mut self, where_clause: WhereClause) -> Self { 35 | self.where_clause = Some(where_clause); 36 | self 37 | } 38 | 39 | pub fn build(self) -> Self { 40 | self 41 | } 42 | } 43 | 44 | impl From for SQLStatement { 45 | fn from(value: DeleteQuery) -> SQLStatement { 46 | SQLStatement::DML(DMLStatement::DeleteQuery(value)) 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | #[allow(non_snake_case)] 52 | mod tests { 53 | use crate::engine::ast::types::SQLExpression; 54 | 55 | use super::*; 56 | 57 | #[test] 58 | fn test_builder_all() { 59 | let delete_query = DeleteQuery::builder() 60 | .set_from_table(TableName::new(None, "table".into())) 61 | .set_where(WhereClause { 62 | expression: SQLExpression::String("a".into()), 63 | }) 64 | .set_from_alias("alias".into()) 65 | .build(); 66 | 67 | assert_eq!( 68 | delete_query, 69 | DeleteQuery { 70 | from_table: Some(UpdateTarget { 71 | table: TableName::new(None, "table".into()), 72 | alias: Some("alias".into()), 73 | }), 74 | where_clause: Some(WhereClause { 75 | expression: SQLExpression::String("a".into()), 76 | }), 77 | } 78 | ); 79 | } 80 | 81 | #[test] 82 | fn test_From_DeleteQuery_for_SQLStatement() { 83 | let delete_query = DeleteQuery::builder() 84 | .set_from_table(TableName::new(None, "table".into())) 85 | .set_where(WhereClause { 86 | expression: SQLExpression::String("a".into()), 87 | }) 88 | .build(); 89 | 90 | let sql_statement: SQLStatement = delete_query.clone().into(); 91 | assert_eq!( 92 | sql_statement, 93 | SQLStatement::DML(DMLStatement::DeleteQuery(delete_query)) 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/engine/actions/ddl/create_table.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind as IOErrorKind; 2 | 3 | use crate::engine::DBEngine; 4 | use crate::engine::ast::ddl::create_table::CreateTableQuery; 5 | use crate::engine::encoder::schema_encoder::StorageEncoder; 6 | use crate::engine::schema::table::TableSchema; 7 | use crate::engine::types::{ 8 | ExecuteColumn, ExecuteColumnType, ExecuteField, ExecuteResult, ExecuteRow, 9 | }; 10 | use crate::errors::execute_error::ExecuteError; 11 | use crate::errors; 12 | 13 | impl DBEngine { 14 | pub async fn create_table(&self, query: CreateTableQuery) -> errors::Result { 15 | let encoder = StorageEncoder::new(); 16 | 17 | let database_name = query.table.clone().unwrap().database_name.unwrap(); 18 | let table_name = query.table.clone().unwrap().table_name; 19 | 20 | let base_path = self.get_data_directory(); 21 | let database_path = base_path.clone().join(&database_name); 22 | 23 | let table_path = database_path.clone().join("tables").join(&table_name); 24 | 25 | if let Err(error) = tokio::fs::create_dir(&table_path).await { 26 | match error.kind() { 27 | IOErrorKind::AlreadyExists => { 28 | return Err(ExecuteError::wrap( 29 | "already exists table".to_string(), 30 | )); 31 | } 32 | _ => { 33 | return Err(ExecuteError::wrap( 34 | "table create failed".to_string(), 35 | )); 36 | } 37 | } 38 | } 39 | 40 | // 각 데이터베이스 단위 설정파일 생성 41 | let config_path = table_path.clone().join("table.config"); 42 | let table_info: TableSchema = query.into(); 43 | 44 | if let Err(error) = tokio::fs::write(&config_path, encoder.encode(table_info)).await { 45 | return Err(ExecuteError::wrap(error.to_string())); 46 | } 47 | 48 | let rows_path = table_path.clone().join("rows"); 49 | 50 | // 데이터 경로 생성 51 | if let Err(error) = tokio::fs::create_dir(&rows_path).await { 52 | return Err(ExecuteError::wrap(error.to_string())); 53 | } 54 | 55 | let index_path = table_path.clone().join("index"); 56 | 57 | // 인덱스 경로 생성 58 | if let Err(error) = tokio::fs::create_dir(&index_path).await { 59 | return Err(ExecuteError::wrap(error.to_string())); 60 | } 61 | 62 | // TODO: primary key 데이터 생성 63 | // TODO: unique key 데이터 생성 64 | // TODO: foreign key 데이터 생성 65 | 66 | Ok(ExecuteResult { 67 | columns: (vec![ExecuteColumn { 68 | name: "desc".into(), 69 | data_type: ExecuteColumnType::String, 70 | }]), 71 | rows: (vec![ExecuteRow { 72 | fields: vec![ExecuteField::String(format!( 73 | "table created: {}", 74 | table_name 75 | ))], 76 | }]), 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/engine/parser/test/drop_database.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use crate::engine::ast::ddl::drop_database::DropDatabaseQuery; 3 | use crate::engine::ast::SQLStatement; 4 | use crate::engine::lexer::tokens::Token; 5 | use crate::engine::parser::predule::Parser; 6 | 7 | #[test] 8 | fn test_handle_drop_database_query() { 9 | struct TestCase { 10 | name: String, 11 | input: Vec, 12 | expected: SQLStatement, 13 | want_error: bool, 14 | } 15 | 16 | let test_cases = vec![ 17 | TestCase { 18 | name: "DROP DATABASE test_db;".into(), 19 | input: vec![Token::Identifier("test_db".to_owned()), Token::SemiColon], 20 | expected: DropDatabaseQuery::builder() 21 | .set_name("test_db".to_owned()) 22 | .build() 23 | .into(), 24 | want_error: false, 25 | }, 26 | TestCase { 27 | name: "DROP DATABASE test_db".into(), 28 | input: vec![Token::Identifier("test_db".to_owned())], 29 | expected: DropDatabaseQuery::builder() 30 | .set_name("test_db".to_owned()) 31 | .build() 32 | .into(), 33 | want_error: false, 34 | }, 35 | TestCase { 36 | name: "DROP DATABASE IF EXISTS test_db;".into(), 37 | input: vec![ 38 | Token::If, 39 | Token::Exists, 40 | Token::Identifier("test_db".to_owned()), 41 | Token::SemiColon, 42 | ], 43 | expected: DropDatabaseQuery::builder() 44 | .set_name("test_db".to_owned()) 45 | .set_if_exists(true) 46 | .build() 47 | .into(), 48 | want_error: false, 49 | }, 50 | TestCase { 51 | name: "오류: DROP DATABASE IF EXISTS".into(), 52 | input: vec![Token::If, Token::Exists], 53 | expected: Default::default(), 54 | want_error: true, 55 | }, 56 | TestCase { 57 | name: "오류: DROP DATABASE IF EXISTS DELETE".into(), 58 | input: vec![Token::If, Token::Exists, Token::Delete], 59 | expected: Default::default(), 60 | want_error: true, 61 | }, 62 | TestCase { 63 | name: "오류: DROP DATABASE test_db&&".into(), 64 | input: vec![Token::Identifier("test_db".to_owned()), Token::And], 65 | expected: Default::default(), 66 | want_error: true, 67 | }, 68 | ]; 69 | 70 | for t in test_cases { 71 | let mut parser = Parser::new(t.input); 72 | 73 | let got = parser.handle_drop_database_query(); 74 | 75 | assert_eq!( 76 | got.is_err(), 77 | t.want_error, 78 | "TC: {} Error: {:?}", 79 | t.name, 80 | got.err() 81 | ); 82 | 83 | if let Ok(alias) = got { 84 | assert_eq!(alias, t.expected, "TC: {}", t.name); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/engine/schema/row.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::engine::ast::types::TableName; 5 | use crate::utils::float::Float64; 6 | 7 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, PartialOrd, Eq, Hash)] 8 | pub enum TableDataFieldType { 9 | // 끝단 Primitive 값 10 | Integer(i64), 11 | Float(Float64), 12 | Boolean(bool), 13 | String(String), 14 | Array(Vec), 15 | Null, 16 | } 17 | 18 | impl TableDataFieldType { 19 | pub fn type_code(&self) -> isize { 20 | match self { 21 | TableDataFieldType::Integer(_) => 1, 22 | TableDataFieldType::Float(_) => 2, 23 | TableDataFieldType::Boolean(_) => 3, 24 | TableDataFieldType::String(_) => 4, 25 | TableDataFieldType::Array(_) => 5, 26 | TableDataFieldType::Null => 0, 27 | } 28 | } 29 | 30 | pub fn to_array(self) -> Self { 31 | Self::Array(vec![self]) 32 | } 33 | 34 | pub fn push(&mut self, value: Self) { 35 | #[allow(clippy::single_match)] 36 | match self { 37 | TableDataFieldType::Array(array) => array.push(value), 38 | _ => {} 39 | } 40 | } 41 | 42 | pub fn is_null(&self) -> bool { 43 | self.type_code() == 0 44 | } 45 | 46 | pub fn is_array(&self) -> bool { 47 | self.type_code() == 5 48 | } 49 | } 50 | 51 | impl ToString for TableDataFieldType { 52 | fn to_string(&self) -> String { 53 | #[allow(unstable_name_collisions)] 54 | match self { 55 | TableDataFieldType::Integer(value) => value.to_string(), 56 | TableDataFieldType::Float(value) => value.to_string(), 57 | TableDataFieldType::Boolean(value) => value.to_string(), 58 | TableDataFieldType::String(value) => value.to_owned(), 59 | TableDataFieldType::Array(value) => value 60 | .iter() 61 | .map(|e| e.to_string()) 62 | .intersperse(", ".to_owned()) 63 | .collect(), 64 | TableDataFieldType::Null => "NULL".into(), 65 | } 66 | } 67 | } 68 | 69 | #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)] 70 | pub struct TableDataField { 71 | pub table_name: TableName, 72 | pub column_name: String, 73 | pub data: TableDataFieldType, 74 | } 75 | 76 | impl TableDataField { 77 | pub fn to_array(self) -> Self { 78 | Self { 79 | table_name: self.table_name, 80 | column_name: self.column_name, 81 | data: self.data.to_array(), 82 | } 83 | } 84 | 85 | pub fn push(&mut self, value: TableDataFieldType) { 86 | #[allow(clippy::single_match)] 87 | match &mut self.data { 88 | TableDataFieldType::Array(array) => array.push(value), 89 | _ => {} 90 | } 91 | } 92 | } 93 | 94 | #[derive(Deserialize, Serialize, Debug, Clone)] 95 | pub struct TableDataRow { 96 | pub fields: Vec, 97 | } 98 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/operators.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | // 2항연산자 4 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 5 | pub enum BinaryOperator { 6 | Add, // A + B 7 | Sub, // A - B 8 | Mul, // A * B 9 | Div, // A / B 10 | And, // A AND B 11 | Or, // A OR B 12 | Lt, // A < B 13 | Gt, // A > B 14 | Lte, // A <= B 15 | Gte, // A >= B 16 | Eq, // A = B 17 | Neq, // A != B, A <> B 18 | Like, // A LIKE B 19 | NotLike, // A NOT LIKE B 20 | In, // A In B 21 | NotIn, // A Not In B 22 | Is, // A Is B 23 | IsNot, // A Is Not B 24 | } 25 | 26 | // 단항연산자 27 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] 28 | pub enum UnaryOperator { 29 | Pos, // +A 30 | Neg, // -A 31 | Not, // Not A 32 | } 33 | 34 | impl BinaryOperator { 35 | // 2항연산자 우선순위 획득 36 | pub fn get_precedence(&self) -> i32 { 37 | match self { 38 | BinaryOperator::Add => 10, 39 | BinaryOperator::Sub => 10, 40 | BinaryOperator::Mul => 40, 41 | BinaryOperator::Div => 40, 42 | BinaryOperator::And => 10, 43 | BinaryOperator::Or => 10, 44 | BinaryOperator::Lt => 10, 45 | BinaryOperator::Gt => 10, 46 | BinaryOperator::Lte => 10, 47 | BinaryOperator::Gte => 10, 48 | BinaryOperator::Eq => 10, 49 | BinaryOperator::Neq => 10, 50 | BinaryOperator::Like => 10, 51 | BinaryOperator::NotLike => 10, 52 | BinaryOperator::In => 10, 53 | BinaryOperator::NotIn => 10, 54 | BinaryOperator::Is => 10, 55 | BinaryOperator::IsNot => 10, 56 | } 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::BinaryOperator; 63 | 64 | #[test] 65 | fn test_get_precedence() { 66 | assert_eq!(BinaryOperator::Add.get_precedence(), 10); 67 | assert_eq!(BinaryOperator::Sub.get_precedence(), 10); 68 | assert_eq!(BinaryOperator::Mul.get_precedence(), 40); 69 | assert_eq!(BinaryOperator::Div.get_precedence(), 40); 70 | assert_eq!(BinaryOperator::And.get_precedence(), 10); 71 | assert_eq!(BinaryOperator::Or.get_precedence(), 10); 72 | assert_eq!(BinaryOperator::Lt.get_precedence(), 10); 73 | assert_eq!(BinaryOperator::Gt.get_precedence(), 10); 74 | assert_eq!(BinaryOperator::Lte.get_precedence(), 10); 75 | assert_eq!(BinaryOperator::Gte.get_precedence(), 10); 76 | assert_eq!(BinaryOperator::Eq.get_precedence(), 10); 77 | assert_eq!(BinaryOperator::Neq.get_precedence(), 10); 78 | assert_eq!(BinaryOperator::Like.get_precedence(), 10); 79 | assert_eq!(BinaryOperator::NotLike.get_precedence(), 10); 80 | assert_eq!(BinaryOperator::In.get_precedence(), 10); 81 | assert_eq!(BinaryOperator::NotIn.get_precedence(), 10); 82 | assert_eq!(BinaryOperator::Is.get_precedence(), 10); 83 | assert_eq!(BinaryOperator::IsNot.get_precedence(), 10); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Test & Coverage 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: # Job-level permissions configuration starts here 7 | contents: write # 'write' access to repository contents 8 | pull-requests: write # 'write' access to pull requests 9 | 10 | jobs: 11 | check: 12 | name: Rust project 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | persist-credentials: false 19 | 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | 24 | - name: Cache Cargo's directories 25 | uses: actions/cache@v4 26 | with: 27 | save-always: true 28 | key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-cargo- 31 | path: | 32 | ~/.cargo/bin/ 33 | ~/.cargo/registry/index/ 34 | ~/.cargo/registry/cache/ 35 | ~/.cargo/git/db/ 36 | ./target 37 | 38 | - name: Cache cargo-tarpaulin 39 | id: cache-tarpaulin 40 | uses: actions/cache@v4 41 | with: 42 | key: ${{ runner.os }}-tarpaulin-v0.30 43 | path: ~/.cargo/bin/cargo-tarpaulin 44 | 45 | - name: Install cargo-tarpaulin 46 | if: steps.cache-tarpaulin.outputs.cache-hit != 'true' 47 | run: cargo install cargo-tarpaulin --locked 48 | 49 | - name: Run cargo-tarpaulin 50 | run: | 51 | cargo tarpaulin -l --out Html | tail -n 1 | grep -o '[^+][0-9]\+\.[0-9]\+%' > coverage_total_percent.txt 52 | 53 | - name: Set coverage env variable 54 | run: | 55 | echo "COVERAGE=$(head -n 1 coverage_total_percent.txt)" >> $GITHUB_ENV 56 | 57 | - name: Clone html_reports repository 58 | run: | 59 | git clone https://github.com/myyrakle/html_reports 60 | 61 | - name: Generate Random Name 62 | run: | 63 | echo "REPORT_NAME=$(date +%s)" >> $GITHUB_ENV 64 | 65 | - name: Copy coverage report 66 | run: | 67 | cp ./tarpaulin-report.html ./html_reports/${{ env.REPORT_NAME }}.html | 68 | cd ./html_reports 69 | 70 | - name: Add 71 | working-directory: html_reports 72 | run: | 73 | git add . 74 | 75 | - name: Commit 76 | working-directory: html_reports 77 | run: | 78 | git config --global user.email "sssang97@naver.com" && 79 | git config --global user.name "myyrakle" && 80 | git commit -m "Add report" 81 | 82 | - name: Push changes 83 | uses: ad-m/github-push-action@master 84 | with: 85 | github_token: ${{ secrets.GH_TOKEN }} 86 | directory: html_reports 87 | repository: myyrakle/html_reports 88 | force: true 89 | ref: master 90 | 91 | - name: Add comment to PR 92 | uses: thollander/actions-comment-pull-request@v1 93 | with: 94 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 95 | message: | 96 | ✅ **Total Coverage**: ${{ env.COVERAGE }} 97 | 🔗 [Coverage View](https://myyrakle.github.io/html_reports/${{ env.REPORT_NAME }}) (최대 몇분 정도의 지연시간이 발생할 수 있습니다.) 98 | -------------------------------------------------------------------------------- /src/engine/parser/test/create_database.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use crate::engine::ast::ddl::create_database::CreateDatabaseQuery; 3 | use crate::engine::ast::SQLStatement; 4 | use crate::engine::lexer::tokens::Token; 5 | use crate::engine::parser::predule::Parser; 6 | 7 | #[test] 8 | fn test_handle_create_database_query() { 9 | struct TestCase { 10 | name: String, 11 | input: Vec, 12 | expected: SQLStatement, 13 | want_error: bool, 14 | } 15 | 16 | let test_cases = vec![ 17 | TestCase { 18 | name: "CREATE DATABASE test_db;".into(), 19 | input: vec![Token::Identifier("test_db".to_owned()), Token::SemiColon], 20 | expected: CreateDatabaseQuery::builder() 21 | .set_name("test_db".to_owned()) 22 | .build() 23 | .into(), 24 | want_error: false, 25 | }, 26 | TestCase { 27 | name: "CREATE DATABASE test_db".into(), 28 | input: vec![Token::Identifier("test_db".to_owned())], 29 | expected: CreateDatabaseQuery::builder() 30 | .set_name("test_db".to_owned()) 31 | .build() 32 | .into(), 33 | want_error: false, 34 | }, 35 | TestCase { 36 | name: "CREATE DATABASE IF NOT EXISTS test_db;".into(), 37 | input: vec![ 38 | Token::If, 39 | Token::Not, 40 | Token::Exists, 41 | Token::Identifier("test_db".to_owned()), 42 | Token::SemiColon, 43 | ], 44 | expected: CreateDatabaseQuery::builder() 45 | .set_name("test_db".to_owned()) 46 | .set_if_not_exists(true) 47 | .build() 48 | .into(), 49 | want_error: false, 50 | }, 51 | TestCase { 52 | name: "오류: 빈 토큰".into(), 53 | input: vec![], 54 | expected: Default::default(), 55 | want_error: true, 56 | }, 57 | TestCase { 58 | name: "오류: CREATE DATABASE IF NOT EXISTS".into(), 59 | input: vec![Token::If, Token::Not, Token::Exists], 60 | expected: Default::default(), 61 | want_error: true, 62 | }, 63 | TestCase { 64 | name: "CREATE DATABASE IF NOT EXISTS DELETE;".into(), 65 | input: vec![ 66 | Token::If, 67 | Token::Not, 68 | Token::Exists, 69 | Token::Delete, 70 | Token::SemiColon, 71 | ], 72 | expected: Default::default(), 73 | want_error: true, 74 | }, 75 | TestCase { 76 | name: "CREATE DATABASE test_db DELETE".into(), 77 | input: vec![Token::Identifier("test_db".to_owned()), Token::Delete], 78 | expected: Default::default(), 79 | want_error: true, 80 | }, 81 | ]; 82 | 83 | for t in test_cases { 84 | let mut parser = Parser::new(t.input); 85 | 86 | let got = parser.handle_create_database_query(); 87 | 88 | assert_eq!( 89 | got.is_err(), 90 | t.want_error, 91 | "TC: {} Error: {:?}", 92 | t.name, 93 | got.err() 94 | ); 95 | 96 | if let Ok(alias) = got { 97 | assert_eq!(alias, t.expected, "TC: {}", t.name); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/engine/parser/test/tcl.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use crate::engine::ast::tcl::{BeginTransactionQuery, CommitQuery, RollbackQuery}; 4 | use crate::engine::ast::SQLStatement; 5 | use crate::engine::parser::predule::{Parser, ParserContext}; 6 | 7 | #[test] 8 | pub fn begin_transaction() { 9 | struct TestCase { 10 | name: String, 11 | input: String, 12 | expected: SQLStatement, 13 | want_err: bool, 14 | } 15 | 16 | let test_cases = vec![ 17 | TestCase { 18 | name: "정상적인 트랜잭션 시작".to_owned(), 19 | input: "BEGIN TRANSACTION;".to_owned(), 20 | expected: BeginTransactionQuery {}.into(), 21 | want_err: false, 22 | }, 23 | TestCase { 24 | name: "begin만 있는 경우".to_owned(), 25 | input: "BEGIN;".to_owned(), 26 | expected: Default::default(), 27 | want_err: true, 28 | }, 29 | TestCase { 30 | name: "begin 이후에 기대하지 않은 입력이 있는 경우".to_owned(), 31 | input: "BEGIN TRANSITION;".to_owned(), 32 | expected: Default::default(), 33 | want_err: true, 34 | }, 35 | ]; 36 | 37 | for tc in test_cases { 38 | let mut parser = Parser::with_string(tc.input).unwrap(); 39 | 40 | let result = parser.parse(ParserContext::default()); 41 | 42 | if tc.want_err { 43 | assert!( 44 | result.is_err(), 45 | "{} - expected error, got {:?}", 46 | tc.name, 47 | result 48 | ); 49 | continue; 50 | } 51 | 52 | assert_eq!(result.unwrap(), vec![tc.expected], "{}", tc.name); 53 | } 54 | } 55 | 56 | #[test] 57 | pub fn commit() { 58 | struct TestCase { 59 | name: String, 60 | input: String, 61 | expected: SQLStatement, 62 | want_err: bool, 63 | } 64 | 65 | let test_cases = vec![TestCase { 66 | name: "정상적인 Commit 명령".to_owned(), 67 | input: "COMMIT;".to_owned(), 68 | expected: CommitQuery {}.into(), 69 | want_err: false, 70 | }]; 71 | 72 | for tc in test_cases { 73 | let mut parser = Parser::with_string(tc.input).unwrap(); 74 | 75 | let result = parser.parse(ParserContext::default()); 76 | 77 | if tc.want_err { 78 | assert!( 79 | result.is_err(), 80 | "{} - expected error, got {:?}", 81 | tc.name, 82 | result 83 | ); 84 | continue; 85 | } 86 | 87 | assert_eq!(result.unwrap(), vec![tc.expected], "{}", tc.name); 88 | } 89 | } 90 | 91 | #[test] 92 | pub fn rollback() { 93 | struct TestCase { 94 | name: String, 95 | input: String, 96 | expected: SQLStatement, 97 | want_err: bool, 98 | } 99 | 100 | let test_cases = vec![TestCase { 101 | name: "정상적인 ROLLBACK 명령".to_owned(), 102 | input: "ROLLBACK;".to_owned(), 103 | expected: RollbackQuery {}.into(), 104 | want_err: false, 105 | }]; 106 | 107 | for tc in test_cases { 108 | let mut parser = Parser::with_string(tc.input).unwrap(); 109 | 110 | let result = parser.parse(ParserContext::default()); 111 | 112 | if tc.want_err { 113 | assert!( 114 | result.is_err(), 115 | "{} - expected error, got {:?}", 116 | tc.name, 117 | result 118 | ); 119 | continue; 120 | } 121 | 122 | assert_eq!(result.unwrap(), vec![tc.expected], "{}", tc.name); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/engine/ast/dml/update.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{types::TableName, DMLStatement, SQLStatement}; 2 | 3 | use super::parts::{_where::WhereClause, target::UpdateTarget, update_item::UpdateItem}; 4 | 5 | #[derive(Clone, Debug, PartialEq, Default)] 6 | pub struct UpdateQuery { 7 | pub target_table: Option, 8 | pub where_clause: Option, 9 | pub update_items: Vec, 10 | } 11 | 12 | impl UpdateQuery { 13 | pub fn builder() -> Self { 14 | Self { 15 | update_items: vec![], 16 | target_table: None, 17 | where_clause: None, 18 | } 19 | } 20 | 21 | pub fn add_update_item(mut self, item: UpdateItem) -> Self { 22 | self.update_items.push(item); 23 | self 24 | } 25 | 26 | pub fn set_target_table(mut self, from: TableName) -> Self { 27 | self.target_table = Some(from.into()); 28 | self 29 | } 30 | 31 | pub fn set_target_alias(mut self, alias: String) -> Self { 32 | if self.target_table.is_some() { 33 | self.target_table = self.target_table.map(|mut e| { 34 | e.alias = Some(alias); 35 | e 36 | }); 37 | } 38 | self 39 | } 40 | 41 | pub fn set_where(mut self, where_clause: WhereClause) -> Self { 42 | self.where_clause = Some(where_clause); 43 | self 44 | } 45 | 46 | pub fn build(self) -> Self { 47 | self 48 | } 49 | } 50 | 51 | impl From for SQLStatement { 52 | fn from(value: UpdateQuery) -> SQLStatement { 53 | SQLStatement::DML(DMLStatement::UpdateQuery(value)) 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | #[allow(non_snake_case)] 59 | mod tests { 60 | use crate::engine::ast::types::SQLExpression; 61 | 62 | use super::*; 63 | 64 | #[test] 65 | fn test_builder_all() { 66 | let update_query = UpdateQuery::builder() 67 | .set_target_table(TableName::new(None, "table".into())) 68 | .add_update_item(UpdateItem { 69 | column: "a".into(), 70 | value: SQLExpression::String("b".into()), 71 | }) 72 | .set_where(WhereClause { 73 | expression: SQLExpression::String("a".into()), 74 | }) 75 | .set_target_alias("alias".into()) 76 | .build(); 77 | assert_eq!( 78 | update_query, 79 | UpdateQuery { 80 | target_table: Some(UpdateTarget { 81 | table: TableName::new(None, "table".into()), 82 | alias: Some("alias".into()), 83 | }), 84 | where_clause: Some(WhereClause { 85 | expression: SQLExpression::String("a".into()), 86 | }), 87 | update_items: vec![UpdateItem { 88 | column: "a".into(), 89 | value: SQLExpression::String("b".into()), 90 | }], 91 | } 92 | ); 93 | } 94 | 95 | #[test] 96 | fn test_From_UpdateQuery_for_SQLStatement() { 97 | let update_query = UpdateQuery::builder() 98 | .set_target_table(TableName::new(None, "table".into())) 99 | .add_update_item(UpdateItem { 100 | column: "a".into(), 101 | value: SQLExpression::String("b".into()), 102 | }) 103 | .set_where(WhereClause { 104 | expression: SQLExpression::String("a".into()), 105 | }) 106 | .build(); 107 | assert_eq!( 108 | SQLStatement::from(update_query.clone()), 109 | SQLStatement::DML(DMLStatement::UpdateQuery(update_query)) 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/engine/ast/dml/expressions/between.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Not; 2 | 3 | use crate::engine::ast::types::SQLExpression; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::not_between::NotBetweenExpression; 7 | 8 | // a BETWEEN x AND y 9 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 10 | pub struct BetweenExpression { 11 | pub a: SQLExpression, 12 | pub x: SQLExpression, 13 | pub y: SQLExpression, 14 | } 15 | 16 | impl From for SQLExpression { 17 | fn from(value: BetweenExpression) -> SQLExpression { 18 | SQLExpression::Between(Box::new(value)) 19 | } 20 | } 21 | 22 | impl From> for SQLExpression { 23 | fn from(value: Box) -> SQLExpression { 24 | SQLExpression::Between(value) 25 | } 26 | } 27 | 28 | impl From for Option> { 29 | fn from(value: BetweenExpression) -> Option> { 30 | Some(Box::new(SQLExpression::Between(Box::new(value)))) 31 | } 32 | } 33 | 34 | impl Not for BetweenExpression { 35 | type Output = NotBetweenExpression; 36 | 37 | fn not(self) -> Self::Output { 38 | NotBetweenExpression { 39 | a: self.a, 40 | x: self.x, 41 | y: self.y, 42 | } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | #[allow(non_snake_case)] 48 | mod tests { 49 | use crate::engine::ast::types::SQLExpression; 50 | 51 | #[test] 52 | fn test_From_Box_BetweenExpression_for_SQLExpression() { 53 | use super::BetweenExpression; 54 | let between = BetweenExpression { 55 | a: SQLExpression::String("a".into()), 56 | x: SQLExpression::String("x".into()), 57 | y: SQLExpression::String("y".into()), 58 | }; 59 | let sql_expression: SQLExpression = Box::new(between).into(); 60 | assert_eq!( 61 | sql_expression, 62 | SQLExpression::Between(Box::new(BetweenExpression { 63 | a: SQLExpression::String("a".into()), 64 | x: SQLExpression::String("x".into()), 65 | y: SQLExpression::String("y".into()) 66 | })) 67 | ); 68 | } 69 | 70 | #[test] 71 | fn test_From_BetweenExpression_for_Option_Box_SQLExpression() { 72 | use super::BetweenExpression; 73 | let between = BetweenExpression { 74 | a: SQLExpression::String("a".into()), 75 | x: SQLExpression::String("x".into()), 76 | y: SQLExpression::String("y".into()), 77 | }; 78 | let sql_expression: Option> = between.into(); 79 | assert_eq!( 80 | sql_expression, 81 | Some(Box::new(SQLExpression::Between(Box::new( 82 | BetweenExpression { 83 | a: SQLExpression::String("a".into()), 84 | x: SQLExpression::String("x".into()), 85 | y: SQLExpression::String("y".into()) 86 | } 87 | )))) 88 | ); 89 | } 90 | 91 | #[test] 92 | fn test_Not_for_BetweenExpression() { 93 | use super::BetweenExpression; 94 | let between = BetweenExpression { 95 | a: SQLExpression::String("a".into()), 96 | x: SQLExpression::String("x".into()), 97 | y: SQLExpression::String("y".into()), 98 | }; 99 | let not_between = !between; 100 | assert_eq!( 101 | not_between, 102 | super::NotBetweenExpression { 103 | a: SQLExpression::String("a".into()), 104 | x: SQLExpression::String("x".into()), 105 | y: SQLExpression::String("y".into()) 106 | } 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/engine/ast/types/column.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::types::DataType; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::expression::SQLExpression; 5 | 6 | #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)] 7 | pub struct Column { 8 | pub name: String, 9 | pub data_type: DataType, 10 | pub comment: String, 11 | pub default: Option, 12 | pub not_null: bool, 13 | pub primary_key: bool, 14 | } 15 | 16 | impl Column { 17 | pub fn builder() -> ColumnBuilder { 18 | ColumnBuilder::default() 19 | } 20 | } 21 | 22 | #[derive(Default)] 23 | pub struct ColumnBuilder { 24 | name: Option, 25 | data_type: Option, 26 | comment: Option, 27 | default: Option, 28 | not_null: Option, 29 | primary_key: Option, 30 | } 31 | 32 | impl ColumnBuilder { 33 | pub fn set_name(mut self, name: String) -> Self { 34 | self.name = Some(name); 35 | self 36 | } 37 | 38 | pub fn set_data_type(mut self, data_type: DataType) -> Self { 39 | self.data_type = Some(data_type); 40 | self 41 | } 42 | 43 | pub fn set_comment(mut self, comment: String) -> Self { 44 | self.comment = Some(comment); 45 | self 46 | } 47 | 48 | pub fn set_default(mut self, default: SQLExpression) -> Self { 49 | self.default = Some(default); 50 | self 51 | } 52 | 53 | pub fn set_not_null(mut self, not_null: bool) -> Self { 54 | self.not_null = Some(not_null); 55 | self 56 | } 57 | 58 | pub fn set_primary_key(mut self, primary_key: bool) -> Self { 59 | self.primary_key = Some(primary_key); 60 | if primary_key { 61 | self.not_null = Some(true); 62 | } 63 | self 64 | } 65 | 66 | pub fn build(self) -> Column { 67 | Column { 68 | name: self.name.unwrap(), 69 | data_type: self.data_type.unwrap(), 70 | comment: self.comment.unwrap_or_else(|| "".into()), 71 | default: self.default, 72 | not_null: self.not_null.unwrap_or(false), 73 | primary_key: self.primary_key.unwrap_or(false), 74 | } 75 | } 76 | } 77 | 78 | // [column_name.]table_name 79 | // 컬럼명을 가리키는 값입니다. 80 | #[derive(Clone, Debug, PartialEq, Eq)] 81 | pub struct ColumnName { 82 | pub table_name: Option, 83 | pub column_name: String, 84 | } 85 | 86 | impl ColumnName { 87 | pub fn new(table_name: Option, column_name: String) -> Self { 88 | ColumnName { 89 | table_name, 90 | column_name, 91 | } 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | 99 | #[test] 100 | fn test_column_builder() { 101 | let column = Column::builder() 102 | .set_name("id".into()) 103 | .set_data_type(DataType::Int) 104 | .set_comment("id column".into()) 105 | .set_default(SQLExpression::Integer(1)) 106 | .set_not_null(true) 107 | .set_primary_key(true) 108 | .build(); 109 | 110 | assert_eq!(column.name, "id"); 111 | assert_eq!(column.data_type, DataType::Int); 112 | assert_eq!(column.comment, "id column"); 113 | assert_eq!(column.default, Some(SQLExpression::Integer(1))); 114 | assert_eq!(column.not_null, true); 115 | assert_eq!(column.primary_key, true); 116 | } 117 | 118 | #[test] 119 | fn test_column_name() { 120 | let column_name = ColumnName::new(Some("table".into()), "column".into()); 121 | assert_eq!(column_name.table_name, Some("table".into())); 122 | assert_eq!(column_name.column_name, "column"); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/pgwire/protocol/extension/data_row_writer.rs: -------------------------------------------------------------------------------- 1 | use bytes::BufMut; 2 | use chrono::{NaiveDate, NaiveDateTime}; 3 | 4 | use crate::pgwire::protocol::FormatCode; 5 | 6 | use super::DataRowBatch; 7 | 8 | macro_rules! primitive_write { 9 | ($name: ident, $type: ident) => { 10 | #[allow(missing_docs)] 11 | pub fn $name(&mut self, val: $type) { 12 | match self.parent.format_code { 13 | FormatCode::Text => self.write_value(&val.to_string().into_bytes()), 14 | FormatCode::Binary => self.write_value(&val.to_be_bytes()), 15 | }; 16 | } 17 | }; 18 | } 19 | 20 | /// Temporarily leased from a [DataRowBatch] to encode a single row. 21 | pub struct DataRowWriter<'a> { 22 | current_col: usize, 23 | parent: &'a mut DataRowBatch, 24 | } 25 | 26 | impl<'a> DataRowWriter<'a> { 27 | pub fn new(parent: &'a mut DataRowBatch) -> Self { 28 | parent.row.put_i16(parent.num_cols as i16); 29 | Self { 30 | current_col: 0, 31 | parent, 32 | } 33 | } 34 | 35 | fn write_value(&mut self, data: &[u8]) { 36 | self.current_col += 1; 37 | self.parent.row.put_i32(data.len() as i32); 38 | self.parent.row.put_slice(data); 39 | } 40 | 41 | /// Writes a null value for the next column. 42 | pub fn write_null(&mut self) { 43 | self.current_col += 1; 44 | self.parent.row.put_i32(-1); 45 | } 46 | 47 | /// Writes a string value for the next column. 48 | pub fn write_string(&mut self, val: &str) { 49 | self.write_value(val.as_bytes()); 50 | } 51 | 52 | /// Writes a bool value for the next column. 53 | pub fn write_bool(&mut self, val: bool) { 54 | match self.parent.format_code { 55 | FormatCode::Text => self.write_value(if val { "t" } else { "f" }.as_bytes()), 56 | FormatCode::Binary => { 57 | self.current_col += 1; 58 | self.parent.row.put_u8(val as u8); 59 | } 60 | }; 61 | } 62 | 63 | fn pg_date_epoch() -> NaiveDate { 64 | NaiveDate::from_ymd_opt(2000, 1, 1).unwrap() 65 | } 66 | 67 | /// Writes a date value for the next column. 68 | pub fn write_date(&mut self, val: NaiveDate) { 69 | match self.parent.format_code { 70 | FormatCode::Binary => { 71 | self.write_int4(val.signed_duration_since(Self::pg_date_epoch()).num_days() as i32) 72 | } 73 | FormatCode::Text => self.write_string(&val.to_string()), 74 | } 75 | } 76 | 77 | /// Writes a timestamp value for the next column. 78 | pub fn write_timestamp(&mut self, val: NaiveDateTime) { 79 | match self.parent.format_code { 80 | FormatCode::Binary => { 81 | self.write_int8( 82 | val.signed_duration_since(Self::pg_date_epoch().and_hms_opt(0, 0, 0).unwrap()) 83 | .num_microseconds() 84 | .unwrap(), 85 | ); 86 | } 87 | FormatCode::Text => self.write_string(&val.to_string()), 88 | } 89 | } 90 | 91 | primitive_write!(write_int2, i16); 92 | primitive_write!(write_int4, i32); 93 | primitive_write!(write_int8, i64); 94 | primitive_write!(write_float4, f32); 95 | primitive_write!(write_float8, f64); 96 | } 97 | 98 | impl<'a> Drop for DataRowWriter<'a> { 99 | fn drop(&mut self) { 100 | assert_eq!( 101 | self.parent.num_cols, self.current_col, 102 | "dropped a row writer with an invalid number of columns" 103 | ); 104 | 105 | self.parent.data.put_u8(b'D'); 106 | self.parent.data.put_i32((self.parent.row.len() + 4) as i32); 107 | self.parent.data.extend(self.parent.row.split()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/engine/actions/ddl/create_database.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind as IOErrorKind; 2 | 3 | use crate::engine::DBEngine; 4 | use crate::engine::ast::ddl::create_database::CreateDatabaseQuery; 5 | use crate::engine::encoder::schema_encoder::StorageEncoder; 6 | use crate::engine::schema::database::DatabaseSchema; 7 | 8 | use crate::engine::types::{ 9 | ExecuteColumn, ExecuteColumnType, ExecuteField, ExecuteResult, ExecuteRow, 10 | }; 11 | use crate::errors::execute_error::ExecuteError; 12 | use crate::errors; 13 | 14 | impl DBEngine { 15 | pub async fn create_database( 16 | &self, 17 | query: CreateDatabaseQuery, 18 | ) -> errors::Result { 19 | let encoder = StorageEncoder::new(); 20 | 21 | let base_path = self.get_data_directory(); 22 | 23 | let database_name = query 24 | .database_name 25 | .clone() 26 | .ok_or_else(|| ExecuteError::wrap("no database name".to_string()))?; 27 | 28 | let database_path = base_path.clone().join(&database_name); 29 | 30 | if let Err(error) = tokio::fs::create_dir(database_path.clone()).await { 31 | match error.kind() { 32 | IOErrorKind::AlreadyExists => { 33 | if query.if_not_exists { 34 | return Ok(ExecuteResult { 35 | columns: (vec![ExecuteColumn { 36 | name: "desc".into(), 37 | data_type: ExecuteColumnType::String, 38 | }]), 39 | rows: (vec![ExecuteRow { 40 | fields: vec![ExecuteField::String( 41 | "database already exists".into(), 42 | )], 43 | }]), 44 | }); 45 | } else { 46 | return Err(ExecuteError::wrap( 47 | "already exists database".to_string(), 48 | )); 49 | } 50 | } 51 | _ => { 52 | return Err(ExecuteError::wrap( 53 | "database create failed".to_string(), 54 | )); 55 | } 56 | } 57 | } 58 | 59 | // tables 경로 추가 60 | let tables_path = database_path.clone().join("tables"); 61 | 62 | if let Err(error) = tokio::fs::create_dir(&tables_path).await { 63 | match error.kind() { 64 | IOErrorKind::AlreadyExists => { 65 | return Err(ExecuteError::wrap( 66 | "already exists tables".to_string(), 67 | )); 68 | } 69 | _ => { 70 | return Err(ExecuteError::wrap( 71 | "tables create failed".to_string(), 72 | )); 73 | } 74 | } 75 | } 76 | 77 | // 각 데이터베이스 단위 설정파일 생성 78 | let config_path = database_path.clone().join("database.config"); 79 | let database_info = DatabaseSchema { 80 | database_name: database_name.clone(), 81 | }; 82 | 83 | if let Err(error) = tokio::fs::write(config_path, encoder.encode(database_info)).await { 84 | return Err(ExecuteError::wrap(error.to_string())); 85 | } 86 | 87 | Ok(ExecuteResult { 88 | columns: (vec![ExecuteColumn { 89 | name: "desc".into(), 90 | data_type: ExecuteColumnType::String, 91 | }]), 92 | rows: (vec![ExecuteRow { 93 | fields: vec![ExecuteField::String(format!( 94 | "database created: {}", 95 | database_name 96 | ))], 97 | }]), 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/engine/actions/dml/scan.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind as IOErrorKind; 2 | use std::path::PathBuf; 3 | 4 | use futures::future::join_all; 5 | 6 | use crate::engine::DBEngine; 7 | use crate::engine::ast::types::TableName; 8 | use crate::engine::encoder::schema_encoder::StorageEncoder; 9 | use crate::engine::schema::row::TableDataRow; 10 | use crate::errors; 11 | use crate::errors::execute_error::ExecuteError; 12 | 13 | impl DBEngine { 14 | pub async fn full_scan( 15 | &self, 16 | table_name: TableName, 17 | ) -> errors::Result> { 18 | let encoder = StorageEncoder::new(); 19 | 20 | let database_name = table_name.database_name.unwrap(); 21 | let table_name = table_name.table_name; 22 | 23 | let base_path = self.get_data_directory(); 24 | 25 | let database_path = base_path.clone().join(&database_name); 26 | 27 | let table_path = database_path.clone().join("tables").join(&table_name); 28 | 29 | // 데이터 행 파일 경로 30 | let rows_path = table_path.clone().join("rows"); 31 | 32 | match std::fs::read_dir(&rows_path) { 33 | Ok(read_dir_result) => { 34 | let futures = read_dir_result.map(|e| async { 35 | match e { 36 | Ok(entry) => match entry.file_type() { 37 | Ok(file_type) => { 38 | if file_type.is_file() { 39 | let path = entry.path(); 40 | 41 | match tokio::fs::read(&path).await { 42 | Ok(result) => { 43 | match encoder.decode::(result.as_slice()) 44 | { 45 | Some(decoded) => Ok((path.to_path_buf(), decoded)), 46 | None => Err(ExecuteError::wrap(format!( 47 | "full scan failed {:?}", 48 | path 49 | ))), 50 | } 51 | } 52 | Err(error) => Err(ExecuteError::wrap(format!( 53 | "full scan failed {}", 54 | error 55 | ))), 56 | } 57 | } else { 58 | Err(ExecuteError::wrap( 59 | "full scan failed".to_string(), 60 | )) 61 | } 62 | } 63 | Err(error) => { 64 | Err(ExecuteError::wrap(format!("full scan failed {}", error))) 65 | } 66 | }, 67 | Err(error) => { 68 | Err(ExecuteError::wrap(format!("full scan failed {}", error))) 69 | } 70 | } 71 | }); 72 | 73 | let rows = join_all(futures) 74 | .await 75 | .into_iter() 76 | .collect::, _>>(); 77 | 78 | match rows { 79 | Ok(rows) => Ok(rows), 80 | Err(error) => Err(ExecuteError::wrap(error.to_string())), 81 | } 82 | } 83 | Err(error) => match error.kind() { 84 | IOErrorKind::NotFound => Err(ExecuteError::wrap( 85 | "base path not exists (3)".to_string(), 86 | )), 87 | _ => Err(ExecuteError::wrap( 88 | "full scan failed".to_string(), 89 | )), 90 | }, 91 | } 92 | } 93 | 94 | pub async fn index_scan(&self, _table_name: TableName) {} 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rrdb 2 | 3 | ![](https://img.shields.io/badge/language-Rust-red) ![](https://img.shields.io/badge/version-0.0.2%20alpha-brightgreen) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/myyrakle/rrdb/blob/master/LICENSE) 4 | 5 | Rust-based RDB 6 | 7 | ## not complete 8 | 9 | --- 10 | 11 | ### 설치 12 | 13 | cargo를 사용한다. 14 | 15 | ``` 16 | cargo install rrdb 17 | ``` 18 | 19 | - 플랫폼별 초기화 (Linux) 20 | 21 | 심볼릭 링크를 생성하고 초기화를 수행합니다. 22 | 23 | ``` 24 | sudo ln -s /home/$USER/.cargo/bin/rrdb /usr/bin/rrdb 25 | sudo rrdb init 26 | ``` 27 | 28 | - 플랫폼별 초기화 (MacOS) 29 | 30 | 심볼릭 링크를 생성하고 초기화를 수행합니다. 31 | 32 | ``` 33 | sudo ln -s /home/$USER/.cargo/bin/rrdb /usr/local/bin/rrdb 34 | sudo rrdb init 35 | ``` 36 | 37 | - 플랫폼별 초기화 (Windows) 38 | 39 | powershell을 관리자 권한으로 실행하고 다음 명령어를 수행합니다. 40 | 41 | ``` 42 | mkdir 'C:\Program Files\rrdb' 43 | cp ~/.cargo/bin/rrdb.exe 'C:\Program Files\rrdb\' 44 | 'C:\Program Files\rrdb\rrdb.exe' init 45 | ``` 46 | 47 | --- 48 | 49 | ### 기본 사용법 50 | 51 | #### Server 52 | 53 | ``` 54 | # 스토리지 초기화 55 | cargo run --bin rrdb init 56 | # 서버 실행 57 | cargo run --bin rrdb run 58 | ``` 59 | 60 | #### Client 61 | 62 | ``` 63 | psql -U rrdb -p 22208 --host 0.0.0.0 64 | ``` 65 | 66 | --- 67 | 68 | ### Syntax 69 | 70 | 1. 키워드는 대소문자를 구별하지 않습니다. 71 | 2. 문자열은 작은 따옴표(')로 구분되며, 따옴표를 포함시킬 때는 2개를 겹칩니다. 72 | 3. 식별자는 단순 텍스트르로 구성해도 되고, 큰 따옴표(")로 구분해도 됩니다. 73 | 74 | #### Database 75 | 76 | ``` 77 | # 데이터베이스 리스트업 78 | SHOW DATABASES; 79 | ``` 80 | 81 | ``` 82 | # 데이터베이스 생성 83 | CREATE DATABASE "database name"; 84 | ``` 85 | 86 | ``` 87 | # 데이터베이스 삭제 88 | DROP DATABASE "database name"; 89 | ``` 90 | 91 | ``` 92 | # 데이터베이스 변경 93 | ALTER DATABASE "from name" rename to "to name"; 94 | ``` 95 | 96 | ``` 97 | # 데이터베이스 변경 98 | USE "database name"; 99 | or 100 | \c "database name"; 101 | ``` 102 | 103 | #### Table 104 | 105 | ``` 106 | # 테이블 목록 조회 107 | SHOW TABLES 108 | ``` 109 | 110 | ``` 111 | # 테이블 상세정보 조회 112 | DESC "table name" 113 | ``` 114 | 115 | ``` 116 | # 테이블 생성 117 | # (table_constraint는 차후 추가할 예정입니다.) 118 | CREATE TABLE [ IF NOT EXISTS ] "table name" 119 | ( 120 | [ 121 | { 122 | "column name" data_type [ column_constraint [ ... ] ] 123 | } 124 | [, ... ] 125 | ] 126 | ) 127 | 128 | # column_constraint는 아래 형태 중 하나입니다. 129 | # (CONSTRAINT나 CHECK, UNIQUE, REFERENCES 등은 차후 추가할 예정입니다.) 130 | { 131 | NOT NULL | 132 | NULL | 133 | DEFAULT default_expr | 134 | PRIMARY KEY index_parameters 135 | } 136 | ``` 137 | 138 | ``` 139 | # 테이블 수정 140 | 141 | 1. ALTER TABLE [ IF EXISTS ] name 142 | action 143 | 2. ALTER TABLE [ IF EXISTS ] name 144 | RENAME [ COLUMN ] column_name TO new_column_name 145 | 3. ALTER TABLE [ IF EXISTS ] name 146 | RENAME TO new_name 147 | 148 | # action은 다음 중 하나입니다. 149 | 150 | 1. ADD [ COLUMN ] column_name data_type [ column_constraint [ ... ] ] # 향후 [IF NOT EXISTS] 신택스 추가 필요 151 | 2. DROP [ COLUMN ] column_name # 향후 [ IF EXISTS ] 신택스 추가 필요 152 | 3. ALTER [ COLUMN ] column_name [ SET DATA ] TYPE data_type 153 | 4. ALTER [ COLUMN ] column_name SET DEFAULT expression 154 | 5. ALTER [ COLUMN ] column_name DROP DEFAULT 155 | 6. ALTER [ COLUMN ] column_name { SET | DROP } NOT NULL 156 | ``` 157 | 158 | #### Insert 159 | 160 | ``` 161 | INSERT INTO table_name ( column_name [, ...] ) 162 | { 163 | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] 164 | | 165 | select_query 166 | } 167 | [, ...] ] 168 | ``` 169 | 170 | #### Select 171 | 172 | ``` 173 | SELECT 174 | [ * | expression [ [ AS ] output_name ] [, ...] ] 175 | [ FROM from_item [, ...] ] 176 | [ WHERE condition ] 177 | [ GROUP BY grouping_element [, ...] ] 178 | [ HAVING condition ] 179 | [ ORDER BY expression [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ] 180 | [ LIMIT limit_number ] 181 | [ OFFSET offset_number ] 182 | 183 | from_item은 다음 중 하나입니다. 184 | 1. table_name [ [ AS ] alias ] 185 | 2. ( select ) [ AS ] alias 186 | ``` 187 | 188 | #### Update 189 | 190 | ``` 191 | UPDATE table_name 192 | SET { column_name = { expression } } [, ...] 193 | [ WHERE condition ] 194 | ``` 195 | 196 | #### Delete 197 | 198 | ``` 199 | DELETE FROM table_name 200 | [ WHERE condition ] 201 | ``` 202 | -------------------------------------------------------------------------------- /src/engine/wal/manager/builder.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::config::launch_config::LaunchConfig; 4 | use crate::engine::wal::endec::{WALDecoder, WALEncoder}; 5 | use crate::engine::wal::types::{EntryType, WALEntry}; 6 | use crate::errors; 7 | use crate::errors::wal_errors::WALError; 8 | 9 | use super::WALManager; 10 | 11 | pub struct WALBuilder<'a> { 12 | config: &'a LaunchConfig, 13 | } 14 | 15 | impl<'a> WALBuilder<'a> { 16 | pub fn new(config: &'a LaunchConfig) -> Self { 17 | Self { config } 18 | } 19 | 20 | pub async fn build(&self, decoder: T, encoder: D) -> errors::Result> 21 | where 22 | T: WALDecoder>, 23 | D: WALEncoder>, 24 | { 25 | let (sequence, entries) = self.load_data(decoder).await?; 26 | 27 | Ok(WALManager::new( 28 | sequence, 29 | entries, 30 | self.config.wal_segment_size as usize, 31 | PathBuf::from(self.config.wal_directory.clone()), 32 | self.config.wal_extension.to_string(), 33 | encoder, 34 | )) 35 | } 36 | 37 | async fn load_data(&self, decoder: T) -> errors::Result<(usize, Vec)> 38 | where 39 | T: WALDecoder>, 40 | { 41 | let mut max_sequence = 0; 42 | let mut last_log_path: Option = None; 43 | 44 | let dir_entries = std::fs::read_dir(&self.config.wal_directory) 45 | .map_err(|e| WALError::wrap(e.to_string()))?; 46 | 47 | for entry_result in dir_entries { 48 | let path = match entry_result { 49 | Ok(entry) => entry.path(), 50 | Err(e) => return Err(WALError::wrap(e.to_string())), 51 | }; 52 | 53 | if !path.is_file() { 54 | continue; 55 | } 56 | 57 | // 파일 명 16진수로 변환 58 | let wrapped_parsed_seq = path 59 | .extension() 60 | .filter(|ext_osstr| ext_osstr.to_str() == Some(self.config.wal_extension.as_str())) 61 | .and_then(|_| path.file_stem()) 62 | .and_then(|stem_osstr| stem_osstr.to_str()) 63 | .and_then(|stem_str| usize::from_str_radix(stem_str, 16).ok()); 64 | 65 | if let Some(seq) = wrapped_parsed_seq 66 | && seq > max_sequence 67 | { 68 | max_sequence = seq; 69 | last_log_path = Some(path); 70 | } 71 | } 72 | 73 | let (current_sequence, entries) = last_log_path.map_or_else( 74 | // Case 1: WAL 파일이 하나도 없는 초기 상태 75 | || { 76 | // 첫 번째 WAL 파일이므로 시퀀스는 1로 시작하고, 복구할 엔트리는 없음 77 | Ok::<(usize, Vec), errors::Errors>((1, Vec::new())) 78 | }, 79 | // Case 2: 최신 WAL 파일이 존재하는 상태 80 | |log_path| { 81 | // 최신 WAL 파일의 내용을 읽음 82 | let content = std::fs::read(&log_path).map_err(|e| { 83 | WALError::wrap(format!("failed to read log file {:?}: {}", log_path, e)) 84 | })?; 85 | 86 | // 파일 내용이 비어있는 경우 복구할 엔트리는 없음 87 | if content.is_empty() { 88 | return Ok((max_sequence + 1, Vec::new())); 89 | } 90 | 91 | let saved_entries: Vec = decoder.decode(&content).map_err(|e| { 92 | WALError::wrap(format!( 93 | "failed to decode log file {:?}: {}", 94 | log_path, 95 | e.to_string() 96 | )) 97 | })?; 98 | 99 | let last_entry = match saved_entries.last() { 100 | Some(entry) => entry, 101 | None => return Ok((max_sequence + 1, Vec::new())), 102 | }; 103 | 104 | if matches!(last_entry.entry_type, EntryType::Checkpoint) { 105 | Ok((max_sequence + 1, Vec::new())) 106 | } else { 107 | // 마지막 엔트리가 체크포인트가 아니면 비정상 종료로 간주 108 | Ok((max_sequence, saved_entries)) 109 | } 110 | }, 111 | )?; 112 | 113 | Ok((current_sequence, entries)) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/engine/parser/implements/dml/update.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::dml::parts::_where::WhereClause; 2 | use crate::engine::ast::dml::parts::update_item::UpdateItem; 3 | use crate::engine::ast::dml::update::UpdateQuery; 4 | use crate::engine::lexer::predule::{OperatorToken, Token}; 5 | use crate::engine::parser::predule::{Parser, ParserContext}; 6 | use crate::errors::parsing_error::ParsingError; 7 | use crate::errors::{self}; 8 | 9 | impl Parser { 10 | pub(crate) fn handle_update_query( 11 | &mut self, 12 | context: ParserContext, 13 | ) -> errors::Result { 14 | if !self.has_next_token() { 15 | return Err(ParsingError::wrap("need more tokens")); 16 | } 17 | 18 | let current_token = self.get_next_token(); 19 | 20 | if current_token != Token::Update { 21 | return Err(ParsingError::wrap(format!( 22 | "expected 'UPDATE'. but your input word is '{:?}'", 23 | current_token 24 | ))); 25 | } 26 | 27 | let mut query_builder = UpdateQuery::builder(); 28 | 29 | if !self.has_next_token() { 30 | return Err(ParsingError::wrap("need more tokens")); 31 | } 32 | 33 | // 테이블명 파싱 34 | let table_name = self.parse_table_name(context.clone())?; 35 | query_builder = query_builder.set_target_table(table_name); 36 | 37 | if self.next_token_is_table_alias() { 38 | let alias = self.parse_table_alias()?; 39 | query_builder = query_builder.set_target_alias(alias); 40 | } 41 | 42 | if !self.has_next_token() { 43 | return Err(ParsingError::wrap("need more tokens")); 44 | } 45 | 46 | let current_token = self.get_next_token(); 47 | 48 | if current_token != Token::Set { 49 | return Err(ParsingError::wrap(format!( 50 | "expected 'SET'. but your input word is '{:?}'", 51 | current_token 52 | ))); 53 | } 54 | 55 | loop { 56 | if !self.has_next_token() { 57 | break; 58 | } 59 | 60 | let current_token = self.get_next_token(); 61 | 62 | match current_token { 63 | Token::Comma => continue, 64 | Token::Where => { 65 | self.unget_next_token(current_token); 66 | break; 67 | } 68 | Token::SemiColon => { 69 | return Ok(query_builder.build()); 70 | } 71 | Token::Identifier(identifier) => { 72 | if !self.has_next_token() { 73 | return Err(ParsingError::wrap("need more tokens")); 74 | } 75 | 76 | let current_token = self.get_next_token(); 77 | 78 | if current_token != Token::Operator(OperatorToken::Eq) { 79 | return Err(ParsingError::wrap(format!( 80 | "expected '='. but your input word is '{:?}'", 81 | current_token 82 | ))); 83 | } 84 | 85 | if !self.has_next_token() { 86 | return Err(ParsingError::wrap("need more tokens")); 87 | } 88 | 89 | let expression = self.parse_expression(context.clone())?; 90 | 91 | let update_item = UpdateItem { 92 | column: identifier, 93 | value: expression, 94 | }; 95 | 96 | query_builder = query_builder.add_update_item(update_item) 97 | } 98 | _ => { 99 | return Err(ParsingError::wrap(format!( 100 | "unexpected input word: '{:?}'", 101 | current_token 102 | ))); 103 | } 104 | } 105 | } 106 | 107 | // Where 절 파싱 108 | if self.next_token_is_where() { 109 | self.get_next_token(); // where 토큰 삼키기 110 | 111 | let expression = self.parse_expression(context)?; 112 | query_builder = query_builder.set_where(WhereClause { expression }); 113 | } 114 | 115 | Ok(query_builder.build()) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/engine/ast/ddl/create_table.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::ast::{ 2 | types::{Column, ForeignKey, TableName, TableOptions, UniqueKey}, 3 | DDLStatement, SQLStatement, 4 | }; 5 | 6 | /* 7 | CREATE TABLE [IF NOT EXISTS] [database_name.]table_name ( 8 | column_name data_type [NOT NULL | NULL] [PRIMARY KEY] [COMMENT 'comment'], 9 | column_name data_type [NOT NULL | NULL] [PRIMARY KEY] [COMMENT 'comment'], 10 | ... 11 | PRIMARY KEY (column_name), 12 | UNIQUE (column_name), 13 | FOREIGN KEY (column_name) REFERENCES table_name (column_name), 14 | FOREIGN KEY (column_name) REFERENCES table_name (column_name), 15 | ... 16 | ); 17 | */ 18 | 19 | #[derive(Clone, Debug, PartialEq)] 20 | pub struct CreateTableQuery { 21 | pub table: Option, 22 | pub columns: Vec, 23 | pub primary_key: Vec, 24 | pub foreign_keys: Vec, 25 | pub unique_keys: Vec, 26 | pub table_options: Option, 27 | pub if_not_exists: bool, 28 | } 29 | 30 | impl CreateTableQuery { 31 | pub fn builder() -> Self { 32 | CreateTableQuery { 33 | table: None, 34 | columns: vec![], 35 | primary_key: vec![], 36 | foreign_keys: vec![], 37 | unique_keys: vec![], 38 | table_options: None, 39 | if_not_exists: false, 40 | } 41 | } 42 | 43 | pub fn set_table(mut self, table: TableName) -> Self { 44 | self.table = Some(table); 45 | self 46 | } 47 | 48 | pub fn set_table_option(mut self, option: TableOptions) -> Self { 49 | self.table_options = Some(option); 50 | self 51 | } 52 | 53 | pub fn add_column(mut self, column: Column) -> Self { 54 | self.columns.push(column); 55 | self 56 | } 57 | 58 | pub fn set_primary_key(mut self, columns: Vec) -> Self { 59 | self.primary_key = columns; 60 | self 61 | } 62 | 63 | pub fn add_unique_key(mut self, unique_key: UniqueKey) -> Self { 64 | self.unique_keys.push(unique_key); 65 | self 66 | } 67 | 68 | pub fn set_if_not_exists(mut self, if_not_exists: bool) -> Self { 69 | self.if_not_exists = if_not_exists; 70 | self 71 | } 72 | 73 | pub fn build(self) -> SQLStatement { 74 | SQLStatement::DDL(DDLStatement::CreateTableQuery(self)) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use crate::engine::ast::types::DataType; 81 | 82 | use super::*; 83 | 84 | #[test] 85 | fn test_create_table() { 86 | let query = CreateTableQuery::builder() 87 | .set_table(TableName::new(None, "table_name".into())) 88 | .add_column( 89 | Column::builder() 90 | .set_name("column_name".into()) 91 | .set_data_type(DataType::Int) 92 | .set_not_null(true) 93 | .set_primary_key(true) 94 | .set_comment("comment".into()) 95 | .build(), 96 | ) 97 | .set_primary_key(vec!["column_name".into()]) 98 | .set_table_option(TableOptions {}) 99 | .add_unique_key(UniqueKey { 100 | key_name: "unique_key".into(), 101 | database_name: None, 102 | columns: vec!["column_name".into()], 103 | }) 104 | .set_if_not_exists(true) 105 | .build(); 106 | 107 | let expected = SQLStatement::DDL(DDLStatement::CreateTableQuery(CreateTableQuery { 108 | table: Some(TableName::new(None, "table_name".into())), 109 | columns: vec![Column { 110 | name: "column_name".into(), 111 | data_type: DataType::Int, 112 | comment: "comment".into(), 113 | default: None, 114 | not_null: true, 115 | primary_key: true, 116 | }], 117 | primary_key: vec!["column_name".into()], 118 | foreign_keys: vec![], 119 | unique_keys: vec![UniqueKey { 120 | key_name: "unique_key".into(), 121 | database_name: None, 122 | columns: vec!["column_name".into()], 123 | }], 124 | table_options: Some(TableOptions {}), 125 | if_not_exists: true, 126 | })); 127 | 128 | assert_eq!(query, expected); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/pgwire/engine/rrdb.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use tokio::sync::oneshot; 3 | 4 | use crate::engine::ast::SQLStatement; 5 | use crate::engine::server::channel::{ChannelRequest, ChannelResponse}; 6 | use crate::engine::server::shared_state::SharedState; 7 | use crate::engine::types::{ExecuteField, ExecuteResult}; 8 | use crate::pgwire::engine::{Engine, Portal}; 9 | use crate::pgwire::protocol::backend::{ErrorResponse, FieldDescription}; 10 | use crate::pgwire::protocol::{DataRowBatch, SqlState}; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct RRDBPortal { 14 | pub shared_state: SharedState, 15 | pub execute_result: ExecuteResult, 16 | } 17 | 18 | #[async_trait] 19 | impl Portal for RRDBPortal { 20 | // 실제 결과 데이터 리스트 전송 21 | async fn fetch(&mut self, batch: &mut DataRowBatch) -> Result<(), ErrorResponse> { 22 | for row in self.execute_result.rows.iter().cloned() { 23 | let mut writer = batch.create_row(); 24 | 25 | for field in row.fields { 26 | match field { 27 | ExecuteField::Bool(data) => { 28 | writer.write_bool(data); 29 | } 30 | ExecuteField::Integer(data) => { 31 | writer.write_int8(data); 32 | } 33 | ExecuteField::Float(data) => { 34 | writer.write_float8(data); 35 | } 36 | ExecuteField::String(data) => { 37 | writer.write_string(&data); 38 | } 39 | ExecuteField::Null => { 40 | writer.write_null(); 41 | } 42 | } 43 | } 44 | } 45 | 46 | return Ok(()); 47 | } 48 | } 49 | 50 | pub struct RRDBEngine { 51 | pub shared_state: SharedState, 52 | pub portal: Option, 53 | } 54 | 55 | #[async_trait] 56 | impl Engine for RRDBEngine { 57 | type PortalType = RRDBPortal; 58 | 59 | // 결과 데이터 리스트의 컬럼 정보 전송 60 | async fn prepare( 61 | &mut self, 62 | statement: &SQLStatement, 63 | ) -> Result, ErrorResponse> { 64 | // Server Background Loop와의 통신용 채널 65 | let (response_sender, response_receiver) = oneshot::channel::(); 66 | 67 | if let Err(error) = self 68 | .shared_state 69 | .sender 70 | .send(ChannelRequest { 71 | statement: statement.to_owned(), 72 | response_sender, 73 | connection_id: self.shared_state.client_info.connection_id.clone(), 74 | }) 75 | .await 76 | { 77 | return Err(ErrorResponse::fatal( 78 | SqlState::CONNECTION_EXCEPTION, 79 | error.to_string(), 80 | )); 81 | } 82 | 83 | match response_receiver.await { 84 | Ok(response) => match response.result { 85 | Ok(result) => { 86 | let return_value = Ok(result 87 | .columns 88 | .iter() 89 | .map(|e| FieldDescription { 90 | name: e.name.to_owned(), 91 | data_type: e.data_type.to_owned().into(), 92 | }) 93 | .collect()); 94 | 95 | self.portal = Some(RRDBPortal { 96 | execute_result: result, 97 | shared_state: self.shared_state.clone(), 98 | }); 99 | 100 | return return_value; 101 | } 102 | Err(error) => { 103 | return Err(ErrorResponse::error( 104 | SqlState::SYNTAX_ERROR, 105 | error.to_string(), 106 | )); 107 | } 108 | }, 109 | Err(error) => { 110 | return Err(ErrorResponse::fatal( 111 | SqlState::CONNECTION_EXCEPTION, 112 | error.to_string(), 113 | )); 114 | } 115 | } 116 | } 117 | 118 | async fn create_portal(&mut self, _: &SQLStatement) -> Result { 119 | match &self.portal { 120 | Some(portal) => Ok(portal.to_owned()), 121 | None => { 122 | return Err(ErrorResponse::fatal( 123 | SqlState::CONNECTION_EXCEPTION, 124 | "not prepared yet".to_string(), 125 | )); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/engine/parser/parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use crate::engine::ast::SQLStatement; 4 | use crate::engine::lexer::predule::{Token, Tokenizer}; 5 | use crate::engine::parser::predule::ParserContext; 6 | use crate::errors; 7 | 8 | #[derive(Debug)] 9 | pub struct Parser { 10 | pub current_token: Token, 11 | pub tokens: VecDeque, 12 | } 13 | 14 | impl Parser { 15 | // 파서 객체 생성 16 | pub fn new(tokens: Vec) -> Self { 17 | Self { 18 | current_token: Token::EOF, 19 | tokens: VecDeque::from(tokens), 20 | } 21 | } 22 | 23 | // deorecated 24 | pub fn with_string(text: String) -> errors::Result { 25 | Ok(Self { 26 | current_token: Token::EOF, 27 | tokens: VecDeque::from(Tokenizer::string_to_tokens(text)?), 28 | }) 29 | } 30 | 31 | // 파서 객체 생성 32 | pub fn with_tokens(tokens: VecDeque) -> Self { 33 | Self { 34 | current_token: Token::EOF, 35 | tokens, 36 | } 37 | } 38 | 39 | pub fn parse(&mut self, context: ParserContext) -> errors::Result> { 40 | let mut statements: Vec = vec![]; 41 | 42 | // Top-Level Parser Loop 43 | loop { 44 | if self.has_next_token() { 45 | let current_token = self.get_next_token(); 46 | 47 | match current_token { 48 | Token::EOF => { 49 | // 루프 종료 50 | break; 51 | } 52 | Token::SemiColon => { 53 | // top-level 세미콜론 무시 54 | continue; 55 | } 56 | Token::Create => statements.push(self.handle_create_query(context.clone())?), 57 | Token::Alter => statements.push(self.handle_alter_query(context.clone())?), 58 | Token::Drop => statements.push(self.handle_drop_query(context.clone())?), 59 | Token::Select => { 60 | self.unget_next_token(current_token); 61 | let query = self.handle_select_query(context.clone())?; 62 | statements.push(query.into()); 63 | } 64 | Token::Update => { 65 | self.unget_next_token(current_token); 66 | let query = self.handle_update_query(context.clone())?; 67 | statements.push(query.into()); 68 | } 69 | Token::Insert => { 70 | self.unget_next_token(current_token); 71 | let query = self.handle_insert_query(context.clone())?; 72 | statements.push(query.into()); 73 | } 74 | Token::Delete => { 75 | self.unget_next_token(current_token); 76 | let query = self.handle_delete_query(context.clone())?; 77 | statements.push(query.into()); 78 | } 79 | Token::Backslash => { 80 | let query = self.parse_backslash_query(context.clone())?; 81 | statements.push(query); 82 | } 83 | Token::Show => { 84 | let query = self.parse_show_query(context.clone())?; 85 | statements.push(query); 86 | } 87 | Token::Use => { 88 | let query = self.parse_use_query(context.clone())?; 89 | statements.push(query); 90 | } 91 | Token::Desc => { 92 | let query = self.parse_desc_query(context.clone())?; 93 | statements.push(query); 94 | } 95 | Token::Begin => { 96 | let query = self.parse_begin_query(context.clone())?; 97 | statements.push(query); 98 | } 99 | Token::Commit => { 100 | let query = self.parse_commit_query(context.clone())?; 101 | statements.push(query); 102 | } 103 | Token::Rollback => { 104 | let query = self.parse_rollback_query(context.clone())?; 105 | statements.push(query); 106 | } 107 | _ => { 108 | break; 109 | } 110 | } 111 | } else { 112 | break; 113 | } 114 | } 115 | 116 | Ok(statements) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/engine/actions/ddl/alter_database.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind as IOErrorKind; 2 | 3 | use crate::engine::DBEngine; 4 | use crate::engine::ast::ddl::alter_database::{AlterDatabaseAction, AlterDatabaseQuery}; 5 | 6 | use crate::engine::encoder::schema_encoder::StorageEncoder; 7 | use crate::engine::schema::database::DatabaseSchema; 8 | use crate::engine::types::{ 9 | ExecuteColumn, ExecuteColumnType, ExecuteField, ExecuteResult, ExecuteRow, 10 | }; 11 | use crate::errors; 12 | use crate::errors::execute_error::ExecuteError; 13 | 14 | impl DBEngine { 15 | pub async fn alter_database(&self, query: AlterDatabaseQuery) -> errors::Result { 16 | let encoder = StorageEncoder::new(); 17 | 18 | let base_path = self.get_data_directory(); 19 | 20 | #[allow(clippy::single_match)] 21 | match query.action { 22 | Some(action) => match action { 23 | AlterDatabaseAction::RenameTo(rename) => { 24 | // 기존 데이터베이스명 25 | let from_database_name = query 26 | .database_name 27 | .clone() 28 | .ok_or_else(|| ExecuteError::wrap("no database name".to_string()))?; 29 | 30 | // 변경할 데이터베이스명 31 | let to_database_name = rename.name; 32 | 33 | // 실제 데이터베이스 디렉터리 경로 34 | let mut from_path = base_path.clone(); 35 | let mut to_path = base_path.clone(); 36 | 37 | from_path.push(from_database_name); 38 | to_path.push(to_database_name.clone()); 39 | 40 | // 디렉터리명 변경 41 | let result = tokio::fs::rename(&from_path, &to_path).await; 42 | 43 | if let Err(error) = result { 44 | match error.kind() { 45 | IOErrorKind::NotFound => { 46 | return Err(ExecuteError::wrap("database not found".to_string())); 47 | } 48 | _ => { 49 | return Err(ExecuteError::wrap( 50 | "database alter failed".to_string(), 51 | )); 52 | } 53 | } 54 | } 55 | 56 | // config data 파일 내용 변경 57 | let mut config_path = to_path.clone(); 58 | config_path.push("database.config"); 59 | 60 | match tokio::fs::read(&config_path).await { 61 | Ok(data) => { 62 | let database_config: Option = 63 | encoder.decode(data.as_slice()); 64 | 65 | match database_config { 66 | Some(mut database_config) => { 67 | database_config.database_name = to_database_name; 68 | if let Err(_error) = tokio::fs::write( 69 | config_path, 70 | encoder.encode(database_config), 71 | ) 72 | .await 73 | { 74 | return Err(ExecuteError::wrap( 75 | "failed to write database config", 76 | )); 77 | } 78 | } 79 | None => { 80 | return Err(ExecuteError::wrap("invalid config data")); 81 | } 82 | } 83 | } 84 | Err(error) => match error.kind() { 85 | IOErrorKind::NotFound => { 86 | return Err(ExecuteError::wrap("database not found")); 87 | } 88 | _ => { 89 | return Err(ExecuteError::wrap(format!("{:?}", error))); 90 | } 91 | }, 92 | } 93 | } 94 | }, 95 | None => {} 96 | } 97 | 98 | Ok(ExecuteResult { 99 | columns: (vec![ExecuteColumn { 100 | name: "desc".into(), 101 | data_type: ExecuteColumnType::String, 102 | }]), 103 | rows: (vec![ExecuteRow { 104 | fields: vec![ExecuteField::String("alter database".into())], 105 | }]), 106 | }) 107 | } 108 | } 109 | --------------------------------------------------------------------------------