diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c0c8bc..ff3c98d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,3 +26,7 @@ jobs: run: raco pkg install --auto --batch - name: Check compilation run: raco exe --gui main.rkt + - name: Create C lib for FFI + run: gcc -c -fPIC utilities/bplustree.c -o utilities/bplustree.o && gcc utilities/bplustree.o -shared -o libbplustree.so + - name: Check test + run: raco test main.rkt diff --git a/README.org b/README.org index a100a08..1bb3048 100644 --- a/README.org +++ b/README.org @@ -1,7 +1,9 @@ #+TITLE: Racket Tower DB 🎾 🏰 -* About -This is a toy database :) +This is a toy relational database using unclustered indexing for queries. + +It is written in Racket for us to be able to practice Scheme lisp and also leverage the +ecosystem. Do not use it professionally (it's always good to warn). @@ -11,18 +13,42 @@ Do not use it professionally (it's always good to warn). - [X] Write the logical schema onto disk - [X] Reads of tables and schemas - [X] Persistent storage - - [ ] Constraints - + [ ] Add row-id to row reading from the disk - + [ ] Add local constraints to table struct - + [ ] Do a proof of concept using the backticked lambdas for local constraints - + [ ] Do a proof of concept using the hashed values for set constraints - + [ ] Add a serialization for constraints + - [X] Constraints + + [X] Add row-id to row reading from the disk + + [X] Add local constraints to table struct + + [X] Do a proof of concept using the backticked lambdas for local constraints + + [X] Do a proof of concept using the hashed values for set constraints + + [X] Add a serialization for constraints + - [X] Keys (logical and indexing) + - [X] Internal tooling for searches +** RacketowerDB 2.0 [0/3] - [ ] Query Language + - [ ] Improve searching with different types - [ ] Server - - [ ] Keys (logical and indexing) - - [ ] Internal tooling for searches -** RacketowerDB 2.0 [0/3] - [ ] Procedures - [ ] Transactions - [ ] Cascading +* How to use RacketowerDB + +** Nix + +#+begin_src shell + > nix develop + > just run + > just test +#+end_src + +** Guix + +TODO + +** Developers + +- EduardoLR10 +- MMagueta +- z-silver + +* Dr.Nekoma + +Built live on [[https://www.twitch.tv/drnekoma][Twitch]] and archived on [[https://youtube.com/playlist?list=PLafNlGaxdt65iwFNDtAG-WXsoRoi-ZJmx&si=he3YkzRNaUHYsyOr][Youtube]]. diff --git a/ast.rkt b/ast.rkt index e1c9e06..f80ec9d 100644 --- a/ast.rkt +++ b/ast.rkt @@ -34,8 +34,8 @@ (case (type-name self) [[INTEGER] (integer32 (integer-bytes->integer byte-stream #t))] [[VARCHAR] (stringl (bytes->string/utf-8 byte-stream))] - [else (raise 'error-with-unknown-type-from-bytes)]) - (raise 'error-with-from-bytes-size-check)))) + [else (error "Unknown type. Supported types: INTEGER and VARCHAR")]) + (error "Unmatch between type byte size and bytes from byte stream")))) (define (to-byte-size self) (type-byte-size self))] #:methods gen:serializable @@ -56,11 +56,22 @@ (byte-size-value (integer-bytes->integer (subbytes byte-stream (+ 1 name-length)) #t))] (type name-value byte-size-value)))]) +(define (stringl-trim string-1) + (string-trim (stringl-value string-1) "\u0000" #:left? false #:repeat? #t)) + +(define (stringl-equal string-1 string-2 recur-equal) + (recur-equal (stringl-trim string-1) + (stringl-trim string-2))) + (define-serializable stringl [value] #:transparent #:guard (checked-guard [(value . string?)] value) + #:methods gen:equal+hash + [(define equal-proc stringl-equal) + (define hash-proc (lambda (stringl rec-hash) (rec-hash (stringl-trim stringl)))) + (define hash2-proc (lambda (stringl rec-hash) (rec-hash (stringl-trim stringl))))] #:methods gen:serializable [(define (serialize self #:size [size #f]) (unless size @@ -136,7 +147,7 @@ (cond [(table? entity) entity] [(procedure? entity) - (error "Don't write procedures yet")]))) + (error (format "Found procedure ~s instead of a table" table-name))]))) (define (table-column-value table table-name column-name) (define (column-name-message column-name) @@ -179,7 +190,7 @@ (bytes-append constraint-size serialized-constraint))) (define constraints-count (length constraint-list)) (unless (<= constraints-count #xff) - (raise 'using-more-constraints-than-supported)) + (error "Using more than supported constraints (max 255)")) (let [(serialized-count (integer->integer-bytes constraints-count 1 #f)) (serialized-constraints (bytes-join (map serialize-constraint constraint-list) #""))] (bytes-append serialized-count serialized-constraints))) diff --git a/justfile b/justfile index c3c1679..f7eeee2 100644 --- a/justfile +++ b/justfile @@ -22,5 +22,5 @@ uninstall NAME: docs NAME: raco docs {{ NAME }} -test: - raco test +test: bplustree + raco test main.rkt diff --git a/main.rkt b/main.rkt index dbd838b..0dee3e7 100644 --- a/main.rkt +++ b/main.rkt @@ -15,21 +15,12 @@ (module+ test (require rackunit) - ;; Any code in this `test` submodule runs when this file is run using DrRacket - ;; or with `raco test`. The code here does not run when this file is - ;; required by another module. - - (check-equal? (+ 2 2) 4)) - -;; http://docs.racket-lang.org/guide/Module_Syntax.html#%28part._main-and-test%29 -(module+ main - + (define field-name (field 0 (type 'VARCHAR 7))) (define field-editor (field 1 (type 'VARCHAR 10))) (define field-year (field 1 (type 'INTEGER 4))) (define field-age (field 2 (type 'INTEGER 4))) - (define constraint-1 #`(lambda [rows] (andmap @@ -39,7 +30,7 @@ (let [(raw-name (car raw-field)) (raw-value (cdr raw-field))] (if (equal? raw-name "AGE") - (>= (integer32-value raw-value) 50) + (<= (integer32-value raw-value) 50) #t))) row)) rows))) @@ -74,81 +65,94 @@ (define procedure-test (procedure "procedure")) (define schema (make-hash (list))) + (define row0 + `(("NAME" . ,(stringl "Marinho")) + ("EDITOR" . ,(stringl "Kakoune")) + ("AGE" . ,(integer32 29)))) + (define row1 `(("NAME" . ,(stringl "Nathan")) ("EDITOR" . ,(stringl "Visual Studio Code")) - ("AGE" . ,(integer32 100)))) + ("AGE" . ,(integer32 23)))) (define row2 `(("NAME" . ,(stringl "Lemos")) ("EDITOR" . ,(stringl "Emacs")) - ("AGE" . ,(integer32 100)))) + ("AGE" . ,(integer32 24)))) (define row3 - `(("MODEL" . ,(stringl "Ford")) - ("YEAR" . ,(integer32 1996)))) + `(("NAME" . ,(stringl "Magueta")) + ("EDITOR" . ,(stringl "Emacs")) + ("AGE" . ,(integer32 24)))) (define row4 - `(("MODEL" . ,(stringl "Abcd")) - ("YEAR" . ,(integer32 1999)))) + `(("MODEL" . ,(stringl "Model X")) + ("YEAR" . ,(integer32 2015)))) (define row5 - `(("MODEL" . ,(stringl "asdf")) - ("YEAR" . ,(integer32 1996)))) + `(("MODEL" . ,(stringl "Model S")) + ("YEAR" . ,(integer32 2012)))) (define row6 - `(("MODEL" . ,(stringl "jkl;")) - ("YEAR" . ,(integer32 1997)))) + `(("MODEL" . ,(stringl "R1S")) + ("YEAR" . ,(integer32 2021)))) (define row7 - `(("MODEL" . ,(stringl "Mach 6")) - ("YEAR" . ,(integer32 1997)))) - - (define row8 - `(("MODEL" . ,(stringl "qwer")) - ("YEAR" . ,(integer32 1997)))) + `(("MODEL" . ,(stringl "Beetle")) + ("YEAR" . ,(integer32 1938)))) - (define row9 + (define row8 `(("MODEL" . ,(stringl "Mach 5")) - ("YEAR" . ,(integer32 1997)))) - - (define row10 - `(("MODEL" . ,(stringl "Tesla")) - ("YEAR" . ,(integer32 2020)))) + ("YEAR" . ,(integer32 1967)))) - (define row11 - `(("MODEL" . ,(stringl "Honda")) - ("YEAR" . ,(integer32 1996)))) - + (define row9 + `(("MODEL" . ,(stringl "Mach 6")) + ("YEAR" . ,(integer32 1967)))) + (hash-set! schema "PROGRAMMER" programmer-table) (hash-set! schema "CAR" car-table) - (set! schema (write-rows-to-disk schema "CAR" (list row3 row4 row5 row6 row7 row8 row9 row10 row11))) - (set! schema (write-rows-to-disk schema "PROGRAMMER" (list row1 row2))) - ;; (println (read-table-values-from-disk schema "PROGRAMMER")) - ;; (define-values (pages amount-already-read) (build-pages 0 1 1 (+ 7 10 4) 0 "AGE" schema "PROGRAMMER")) - (println (search schema (query "PROGRAMMER" "AGE" 100))) - ;; (check-local-constraints programmer-table (list row1 row2)) - ;; (hash-set! schema "TEST" procedure-test) - ;; (write-schema-to-disk schema) - ;; (set! schema (read-schema-from-disk "schema")) - ;; (check-local-constraints (hash-ref schema "PROGRAMMER") (list row1 row2)) - ;; (println schema) - ;; (write-table-to-disk programmer-table "PROGRAMMER") - ;; (let ((read-table (read-table-from-disk "PROGRAMMER"))) - ;; (hash-set! schema "PROGRAMMER" read-table) - ;; (set! schema (write-rows-to-disk schema "PROGRAMMER" (list row1 row2))) - ;; (println schema)) - ;; (tree-test) - ) - - ;;(exit-handler) - ;;(server-entrypoint) - - ;;(require racket/cmdline) - ;;(define who (box "world")) - ;;(command-line - ;; #:program "my-program" - ;; #:once-each - ;; [("-n" "--name") name "Who to say hello to" (set-box! who name)] - ;; #:args () - ;; (printf "hello ~a~n" (unbox who)))) + (define programmer-list (list row0 row1 row2 row3)) + (define car-list (list row4 row5 row6 row7 row8 row9)) + + (test-case + "Write mocked data into disk with PROGRAMMER table" + (set! schema (write-rows-to-disk schema "PROGRAMMER" programmer-list)) + (check-equal? (table-row-id (hash-ref schema "PROGRAMMER")) 4)) + + (test-case + "Write mocked data into disk with CAR table" + (set! schema (write-rows-to-disk schema "CAR" car-list)) + (check-equal? (table-row-id (hash-ref schema "CAR")) 6)) + + (test-case + "Read written data from disk with CAR table" + (check-equal? (read-table-values-from-disk schema "CAR") (map make-hash car-list))) + + (test-case + "Check for constraints in the PROGRAMMER table" + (check-true (check-local-constraints (hash-ref schema "PROGRAMMER") programmer-list))) + + (test-case + "Check writing schema into disk" + (write-schema-to-disk schema) + (check-match (read-schema-from-disk "schema") schema)) + + (test-case + "Check writing table PROGRAMMER into disk" + (write-table-to-disk programmer-table "PROGRAMMER") + (check-match (read-table-from-disk "PROGRAMMER") programmer-table)) + (test-case + "Check writing table CAR into disk" + (write-table-to-disk car-table "CAR") + (check-match (read-table-from-disk "CAR") car-table)) + + (test-case + "Query integer values from persisted PROGRAMMER table" + (check-equal? (search schema (query "PROGRAMMER" "AGE" 24)) (map make-hash (list row2 row3)))) + (test-case + "Query integer values from persisted CAR table" + (check-equal? (search schema (query "CAR" "YEAR" 1967)) (map make-hash (list row8 row9))))) + +(module+ main + (println "Welcome to RacketowerDB!") + (println "TODO: Server yet to be implemented xD")) diff --git a/ndf/data/CAR.ndf b/ndf/data/CAR.ndf index 66e547a..4679b54 100644 Binary files a/ndf/data/CAR.ndf and b/ndf/data/CAR.ndf differ diff --git a/ndf/data/PROGRAMMER.ndf b/ndf/data/PROGRAMMER.ndf index 65b507a..17d1c31 100644 Binary files a/ndf/data/PROGRAMMER.ndf and b/ndf/data/PROGRAMMER.ndf differ diff --git a/ndf/entities/CAR.ndf b/ndf/entities/CAR.ndf index 6863c9f..8e814d5 100644 Binary files a/ndf/entities/CAR.ndf and b/ndf/entities/CAR.ndf differ diff --git a/ndf/entities/PROGRAMMER.ndf b/ndf/entities/PROGRAMMER.ndf index 1aa0ccd..2981921 100644 Binary files a/ndf/entities/PROGRAMMER.ndf and b/ndf/entities/PROGRAMMER.ndf differ diff --git a/ndf/schemas/schema.ndf b/ndf/schemas/schema.ndf index 6780c0a..886d6ed 100644 Binary files a/ndf/schemas/schema.ndf and b/ndf/schemas/schema.ndf differ diff --git a/util.rkt b/util.rkt index b753e70..f7a93cc 100644 --- a/util.rkt +++ b/util.rkt @@ -22,7 +22,7 @@ [('entity) "ndf/entities/"] [('schema) "ndf/schemas/"] [('data) "ndf/data/"] - [else (raise 'error-not-specified-datatype)]))] + [else (error (format "Unknown data type for filename creation: ~a" data?))]))] (string-append path (string-append name ".ndf")))) (define (chunk-by-size chunk-size elements)