이전 게시물에서 SQLITE 파일 형식을 조사하고 간단한 SQL 파서를 구축했습니다. 이제이 조각들을 모아 쿼리 평가자를 구현할 때입니다! 이 게시물에서는 SQL 쿼리를 평가하기위한 토대를 마련하고 기본 선택 문을 처리 할 수있는 쿼리 평가기를 작성합니다. 첫 번째 구현은 아직 필터링, 정렬, 그룹화 또는 결합을 지원하지 않지만 이러한 기능을 향후 게시물에 추가하기위한 기초를 제공합니다.
항상 그렇듯이이 게시물의 전체 소스 코드는 GitHub에서 사용할 수 있습니다.
테스트 데이터베이스를 설정하십시오
쿼리를 평가하기 전에 쿼리하려면 데이터베이스가 필요합니다. 먼저, 우리는 ID와 값이있는 단일 테이블 테이블 1 인 두 개의 열이있는 간단한 데이터베이스를 만드는 것으로 시작합니다.
sqlite3 queries_test.db sqlite> 테이블 생성 table1 (id integer, value text)> insert (id, value) 값 …> (1, 11 ‘), …> (2,’12 ‘ ), …> (3, ’13’);
⚠️ 기존 SQLITE 데이터베이스를 사용하여 쿼리를 테스트하려는 유혹을받을 수 있지만 구현은 아직 오버 플로우 페이지를 지원하지 않으므로 데이터베이스 파일에서 데이터를 읽지 못할 수 있습니다.
이 섹션은 Rust 구현에만 해당됩니다. 다른 언어로 팔로우하는 경우 안전하게 건너 뛸 수 있습니다!
현재 우리의 호출기는 독점적 인 변수 참조로만 제공됩니다. 이것은 첫 번째 유스 케이스에서는 괜찮 았지만 일단 더 복잡한 기능을 구축하기 시작하면이 제한을 유지함으로써 제약을 받게됩니다. 내부 변수 필드는 아크입니다 <_ >> 및 아크 <_ >> 포장으로 포켓을 공유 할 수 있습니다. 이를 통해 호출기를 효과적으로 복제하고 자치구 체커와의 문제를 해결하지 않고도 여러 위치에서 사용할 수 있습니다. 이 프로젝트 의이 단계에서 간단한 RC <_ >> 사용을 선택할 수 있었지만 처음부터 스레드 안전 대응 물을 사용할 것입니다. 궁극적으로 호출기에 대한 동시 액세스를 지원해야합니다.
// src/pager.rs-#[derive(Debug, Clone)]
+#[derive(Debug)]
펍 구조물 호출기 – 입력 : i, + 입력 : 아크 > page_size : usize, – 페이지 : 해시 맵
READ_PAGE 및 LOAD_PAGE 메소드는 그에 따라 업데이트해야합니다.
임시 휴대용 소형 무선 호출기 펍 fn read_page (& self, n : usize) -> anyaw :: result
read_page 메소드에 대한 두 가지 사항 :
- 캐시에서 페이지를 읽는 첫 번째 시도는 블록에 중첩되어 읽기 잠금의 범위를 제한하고 쓰기 잠금을 획득하기 전에 해제되도록합니다.
- 쓰기 잠금을 획득 한 후 두 개의 잠금 획득 사이에 삽입 된 경우에 페이지가 캐시에 이미 있는지 다시 확인하십시오.
마찬가지로, 쿼리 평가자에 사용할 값 열거의 소유 버전을 정의하십시오.
틀[derive(Debug, Clone)]
Pub Enum onedValue Null, String (Rc
마지막으로 커서 구조물을 풍부하게하고 필드의 가치를 소유권 번호로 반환하는 방법을 제공합니다.
Imple Cursor Pub fn oned_field (& self, n : usize) -> 옵션
쿼리 엔진은 두 가지 주요 구성 요소로 구성됩니다.
- 테이블 스캔 및 행 필터링과 같은 데이터베이스의 중첩 작업을 나타내는 반복자와 같은 연산자 열거. 첫 번째 구현에는 테이블에서 모든 행을 생성하는 Seqscan 연산자 만 포함됩니다.
- 구문 분석 된 SQL 쿼리를 검색하고 쿼리 결과를 생성하기 위해 평가할 수있는 연산자를 생성하는 플래너 구조.
연산자의 열거를 정의하는 것으로 시작하겠습니다.
어쨌든 사용 :: 문맥. Crate :: Cursor :: Scanner, Value :: OwnedValue를 사용하십시오. 틀[derive(Debug)]
Pub Enum Operator Seqscan (Seqscan), Imple Operator Pub fn Next_row (& mut self) -> 어쨌든 :: 결과 <선택 사항<&[OwnedValue]>> self-operator :: seqscan (s) => s.next_row (),
쿼리를 평가 한 결과는 연산자의 Next_row 메소드를 반복적으로 호출하여 얻습니다. 반환 된 슬라이스의 각 값은 쿼리 결과의 열에 해당합니다.
Seqscan 구조는 테이블과 행을 스캔합니다.
틀[derive(Debug)]
Pub Struct Seqscan 필드 : Vec
Seqscan 구조는 각 레코드에서 읽을 필드 인덱스 목록을 생성하는 스캐너로 초기화되고 테이블의 모든 행에 대한 레코드를 스캔합니다. 읽을 필드 수는 모든 행에 대해 동일하므로 버퍼를 Prealloce로하여 선택한 필드의 값을 저장할 수 있습니다. Next_row 메소드는 스캐너에서 다음 레코드를 검색하고 요청 된 필드 (인덱스별로 지정 됨)를 추출하고 버퍼에 저장합니다.
선택한 명령문을 평가할 연산자가 있으므로 구문 분석 된 SQL 쿼리에서 연산자를 생성하는 플래너 구조로 진행합시다.
사용 :: 보석, 문맥, OK; Super :: Operator :: Operator, Seqscan을 사용하십시오. Pub Struct Planner <'d> DB : & ‘d db, inft’d> 플래너 <' d> Pub Fn New (DB : & ‘D DB) -> Self Self DB Pub FN 컴파일 (자체, 진술 : & ast : : 문) -> anywaw :: 결과
Planner Struct는 데이터베이스를 참조하여 초기화 된 SQL 문을 검색하고 해당 연산자를 반환하는 컴파일 방법을 제공합니다. 컴파일 메소드는 각 유형의 SQL 문에 대한 특정 메소드로 발산됩니다.
선택 문을 위해 연산자를 구축하는 방법을 살펴 보겠습니다.
임프 <'d> 입안자 <' d> {fn compile_select (self, select : & ast :: selectstatement) -> anywaw :: result
먼저, select 문의 테이블 이름과 일치하는 테이블 메타 데이터 항목을 찾으십시오. 그런 다음 명령문의 결과 열을 반복하고 모든 열로 확장하거나 테이블 메타 데이터의 열 이름을 검사하여 각 레코드에서 읽을 수있는 필드 지수 목록을 만듭니다.
마지막으로 전체 탭을 스캔하고 각 행에 대해 선택한 필드를 생성하는 Seqscan 연산자를 만듭니다.
REPL 쿼리 평가
이제 테스트에 쿼리 평가자를 소개 할 시간입니다! 원시 SQL 쿼리를 읽고 평가하는 간단한 기능을 만듭니다.
fn eval_query (db : & db :: db, query : & str) -> anyaw :: result <()> parsed_query = sql :: parse_statement (query, false)? Mut Op = Engine :: Planner :: new (db) .compile (& parsed_query)? ret (value) = op.next_row () let format = value .iter (). map (tostring :: to_string) .collect :: <_ >> (). Jein ( “OK ((())))
이 기능은 파이프 라인을 만듭니다. 결과 연산자의 Next_row ()로의 반복 호출은 SQL 쿼리를 구문 분석하고 플래너에 연산자를 빌드 한 다음 결과의 각 행을 검색하고 표시합니다.
마지막 단계는이 기능을 Repl 루프에서 사용하는 것입니다.
// src/main.rs // […]
fn cli (mut db : db :: db) -> 어쨌든 :: 결과 <()> {print_flushed ( “rqlite>”)? mut line_buffer = string :: new (); 잠그다(). read_line (& mut line_buffer) .is_ok () {match line_buffer.trim () “.exit”=> break, “.tables”=> display_tables (& mut db)? , +stmt = >> eval_query (& db, stmt)? , -stmt => match sql :: parse_statement (stmt, true) -ok (stmt) => -println! ( “:?”, stmt); ( “오류 :”, e); -print_flushed ( “\ nrqlite>”) line_buffer.clear ();
이제 간단한 선택 문을 평가하기 위해 REPL을 실행할 수 있습니다.
cargolan-queries_test.db rqlite> table1에서 선택 *;
모든 것이 잘되면 다음 출력이 표시됩니다.
1 |. 11 2 |
우리의 작은 데이터베이스 엔진이 형성되기 시작했습니다! 이제 간단한 선택 쿼리를 분석하고 평가할 수 있습니다. 그러나 완전히 기능적인 데이터베이스 엔진이라고 부르기 전에 아직 다루어야 할 것이 많습니다. 다음 포스트에서는 행을 필터링하고 인덱스를 읽고 정렬 및 그룹화를 구현하는 방법을 발견합니다.