├── .gitignore ├── LICENSE ├── Makefile ├── README.org ├── img ├── Compiling PigletQL Query Tree - eng.png ├── Compiling PigletQL Query Tree - eng.svg ├── Compiling PigletQL Query Tree.jpg ├── Compiling PigletQL Query Tree.png ├── Compiling PigletQL Query Tree.svg ├── General SQL Intepreter Structure - eng.png ├── General SQL Intepreter Structure - eng.svg ├── General SQL Intepreter Structure.jpg ├── General SQL Intepreter Structure.png ├── General SQL Intepreter Structure.svg ├── Join Select Example - eng.png ├── Join Select Example - eng.svg ├── Join Select Example.jpg ├── Join Select Example.png ├── Join Select Example.svg ├── PigletQL Structure - eng.png ├── PigletQL Structure - eng.svg ├── PigletQL Structure.jpg ├── PigletQL Structure.png ├── PigletQL Structure.svg ├── PigletQL Tuple Path - eng.png ├── PigletQL Tuple Path - eng.svg ├── PigletQL Tuple Path.jpg ├── PigletQL Tuple Path.png ├── PigletQL Tuple Path.svg ├── Poster.jpg ├── Project Example - eng.png ├── Project Example - eng.svg ├── Project Example.jpg ├── Project Example.png ├── Project Example.svg ├── Select Example - eng.png ├── Select Example - eng.svg ├── Select Example.jpg ├── Select Example.png ├── Select Example.svg ├── Sort Example - eng.png ├── Sort Example - eng.svg ├── Sort Example.jpg ├── Sort Example.png ├── Sort Example.svg ├── Volcano Model - eng.png ├── Volcano Model - eng.svg ├── Volcano Model.jpg ├── Volcano Model.png ├── Volcano Model.svg ├── Volcano Operator - eng.png ├── Volcano Operator - eng.svg ├── Volcano Operator.jpg ├── Volcano Operator.png └── Volcano Operator.svg ├── pigletql-catalogue-test.c ├── pigletql-catalogue.c ├── pigletql-catalogue.h ├── pigletql-def.h ├── pigletql-eval-test.c ├── pigletql-eval.c ├── pigletql-eval.h ├── pigletql-parser-test.c ├── pigletql-parser.c ├── pigletql-parser.h ├── pigletql-validate-test.c ├── pigletql-validate.c ├── pigletql-validate.h ├── pigletql.c └── sql-interpreter.org /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # global 55 | GPATH 56 | GRTAGS 57 | GTAGS 58 | 59 | pigletql 60 | pigletql-test 61 | pigletql-parser-test 62 | pigletql-eval-test 63 | pigletql-catalogue-test 64 | pigletql-validate-test 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -std=gnu11 -O2 -g 3 | 4 | TESTS = pigletql-eval-test pigletql-parser-test pigletql-catalogue-test pigletql-validate-test 5 | 6 | all: pigletql 7 | 8 | test: $(TESTS) 9 | ./pigletql-eval-test 10 | ./pigletql-parser-test 11 | ./pigletql-catalogue-test 12 | ./pigletql-validate-test 13 | 14 | pigletql: pigletql.c pigletql-parser.c pigletql-eval.c pigletql-catalogue.c pigletql-validate.c 15 | $(CC) $(CFLAGS) $^ -o $@ 16 | 17 | pigletql-catalogue-test: pigletql-catalogue-test.c pigletql-catalogue.c pigletql-eval.c 18 | $(CC) $(CFLAGS) $^ -o $@ 19 | 20 | pigletql-eval-test: pigletql-eval-test.c pigletql-eval.c 21 | $(CC) $(CFLAGS) $^ -o $@ 22 | 23 | pigletql-parser-test: pigletql-parser-test.c pigletql-parser.c 24 | $(CC) $(CFLAGS) $^ -o $@ 25 | 26 | pigletql-validate-test: pigletql-validate-test.c pigletql-parser.c pigletql-catalogue.c pigletql-validate.c pigletql-eval.c 27 | $(CC) $(CFLAGS) $^ -o $@ 28 | 29 | clean: 30 | rm -vf pigletql $(TESTS) 31 | 32 | .PHONY: all clean $(TESTS) 33 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * PigletQL - a tiny SQL-like interpreter 2 | 3 | PigletQL is a tiny SQL-like language featuring a single value type, three kinds of queries (CREATE 4 | TABLE, INSERT, SELECT) and a Volcano-style (see the "Volcano - an extensible and parallel query 5 | evaluation system" paper) interpreter. It does not do any optimization and keeps all data in 6 | memory. 7 | 8 | It was created as an example language for an article ([[https://habr.com/ru/company/badoo/blog/461699/][Russian]], [[https://badootech.badoo.com/volcanic-piglet-or-do-it-yourself-sql-65357cd9d891][English]]) on SQL interpreter 9 | architecture. 10 | 11 | * Building 12 | 13 | It should be trivial to compile the interpreter on Linux using a recent GCC. Just clone the repo 14 | and do: 15 | 16 | #+BEGIN_EXAMPLE 17 | 18 | > make 19 | > make test 20 | > ./pigletql 21 | > # your query here 22 | 23 | #+END_EXAMPLE 24 | 25 | * Example queries 26 | 27 | #+BEGIN_EXAMPLE 28 | 29 | > ./pigletql 30 | > create table rel1 (a1,a2,a3); 31 | > insert into rel1 values (1,2,3); 32 | > insert into rel1 values (4,5,6); 33 | > select a1 from rel1; 34 | a1 35 | 1 36 | 4 37 | rows: 2 38 | > 39 | 40 | #+END_EXAMPLE 41 | 42 | #+BEGIN_EXAMPLE 43 | 44 | > ./pigletql 45 | > create table rel1 (a1,a2,a3); 46 | > insert into rel1 values (1,2,3); 47 | > insert into rel1 values (4,5,6); 48 | > select a1 from rel1 where a1 > 3; 49 | a1 50 | 4 51 | rows: 1 52 | > 53 | 54 | #+END_EXAMPLE 55 | 56 | #+BEGIN_EXAMPLE 57 | 58 | > ./pigletql 59 | > create table rel1 (a1,a2,a3); 60 | > insert into rel1 values (1,2,3); 61 | > insert into rel1 values (4,5,6); 62 | > select a1 from rel1 order by a1 desc; 63 | a1 64 | 4 65 | 1 66 | rows: 2 67 | 68 | #+END_EXAMPLE 69 | 70 | #+BEGIN_EXAMPLE 71 | 72 | > ./pigletql 73 | > create table rel1 (a1,a2,a3); 74 | > insert into rel1 values (1,2,3); 75 | > insert into rel1 values (4,5,6); 76 | > create table rel2 (a4,a5,a6); 77 | > insert into rel2 values (7,8,6); 78 | > insert into rel2 values (9,10,6); 79 | > select a1,a2,a3,a4,a5,a6 from rel1, rel2 where a3=a6; 80 | a1 a2 a3 a4 a5 a6 81 | 4 5 6 7 8 6 82 | 4 5 6 9 10 6 83 | rows: 2 84 | 85 | #+END_EXAMPLE 86 | 87 | * Code structure 88 | 89 | - [[file:pigletql-eval.h][pigletql-eval.h]] - evaluation engine, i.e. Volcano-style operators, relations, tuples, etc 90 | 91 | - [[file:pigletql-parser.h][pigletql-parser.h]] - lexer/parser 92 | 93 | - [[file:pigletql-validate.h][pigletql-validate.h]] - query validation logic 94 | 95 | - [[file:pigletql-catalogue.h][pigletql-catalogue.h]] - a catalogue of relations available 96 | 97 | - [[file:pigletql-def.h][pigletql-def.h]] - constants and helpers 98 | 99 | - [[file:pigletql.c][pigletql.c]] - putting everything together 100 | -------------------------------------------------------------------------------- /img/Compiling PigletQL Query Tree - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Compiling PigletQL Query Tree - eng.png -------------------------------------------------------------------------------- /img/Compiling PigletQL Query Tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Compiling PigletQL Query Tree.jpg -------------------------------------------------------------------------------- /img/Compiling PigletQL Query Tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Compiling PigletQL Query Tree.png -------------------------------------------------------------------------------- /img/General SQL Intepreter Structure - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/General SQL Intepreter Structure - eng.png -------------------------------------------------------------------------------- /img/General SQL Intepreter Structure - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Query stringQuery stringQuery representationQuery representationLogical algebra operator treeLogical algebra operator treePhysical algebra operator treePhysical algebra operator treeAlgorithm selectionAlgorithm selectionLexical and syntax analysisLexical and syntax analysisSemantic analysisSemantic analysisOptimizing transformationsOptimizing transformationsOperator tree executionOperator tree executionDatabase catalogueDatabase catalogue -------------------------------------------------------------------------------- /img/General SQL Intepreter Structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/General SQL Intepreter Structure.jpg -------------------------------------------------------------------------------- /img/General SQL Intepreter Structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/General SQL Intepreter Structure.png -------------------------------------------------------------------------------- /img/General SQL Intepreter Structure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Строка запросаСтрока запросаПредставление запросаПредставление запросаДерево операторов логической алгебрыДерево операторов логической алгебрыДерево операторов физической алгебрыДерево операторов физической алгебрыВыбор алгоритмов операторовВыбор алгоритмов операторовЛексический и синтаксический анализЛексический и синтаксический анализСемантический анализСемантический анализОптимизирующие преобразованияОптимизирующие преобразованияИсполнение дерева операторовИсполнение дерева операторовКаталог базы данныхКаталог базы данных -------------------------------------------------------------------------------- /img/Join Select Example - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Join Select Example - eng.png -------------------------------------------------------------------------------- /img/Join Select Example - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanScanScanJoinJoinProject (a1,a2,a3,a4,a5,a6)Project (a1,a2,a3,a4,a5,a6)Select (a3=a6)Select (a3=a6)Relation rel1 (a1,a2,a3)Relation rel1 (a1,a2,a3)Relation rel2 (a4,a5,a6)Relation rel2 (a4,a5,a6)Data outputData output -------------------------------------------------------------------------------- /img/Join Select Example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Join Select Example.jpg -------------------------------------------------------------------------------- /img/Join Select Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Join Select Example.png -------------------------------------------------------------------------------- /img/Join Select Example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanScanScanJoinJoinProject (a1,a2,a3,a4,a5,a6)Project (a1,a2,a3,a4,a5,a6)Select (a3=a6)Select (a3=a6)Отношение rel1 (a1,a2,a3)Отношение rel1 (a1,a2,a3)Отношение rel2 (a4,a5,a6)Отношение rel2 (a4,a5,a6)Вывод данныхВывод данных -------------------------------------------------------------------------------- /img/PigletQL Structure - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/PigletQL Structure - eng.png -------------------------------------------------------------------------------- /img/PigletQL Structure - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Query stringQuery stringQuery representationQuery representationPhysical algebra operator treePhysical algebra operator treeLexical and syntax analysisLexical and syntax analysisSemantic analysisSemantic analysisOperator tree executionOperator tree executionDatabase catalogueDatabase catalogue -------------------------------------------------------------------------------- /img/PigletQL Structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/PigletQL Structure.jpg -------------------------------------------------------------------------------- /img/PigletQL Structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/PigletQL Structure.png -------------------------------------------------------------------------------- /img/PigletQL Structure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Строка запросаСтрока запросаПредставление запросаПредставление запросаДерево операторов физической алгебрыДерево операторов физической алгебрыЛексический и синтаксический анализЛексический и синтаксический анализСемантический анализСемантический анализИсполнение дерева операторовИсполнение дерева операторовКаталог базы данныхКаталог базы данных -------------------------------------------------------------------------------- /img/PigletQL Tuple Path - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/PigletQL Tuple Path - eng.png -------------------------------------------------------------------------------- /img/PigletQL Tuple Path.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/PigletQL Tuple Path.jpg -------------------------------------------------------------------------------- /img/PigletQL Tuple Path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/PigletQL Tuple Path.png -------------------------------------------------------------------------------- /img/Poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Poster.jpg -------------------------------------------------------------------------------- /img/Project Example - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Project Example - eng.png -------------------------------------------------------------------------------- /img/Project Example - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanProject (a1)Project (a1)Relation rel1 (a1,a2,a3)Relation rel1 (a1,a2,a3)Data outputData output -------------------------------------------------------------------------------- /img/Project Example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Project Example.jpg -------------------------------------------------------------------------------- /img/Project Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Project Example.png -------------------------------------------------------------------------------- /img/Project Example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanProject (a1)Project (a1)Отношение rel1 (a1,a2,a3)Отношение rel1 (a1,a2,a3)Вывод данныхВывод данных -------------------------------------------------------------------------------- /img/Select Example - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Select Example - eng.png -------------------------------------------------------------------------------- /img/Select Example - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanProject (a1)Project (a1)Select (a1 > 3)Select (a1 > 3)Relation rel1 (a1,a2,a3)Relation rel1 (a1,a2,a3)Data outputData output -------------------------------------------------------------------------------- /img/Select Example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Select Example.jpg -------------------------------------------------------------------------------- /img/Select Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Select Example.png -------------------------------------------------------------------------------- /img/Select Example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanProject (a1)Project (a1)Select (a1 > 3)Select (a1 > 3)Отношение rel1 (a1,a2,a3)Отношение rel1 (a1,a2,a3)Вывод данныхВывод данных -------------------------------------------------------------------------------- /img/Sort Example - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Sort Example - eng.png -------------------------------------------------------------------------------- /img/Sort Example - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanProject (a1)Project (a1)Sort (a1 desc)Sort (a1 desc)Relation rel1 (a1,a2,a3)Relation rel1 (a1,a2,a3)Data outputData output -------------------------------------------------------------------------------- /img/Sort Example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Sort Example.jpg -------------------------------------------------------------------------------- /img/Sort Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Sort Example.png -------------------------------------------------------------------------------- /img/Sort Example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanScanProject (a1)Project (a1)Sort (a1 desc)Sort (a1 desc)Отношение rel1 (a1,a2,a3)Отношение rel1 (a1,a2,a3)Вывод данныхВывод данных -------------------------------------------------------------------------------- /img/Volcano Model - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Volcano Model - eng.png -------------------------------------------------------------------------------- /img/Volcano Model - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Scan operatorScan operatorScan operatorScan operatorScan operatorScan operatorJoin operatorJoin operatorJoin operatorJoin operatorProject operatorProject operatorSelect operatorSelect operatorRelationRelationRelationRelationRelationRelationTuplesTuplesData outputData output -------------------------------------------------------------------------------- /img/Volcano Model.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Volcano Model.jpg -------------------------------------------------------------------------------- /img/Volcano Model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Volcano Model.png -------------------------------------------------------------------------------- /img/Volcano Model.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Оператор ScanОператор ScanОператор ScanОператор ScanОператор ScanОператор ScanОператор JoinОператор JoinОператор JoinОператор JoinОператор ProjectОператор ProjectОператор SelectОператор SelectОтношениеОтношениеОтношениеОтношениеОтношениеОтношениеКортежиКортежиВывод данныхВывод данных -------------------------------------------------------------------------------- /img/Volcano Operator - eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Volcano Operator - eng.png -------------------------------------------------------------------------------- /img/Volcano Operator - eng.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Volcano operatoropenclosenextstate[Not supported by viewer]ОператорОператорОператорОператорOperatorOperator -------------------------------------------------------------------------------- /img/Volcano Operator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Volcano Operator.jpg -------------------------------------------------------------------------------- /img/Volcano Operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vkazanov/sql-interpreters-post/687173f1b6ea8b51cd9129f624b3e19699b84c67/img/Volcano Operator.png -------------------------------------------------------------------------------- /img/Volcano Operator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Оператор Volcanoуказатель openуказатель closeуказатель nextуказатель state[Not supported by viewer]ОператорОператорОператорОператорОператорОператор -------------------------------------------------------------------------------- /pigletql-catalogue-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "pigletql-def.h" 5 | #include "pigletql-catalogue.h" 6 | #include "pigletql-eval.h" 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | (void) argc; (void) argv; 11 | 12 | /* Check an empty catalogue and adding relations to it*/ 13 | { 14 | catalogue_t *cat = catalogue_create(); 15 | assert(cat); 16 | 17 | assert(NULL == catalogue_get_relation(cat, "rel1")); 18 | 19 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 20 | const size_t attr_num = ARRAY_SIZE(attr_names); 21 | 22 | relation_t *rel1 = relation_create(attr_names, attr_num); 23 | catalogue_add_relation(cat, "rel1", rel1); 24 | 25 | rel1 = catalogue_get_relation(cat, "rel1"); 26 | assert(rel1); 27 | assert(relation_get_attr_num(rel1) == attr_num); 28 | assert(relation_has_attr(rel1, "id")); 29 | assert(relation_has_attr(rel1, "attr1")); 30 | assert(relation_has_attr(rel1, "attr2")); 31 | 32 | relation_t *rel2 = relation_create(attr_names, attr_num); 33 | catalogue_add_relation(cat, "rel2", rel2); 34 | assert(catalogue_get_relation(cat, "rel2")); 35 | 36 | assert(!catalogue_get_relation(cat, "no rel")); 37 | 38 | catalogue_destroy(cat); 39 | } 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /pigletql-catalogue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "pigletql-catalogue.h" 5 | 6 | typedef struct record_t { 7 | rel_name_t name; 8 | relation_t *relation; 9 | struct record_t *next; 10 | } record_t; 11 | 12 | typedef struct catalogue_t { 13 | record_t *record_list; 14 | } catalogue_t; 15 | 16 | catalogue_t *catalogue_create(void) 17 | { 18 | catalogue_t *cat = calloc(1, sizeof(*cat)); 19 | if (!cat) 20 | return NULL; 21 | 22 | return cat; 23 | } 24 | 25 | void catalogue_destroy(catalogue_t *cat) 26 | { 27 | for (record_t **this = &cat->record_list; *this; this = &(*this)->next) { 28 | relation_destroy((*this)->relation); 29 | free(*this); 30 | } 31 | free(cat); 32 | } 33 | 34 | relation_t *catalogue_get_relation(catalogue_t *cat, const rel_name_t rel_name) 35 | { 36 | for (record_t **this = &cat->record_list; *this; this = &(*this)->next) 37 | if (0 == strncmp((*this)->name, rel_name, MAX_REL_NAME_LEN)) 38 | return (*this)->relation; 39 | return NULL; 40 | } 41 | 42 | relation_t *catalogue_add_relation(catalogue_t *cat, const rel_name_t rel_name, relation_t *rel) 43 | { 44 | record_t *record = calloc(1, sizeof(*record)); 45 | if (!record) 46 | return NULL; 47 | strncpy(record->name, rel_name, MAX_REL_NAME_LEN); 48 | record->relation = rel; 49 | 50 | record_t **this = &cat->record_list; 51 | for (; *this; this = &(*this)->next); 52 | *this = record; 53 | 54 | return rel; 55 | } 56 | -------------------------------------------------------------------------------- /pigletql-catalogue.h: -------------------------------------------------------------------------------- 1 | #ifndef PIGLETQL_CATALOGUE_H 2 | #define PIGLETQL_CATALOGUE_H 3 | 4 | #include 5 | 6 | #include "pigletql-def.h" 7 | #include "pigletql-eval.h" 8 | 9 | typedef struct catalogue_t catalogue_t; 10 | 11 | catalogue_t *catalogue_create(void); 12 | 13 | void catalogue_destroy(catalogue_t *catalogue); 14 | 15 | relation_t *catalogue_get_relation(catalogue_t *catalogue, const rel_name_t rel_name); 16 | 17 | relation_t *catalogue_add_relation(catalogue_t *catalogue, const rel_name_t rel_name, relation_t *rel); 18 | 19 | #endif //PIGLETQL_CATALOGUE_H 20 | -------------------------------------------------------------------------------- /pigletql-def.h: -------------------------------------------------------------------------------- 1 | #ifndef PIGLETQL_DEF_H 2 | #define PIGLETQL_DEF_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | * Common definitions 9 | * */ 10 | 11 | /* maximum number of attributes per relation */ 12 | #define MAX_ATTR_NUM (UINT16_MAX - 1) 13 | /* return value telling that an attribute was not found */ 14 | #define ATTR_NOT_FOUND UINT16_MAX 15 | /* maximum attribute name length */ 16 | #define MAX_ATTR_NAME_LEN 256 17 | 18 | /* maximum number of relations */ 19 | #define MAX_REL_NUM UINT16_MAX 20 | /* maximum relation name length */ 21 | #define MAX_REL_NAME_LEN 256 22 | 23 | /* maximum number of predicates per query */ 24 | #define MAX_PRED_NUM UINT16_MAX 25 | 26 | typedef enum sort_order_t { 27 | SORT_ASC = 0, 28 | SORT_DESC, 29 | } sort_order_t; 30 | 31 | #define PRI_VALUE PRIu32 32 | #define SCN_VALUE SCNu32 33 | typedef uint32_t value_type_t; /* a single value type supported */ 34 | 35 | typedef char attr_name_t[MAX_ATTR_NAME_LEN]; /* attribute names are fixed-size strings */ 36 | typedef char rel_name_t[MAX_REL_NAME_LEN]; /* relation names are fixed-size strings */ 37 | 38 | /* just a helper macro */ 39 | #define ARRAY_SIZE(arr) sizeof(arr) / sizeof((arr)[0]) 40 | 41 | #endif //PIGLETQL_DEF_H 42 | -------------------------------------------------------------------------------- /pigletql-eval.h: -------------------------------------------------------------------------------- 1 | #ifndef PIGLETQL_EVAL_H 2 | #define PIGLETQL_EVAL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "pigletql-def.h" 9 | 10 | /* 11 | * A tuple is a reference to a real tuple stored in a relation 12 | * */ 13 | 14 | typedef struct tuple_t tuple_t; 15 | 16 | bool tuple_has_attr(const tuple_t *tuple, const attr_name_t attr_name); 17 | 18 | value_type_t tuple_get_attr_value(const tuple_t *tuple, const attr_name_t attr_name); 19 | 20 | uint16_t tuple_get_attr_num(const tuple_t *tuple); 21 | 22 | value_type_t tuple_get_attr_value_by_i(const tuple_t *tuple, const uint16_t attr_i); 23 | 24 | const char *tuple_get_attr_name_by_i(const tuple_t *tuple, const uint16_t attr_i); 25 | 26 | /* 27 | * Relation is an in-memory table containing raw tuple data 28 | * */ 29 | 30 | typedef struct relation_t relation_t; 31 | 32 | relation_t *relation_create(const attr_name_t *attr_names, const uint16_t attr_num); 33 | 34 | relation_t *relation_create_for_tuple(const tuple_t *tuple); 35 | 36 | void relation_fill_from_table(relation_t *relation, 37 | const value_type_t *table, 38 | const uint32_t table_tuple_num); 39 | 40 | void relation_order_by(relation_t *rel, const attr_name_t sort_attr_name, const sort_order_t order); 41 | 42 | value_type_t *relation_tuple_values_by_id(const relation_t *rel, const uint32_t tuple_i); 43 | 44 | uint16_t relation_attr_i_by_name(const relation_t *rel, const attr_name_t attr_name); 45 | 46 | const char *relation_attr_name_by_i(const relation_t *rel, const uint16_t attr_i); 47 | 48 | bool relation_has_attr(const relation_t *rel, const attr_name_t attr_name); 49 | 50 | uint16_t relation_get_attr_num(const relation_t *rel); 51 | 52 | uint32_t relation_get_tuple_num(const relation_t *rel); 53 | 54 | void relation_append_tuple(relation_t *rel, const tuple_t *tuple); 55 | 56 | void relation_append_values(relation_t *rel, const value_type_t *values); 57 | 58 | void relation_reset(relation_t *relation); 59 | 60 | void relation_destroy(relation_t *relation); 61 | 62 | /* 63 | * Operators iterate over relation tuples or tuples returned from other operators using 3 standard 64 | * ops: open, next, close. 65 | * 66 | * open - starts iteration over available tuples from the very beginning 67 | * 68 | * next - retrieves the next tuple, ending with a NULL value 69 | * 70 | * close - closes the operator and resets its state 71 | * 72 | * destroy - deallocates all the memory required by an operator and it's child operators 73 | * */ 74 | 75 | typedef struct operator_t operator_t; 76 | 77 | typedef void (*op_open)(void *state); 78 | typedef tuple_t *(*op_next)(void *state); 79 | typedef void (*op_close)(void *state); 80 | typedef void (*op_destroy)(operator_t *state); 81 | 82 | /* The operator itself is just 4 pointers to related ops and operator state */ 83 | struct operator_t { 84 | op_open open; 85 | op_next next; 86 | op_close close; 87 | op_destroy destroy; 88 | 89 | void *state; 90 | } ; 91 | 92 | /* 93 | * Table scan operator just goes over all tuples in a relation. 94 | * */ 95 | 96 | operator_t *scan_op_create(const relation_t *relation); 97 | 98 | /* 99 | * Projection operator chooses a subset of attributes. 100 | * */ 101 | 102 | operator_t *proj_op_create(operator_t *source, 103 | const attr_name_t *tuple_attr_names, 104 | const uint16_t tuple_attr_num); 105 | 106 | /* 107 | * Union operator gets tuples from both supplied relations with the same attributes. 108 | * */ 109 | 110 | operator_t *union_op_create(operator_t *left_source, 111 | operator_t *right_source); 112 | 113 | /* 114 | * Join operator does a cross join of two relations, i.e. it returns all possible combinations of 115 | * tuples from both relations, with attributes joined. 116 | * */ 117 | 118 | operator_t *join_op_create(operator_t *left_source, 119 | operator_t *right_source); 120 | 121 | 122 | /* 123 | * Selection operator filters tuples according to a list of predicates 124 | * */ 125 | 126 | typedef enum select_predicate_op { 127 | SELECT_GT, /* greater than */ 128 | SELECT_LT, /* less than */ 129 | SELECT_EQ /* equal */ 130 | } select_predicate_op; 131 | 132 | void select_op_add_attr_const_predicate(operator_t *operator, 133 | const attr_name_t left_attr_name, 134 | const select_predicate_op predicate_op, 135 | const value_type_t right_constant); 136 | 137 | void select_op_add_attr_attr_predicate(operator_t *operator, 138 | const attr_name_t left_attr_name, 139 | const select_predicate_op predicate_op, 140 | const attr_name_t right_attr_name); 141 | 142 | operator_t *select_op_create(operator_t *source); 143 | 144 | /* 145 | * Sort operator sorts tuples by a given attribute in ascending or descending order 146 | * */ 147 | 148 | operator_t *sort_op_create(operator_t *source, 149 | const attr_name_t sort_attr_name, 150 | const sort_order_t order); 151 | 152 | #endif //PIGLETQL_EVAL_H 153 | -------------------------------------------------------------------------------- /pigletql-parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "pigletql-parser.h" 9 | 10 | typedef struct scanner_t { 11 | const char *input; 12 | const char *token_start; 13 | } scanner_t; 14 | 15 | typedef struct parser_t { 16 | scanner_t *scanner; 17 | query_t *query; 18 | 19 | token_t current; 20 | token_t previous; 21 | 22 | bool had_error; 23 | } parser_t; 24 | 25 | scanner_t *scanner_create(const char *string) 26 | { 27 | scanner_t *scanner = calloc(1, sizeof(*scanner)); 28 | assert(scanner); 29 | 30 | scanner->input = string; 31 | 32 | return scanner; 33 | } 34 | 35 | void scanner_destroy(scanner_t *scanner) 36 | { 37 | if (scanner) 38 | free(scanner); 39 | } 40 | 41 | static bool char_is_alpha(char c) 42 | { 43 | return (c >= 'a' && c <= 'z') 44 | || (c >= 'A' && c <= 'Z') 45 | || c == '_'; 46 | } 47 | 48 | static bool char_is_digit(char c) 49 | { 50 | return (c >= '0' && c <= '9'); 51 | } 52 | 53 | static bool scanner_at_eos(scanner_t *scanner) 54 | { 55 | return *scanner->input == '\0'; 56 | } 57 | 58 | static char scanner_peek(scanner_t *scanner) 59 | { 60 | return tolower(*scanner->input); 61 | } 62 | 63 | static char scanner_peek_start(scanner_t *scanner) 64 | { 65 | return tolower(*scanner->token_start); 66 | } 67 | 68 | static char scanner_advance(scanner_t *scanner) 69 | { 70 | return *scanner->input++; 71 | } 72 | 73 | static void scanner_skip_space(scanner_t *scanner) 74 | { 75 | for (;;) { 76 | char c = scanner_peek(scanner); 77 | switch (c) { 78 | case ' ': 79 | case '\r': 80 | case '\n': 81 | case '\t': 82 | scanner_advance(scanner); 83 | break; 84 | default: 85 | return; 86 | } 87 | } 88 | } 89 | 90 | static token_t scanner_token_create(scanner_t *scanner, token_type type) 91 | { 92 | token_t token; 93 | token.type = type; 94 | token.start = scanner->token_start; 95 | token.length = scanner->input - scanner->token_start; 96 | return token; 97 | } 98 | 99 | static token_t scanner_token_error_create(const char *error_msg) 100 | { 101 | token_t token; 102 | token.type = TOKEN_ERROR; 103 | token.start = error_msg; 104 | token.length = strlen(error_msg); 105 | return token; 106 | } 107 | 108 | static token_type scan_keyword(scanner_t *scanner, int start, int length, const char* suffix, token_type type) 109 | { 110 | if (scanner->input - scanner->token_start != start + length) 111 | return TOKEN_IDENT; 112 | 113 | for (int i = start; i < start + length; i++) { 114 | if (tolower(scanner->token_start[i]) != suffix[i - start]) 115 | return TOKEN_IDENT; 116 | } 117 | 118 | return type; 119 | } 120 | 121 | static token_type scan_ident_type(scanner_t *scanner) 122 | { 123 | switch(scanner_peek_start(scanner)) { 124 | case 's': return scan_keyword(scanner, 1, 5, "elect", TOKEN_SELECT); 125 | case 'f': return scan_keyword(scanner, 1, 3, "rom", TOKEN_FROM); 126 | case 'w': return scan_keyword(scanner, 1, 4, "here", TOKEN_WHERE); 127 | case 'a': { 128 | /* either AND or ASC here */ 129 | token_type t = scan_keyword(scanner, 1, 2, "nd", TOKEN_AND); 130 | if (t != TOKEN_IDENT) 131 | return t; 132 | 133 | return scan_keyword(scanner, 1, 2, "sc", TOKEN_ASC);; 134 | } 135 | case 'o': return scan_keyword(scanner, 1, 4, "rder", TOKEN_ORDER); 136 | case 'b': return scan_keyword(scanner, 1, 1, "y", TOKEN_BY); 137 | case 'd': return scan_keyword(scanner, 1, 3, "esc", TOKEN_DESC); 138 | case 'c': return scan_keyword(scanner, 1, 5, "reate", TOKEN_CREATE); 139 | case 't': return scan_keyword(scanner, 1, 4, "able", TOKEN_TABLE); 140 | case 'i': { 141 | /* either INTO or INSERT */ 142 | token_type t = scan_keyword(scanner, 1, 5, "nsert", TOKEN_INSERT); 143 | if (t != TOKEN_IDENT) 144 | return t; 145 | 146 | return scan_keyword(scanner, 1, 3, "nto", TOKEN_INTO);; 147 | } 148 | case 'v': return scan_keyword(scanner, 1, 5, "alues", TOKEN_VALUES); 149 | } 150 | return TOKEN_IDENT; 151 | } 152 | 153 | static token_t scanner_ident(scanner_t *scanner) 154 | { 155 | while (char_is_digit(scanner_peek(scanner)) || char_is_alpha(scanner_peek(scanner))) 156 | scanner_advance(scanner); 157 | 158 | return scanner_token_create(scanner, scan_ident_type(scanner)); 159 | } 160 | 161 | static token_t scanner_number(scanner_t *scanner) 162 | { 163 | while (char_is_digit(scanner_peek(scanner))) 164 | scanner_advance(scanner); 165 | 166 | return scanner_token_create(scanner, TOKEN_NUMBER); 167 | } 168 | 169 | token_t scanner_next(scanner_t *scanner) 170 | { 171 | scanner_skip_space(scanner); 172 | 173 | scanner->token_start = scanner->input; 174 | 175 | if (scanner_at_eos(scanner)) 176 | return scanner_token_create(scanner, TOKEN_EOS); 177 | 178 | char c = scanner_advance(scanner); 179 | 180 | if (char_is_alpha(c)) 181 | return scanner_ident(scanner); 182 | 183 | if (char_is_digit(c)) 184 | return scanner_number(scanner); 185 | 186 | switch (c) { 187 | case ';': return scanner_token_create(scanner, TOKEN_SEMICOLON); 188 | case ',': return scanner_token_create(scanner, TOKEN_COMMA); 189 | case '*': return scanner_token_create(scanner, TOKEN_STAR); 190 | case '=': return scanner_token_create(scanner, TOKEN_EQUAL); 191 | case '<': return scanner_token_create(scanner, TOKEN_LESS); 192 | case '>': return scanner_token_create(scanner, TOKEN_GREATER); 193 | case '(': return scanner_token_create(scanner, TOKEN_LPAREN); 194 | case ')': return scanner_token_create(scanner, TOKEN_RPAREN); 195 | } 196 | 197 | return scanner_token_error_create("Unknown character"); 198 | } 199 | 200 | 201 | query_t *query_create(void) 202 | { 203 | query_t *query = calloc(1, sizeof(*query)); 204 | if (!query) 205 | return NULL; 206 | return query; 207 | } 208 | 209 | void query_destroy(query_t *query) 210 | { 211 | if (query) 212 | free(query); 213 | } 214 | 215 | static void query_select_add_attr(query_t *query, token_t token) 216 | { 217 | strncpy(query->as.select.attr_names[query->as.select.attr_num], token.start, (size_t)token.length); 218 | query->as.select.attr_num++; 219 | } 220 | 221 | static void query_create_table_add_attr(query_t *query, token_t token) 222 | { 223 | strncpy(query->as.create_table.attr_names[query->as.create_table.attr_num], token.start, (size_t)token.length); 224 | query->as.create_table.attr_num++; 225 | } 226 | 227 | static void query_insert_add_value(query_t *query, token_t token) 228 | { 229 | query->as.insert.values[query->as.insert.value_num] = (value_type_t)atoi(token.start); 230 | query->as.insert.value_num++; 231 | } 232 | 233 | static void query_select_add_rel(query_t *query, token_t token) 234 | { 235 | strncpy(query->as.select.rel_names[query->as.select.rel_num], token.start, (size_t)token.length); 236 | query->as.select.rel_num++; 237 | } 238 | 239 | static void query_create_table_add_rel(query_t *query, token_t token) 240 | { 241 | strncpy(query->as.create_table.rel_name, token.start, (size_t)token.length); 242 | } 243 | 244 | static void query_insert_add_rel(query_t *query, token_t token) 245 | { 246 | strncpy(query->as.insert.rel_name, token.start, (size_t)token.length); 247 | } 248 | 249 | static void query_select_add_pred(query_t *query, token_t left_operand, token_t operator, token_t right_operand) 250 | { 251 | query->as.select.predicates[query->as.select.pred_num].left = left_operand; 252 | query->as.select.predicates[query->as.select.pred_num].op = operator; 253 | query->as.select.predicates[query->as.select.pred_num].right = right_operand; 254 | query->as.select.pred_num++; 255 | } 256 | 257 | static void query_select_add_order_by_attr(query_t *query, token_t token) 258 | { 259 | query->as.select.has_order = true; 260 | strncpy(query->as.select.order_by_attr, token.start, (size_t)token.length); 261 | } 262 | 263 | static void query_select_add_sort_order(query_t *query, token_t token) 264 | { 265 | if (token.type == TOKEN_ASC) { 266 | query->as.select.order_type = SORT_ASC; 267 | } else { 268 | query->as.select.order_type = SORT_DESC; 269 | } 270 | } 271 | 272 | parser_t *parser_create(void) 273 | { 274 | parser_t *parser = calloc(1, sizeof(*parser)); 275 | if (!parser) 276 | return NULL; 277 | return parser; 278 | 279 | } 280 | 281 | void parser_destroy(parser_t *parser) 282 | { 283 | if (parser) 284 | free(parser); 285 | } 286 | 287 | static void parser_error_at(parser_t *parser, token_t token, const char *msg) 288 | { 289 | if (parser->had_error) 290 | return; 291 | 292 | fprintf(stderr, "Error"); 293 | 294 | if (token.type == TOKEN_EOS) { 295 | fprintf(stderr, " at end"); 296 | } else if (token.type == TOKEN_ERROR) { 297 | /* Just skip */ 298 | } else { 299 | fprintf(stderr, " parsing '%.*s'", token.length, token.start); 300 | } 301 | 302 | fprintf(stderr, ": %s\n", msg); 303 | 304 | parser->had_error = true; 305 | } 306 | 307 | static void parser_error(parser_t *parser, const char* msg) { 308 | parser_error_at(parser, parser->previous, msg); 309 | } 310 | 311 | static void parser_error_at_current(parser_t *parser, const char *msg) 312 | { 313 | parser_error_at(parser, parser->current, msg); 314 | } 315 | 316 | static void parser_advance(parser_t *parser) 317 | { 318 | parser->previous = parser->current; 319 | for (;;) { 320 | parser->current = scanner_next(parser->scanner); 321 | if (parser->current.type != TOKEN_ERROR) 322 | break; 323 | 324 | parser_error_at_current(parser, parser->current.start); 325 | } 326 | } 327 | 328 | static void parser_consume(parser_t *parser, token_type type, const char *msg) 329 | { 330 | if (parser->current.type == type) { 331 | parser_advance(parser); 332 | return; 333 | } 334 | 335 | parser_error_at_current(parser, msg); 336 | } 337 | 338 | static bool parser_check(parser_t *parser, token_type type) { 339 | return parser->current.type == type; 340 | } 341 | 342 | static bool parser_match(parser_t *parser, token_type type) 343 | { 344 | if (!parser_check(parser, type)) 345 | return false; 346 | parser_advance(parser); 347 | return true; 348 | } 349 | 350 | static void parse_predicate(parser_t *parser) 351 | { 352 | parser_consume(parser, TOKEN_IDENT, "Left predicate identifier expected"); 353 | token_t left = parser->previous; 354 | 355 | if (!parser_match(parser, TOKEN_EQUAL) && 356 | !parser_match(parser, TOKEN_LESS) && 357 | !parser_match(parser, TOKEN_GREATER)) { 358 | parser_error(parser, "Predicate operator expected"); 359 | return; 360 | } 361 | token_t op = parser->previous; 362 | 363 | if (!parser_match(parser, TOKEN_IDENT) && 364 | !parser_match(parser, TOKEN_NUMBER)) { 365 | parser_error(parser, "Right predicate identifier or number expected"); 366 | return; 367 | } 368 | token_t right = parser->previous; 369 | 370 | query_select_add_pred(parser->query, left, op, right); 371 | } 372 | 373 | static void parse_order(parser_t *parser) 374 | { 375 | parser_consume(parser, TOKEN_BY, "ORDER should always be followed by BY"); 376 | 377 | parser_consume(parser, TOKEN_IDENT, "Attribute name expected"); 378 | query_select_add_order_by_attr(parser->query, parser->previous); 379 | 380 | if (parser_match(parser, TOKEN_ASC) || parser_match(parser, TOKEN_DESC)) 381 | query_select_add_sort_order(parser->query, parser->previous); 382 | } 383 | 384 | static void parse_select(parser_t *parser) 385 | { 386 | /* Collect attribute names */ 387 | do { 388 | parser_consume(parser, TOKEN_IDENT, "Attribute name expected"); 389 | query_select_add_attr(parser->query, parser->previous); 390 | } while (parser_match(parser, TOKEN_COMMA)); 391 | 392 | /* Collect relation names */ 393 | parser_consume(parser, TOKEN_FROM, "FROM expected"); 394 | 395 | do { 396 | parser_consume(parser, TOKEN_IDENT, "Relation name expected"); 397 | query_select_add_rel(parser->query, parser->previous); 398 | } while (parser_match(parser, TOKEN_COMMA)); 399 | 400 | /* Collect filtering predicates */ 401 | if (parser_match(parser, TOKEN_WHERE)) { 402 | do { 403 | parse_predicate(parser); 404 | } while (parser_match(parser, TOKEN_AND)); 405 | } 406 | 407 | /* Order by */ 408 | if (parser_match(parser, TOKEN_ORDER)) 409 | parse_order(parser); 410 | } 411 | 412 | static void parser_create_table(parser_t *parser) 413 | { 414 | /* Relation name */ 415 | parser_consume(parser, TOKEN_IDENT, "Relation name expected"); 416 | query_create_table_add_rel(parser->query, parser->previous); 417 | 418 | /* Attribute list */ 419 | parser_consume(parser, TOKEN_LPAREN, "LPAREN expected"); 420 | do { 421 | parser_consume(parser, TOKEN_IDENT, "Attribute name expected"); 422 | query_create_table_add_attr(parser->query, parser->previous); 423 | } while (parser_match(parser, TOKEN_COMMA)); 424 | parser_consume(parser, TOKEN_RPAREN, "RPAREN expected"); 425 | } 426 | 427 | static void parser_insert(parser_t *parser) 428 | { 429 | /* Relation name */ 430 | parser_consume(parser, TOKEN_IDENT, "Relation name expected"); 431 | query_insert_add_rel(parser->query, parser->previous); 432 | 433 | /* Value list */ 434 | parser_consume(parser, TOKEN_VALUES, "VALUES expected"); 435 | 436 | parser_consume(parser, TOKEN_LPAREN, "LPAREN expected"); 437 | do { 438 | parser_consume(parser, TOKEN_NUMBER, "An integer value expected"); 439 | query_insert_add_value(parser->query, parser->previous); 440 | } while (parser_match(parser, TOKEN_COMMA)); 441 | parser_consume(parser, TOKEN_RPAREN, "RPAREN expected"); 442 | } 443 | 444 | static void parse_query(parser_t *parser) 445 | { 446 | if (parser_match(parser, TOKEN_SELECT)) { 447 | parser->query->tag = QUERY_SELECT; 448 | parse_select(parser); 449 | } else if (parser_match(parser, TOKEN_CREATE)) { 450 | parser_consume(parser, TOKEN_TABLE, "TABLE expected"); 451 | parser->query->tag = QUERY_CREATE_TABLE; 452 | parser_create_table(parser); 453 | } else if (parser_match(parser, TOKEN_INSERT)) { 454 | parser_consume(parser, TOKEN_INTO, "INTO expected"); 455 | parser->query->tag = QUERY_INSERT; 456 | parser_insert(parser); 457 | } else 458 | parser_error(parser, "Query type unsupported"); 459 | 460 | parser_consume(parser, TOKEN_SEMICOLON, "Queries should end with a semicolon"); 461 | parser_consume(parser, TOKEN_EOS, "Only single line queries are supported"); 462 | } 463 | 464 | bool parser_parse(parser_t *parser, scanner_t *scanner, query_t *query) 465 | { 466 | parser->scanner = scanner; 467 | parser->query = query; 468 | 469 | parser->had_error = false; 470 | 471 | parser_advance(parser); 472 | while (!parser_match(parser, TOKEN_EOS)) { 473 | parse_query(parser); 474 | if (parser->had_error) 475 | break; 476 | } 477 | 478 | return !parser->had_error; 479 | } 480 | -------------------------------------------------------------------------------- /pigletql-parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PIGLETQL_PARSER_H 2 | #define PIGLETQL_PARSER_H 3 | 4 | #include 5 | 6 | #include "pigletql-def.h" 7 | 8 | typedef enum token_type { 9 | TOKEN_IDENT, 10 | TOKEN_NUMBER, 11 | 12 | TOKEN_STAR, 13 | TOKEN_COMMA, 14 | TOKEN_SEMICOLON, 15 | 16 | TOKEN_LPAREN, 17 | TOKEN_RPAREN, 18 | 19 | TOKEN_EQUAL, 20 | TOKEN_LESS, 21 | TOKEN_GREATER, 22 | 23 | TOKEN_SELECT, 24 | TOKEN_CREATE, 25 | TOKEN_TABLE, 26 | TOKEN_INSERT, 27 | 28 | TOKEN_FROM, 29 | TOKEN_WHERE, 30 | TOKEN_AND, 31 | 32 | TOKEN_ORDER, 33 | TOKEN_BY, 34 | TOKEN_ASC, 35 | TOKEN_DESC, 36 | 37 | TOKEN_INTO, 38 | TOKEN_VALUES, 39 | 40 | TOKEN_ERROR, /* failed to scan */ 41 | TOKEN_EOS /* end of stream */ 42 | } token_type; 43 | 44 | typedef struct token_t { 45 | token_type type; /* token type tag */ 46 | const char *start; /* start of the token */ 47 | int length; /* length of the token string */ 48 | } token_t; 49 | 50 | typedef struct query_predicate_t { 51 | token_t left; 52 | token_t op; 53 | token_t right; 54 | } query_predicate_t; 55 | 56 | typedef enum query_tag { 57 | QUERY_SELECT, 58 | QUERY_CREATE_TABLE, 59 | QUERY_INSERT, 60 | } query_tag; 61 | 62 | typedef struct query_select_t { 63 | /* Attributes to output */ 64 | attr_name_t attr_names[MAX_ATTR_NUM]; 65 | uint16_t attr_num; 66 | 67 | /* Relations to get tuples from */ 68 | rel_name_t rel_names[MAX_REL_NUM]; 69 | uint16_t rel_num; 70 | 71 | /* Predicates to apply to tuples */ 72 | query_predicate_t predicates[MAX_PRED_NUM]; 73 | uint16_t pred_num; 74 | 75 | /* Pick an attribute to sort by */ 76 | bool has_order; 77 | attr_name_t order_by_attr; 78 | sort_order_t order_type; 79 | } query_select_t; 80 | 81 | typedef struct query_create_table_t { 82 | rel_name_t rel_name; 83 | 84 | attr_name_t attr_names[MAX_ATTR_NUM]; 85 | uint16_t attr_num; 86 | } query_create_table_t; 87 | 88 | typedef struct query_insert_t { 89 | rel_name_t rel_name; 90 | 91 | value_type_t values[MAX_ATTR_NUM]; 92 | uint16_t value_num; 93 | } query_insert_t; 94 | 95 | typedef struct query_t { 96 | query_tag tag; 97 | union { 98 | query_select_t select; 99 | query_create_table_t create_table; 100 | query_insert_t insert; 101 | } as; 102 | } query_t; 103 | 104 | typedef struct parser_t parser_t; 105 | 106 | typedef struct scanner_t scanner_t; 107 | 108 | scanner_t *scanner_create(const char *string); 109 | 110 | void scanner_destroy(scanner_t *scanner); 111 | 112 | token_t scanner_next(scanner_t *scanner); 113 | 114 | query_t *query_create(void); 115 | 116 | void query_destroy(query_t *query); 117 | 118 | parser_t *parser_create(void); 119 | 120 | void parser_destroy(parser_t *parser); 121 | 122 | bool parser_parse(parser_t *parser, scanner_t *scanner, query_t *query); 123 | 124 | #endif //PIGLETQL_PARSER_H 125 | -------------------------------------------------------------------------------- /pigletql-validate-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "pigletql-validate.h" 5 | 6 | static void create_validate_test(void) 7 | { 8 | /* Correctly create a table */ 9 | { 10 | const char *query_str = "CREATE TABLE rel1 (a1, a2);"; 11 | 12 | catalogue_t *cat = catalogue_create(); 13 | scanner_t *scanner = scanner_create(query_str); 14 | parser_t *parser = parser_create(); 15 | query_t *query = query_create(); 16 | assert(cat); 17 | assert(scanner); 18 | assert(parser); 19 | assert(query); 20 | assert(parser_parse(parser, scanner, query)); 21 | 22 | assert(validate(cat, query)); 23 | 24 | query_destroy(query); 25 | parser_destroy(parser); 26 | scanner_destroy(scanner); 27 | catalogue_destroy(cat); 28 | } 29 | 30 | /* Create a table with non-unique attributes */ 31 | { 32 | const char *query_str = "CREATE TABLE rel1 (a1, a1);"; 33 | 34 | catalogue_t *cat = catalogue_create(); 35 | scanner_t *scanner = scanner_create(query_str); 36 | parser_t *parser = parser_create(); 37 | query_t *query = query_create(); 38 | assert(cat); 39 | assert(scanner); 40 | assert(parser); 41 | assert(query); 42 | assert(parser_parse(parser, scanner, query)); 43 | 44 | assert(!validate(cat, query)); 45 | 46 | query_destroy(query); 47 | parser_destroy(parser); 48 | scanner_destroy(scanner); 49 | catalogue_destroy(cat); 50 | } 51 | 52 | /* A table that already exists */ 53 | { 54 | const char *query_str = "CREATE TABLE rel1 (a1, a1);"; 55 | 56 | catalogue_t *cat = catalogue_create(); 57 | { 58 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 59 | const size_t attr_num = ARRAY_SIZE(attr_names); 60 | relation_t *rel1 = relation_create(attr_names, attr_num); 61 | catalogue_add_relation(cat, "rel1", rel1); 62 | } 63 | 64 | scanner_t *scanner = scanner_create(query_str); 65 | parser_t *parser = parser_create(); 66 | query_t *query = query_create(); 67 | assert(cat); 68 | assert(scanner); 69 | assert(parser); 70 | assert(query); 71 | assert(parser_parse(parser, scanner, query)); 72 | 73 | assert(!validate(cat, query)); 74 | 75 | query_destroy(query); 76 | parser_destroy(parser); 77 | scanner_destroy(scanner); 78 | catalogue_destroy(cat); 79 | } 80 | } 81 | 82 | static void insert_validate_test(void) 83 | { 84 | /* Insert into a non-existing table */ 85 | { 86 | const char *query_str = "INSERT INTO rel1 VALUES (1, 2);"; 87 | 88 | catalogue_t *cat = catalogue_create(); 89 | scanner_t *scanner = scanner_create(query_str); 90 | parser_t *parser = parser_create(); 91 | query_t *query = query_create(); 92 | assert(cat); 93 | assert(scanner); 94 | assert(parser); 95 | assert(query); 96 | assert(parser_parse(parser, scanner, query)); 97 | 98 | assert(!validate(cat, query)); 99 | 100 | query_destroy(query); 101 | parser_destroy(parser); 102 | scanner_destroy(scanner); 103 | catalogue_destroy(cat); 104 | } 105 | 106 | /* Correct insertion */ 107 | { 108 | const char *query_str = "INSERT INTO rel1 VALUES (1, 2, 3);"; 109 | 110 | catalogue_t *cat = catalogue_create(); 111 | { 112 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 113 | const size_t attr_num = ARRAY_SIZE(attr_names); 114 | relation_t *rel1 = relation_create(attr_names, attr_num); 115 | catalogue_add_relation(cat, "rel1", rel1); 116 | } 117 | 118 | scanner_t *scanner = scanner_create(query_str); 119 | parser_t *parser = parser_create(); 120 | query_t *query = query_create(); 121 | assert(cat); 122 | assert(scanner); 123 | assert(parser); 124 | assert(query); 125 | assert(parser_parse(parser, scanner, query)); 126 | 127 | assert(validate(cat, query)); 128 | 129 | query_destroy(query); 130 | parser_destroy(parser); 131 | scanner_destroy(scanner); 132 | catalogue_destroy(cat); 133 | } 134 | 135 | /* Insert into a table with a different number of columns */ 136 | { 137 | const char *query_str = "INSERT INTO rel1 VALUES (2, 3);"; 138 | 139 | catalogue_t *cat = catalogue_create(); 140 | { 141 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 142 | const size_t attr_num = ARRAY_SIZE(attr_names); 143 | relation_t *rel1 = relation_create(attr_names, attr_num); 144 | catalogue_add_relation(cat, "rel1", rel1); 145 | } 146 | 147 | scanner_t *scanner = scanner_create(query_str); 148 | parser_t *parser = parser_create(); 149 | query_t *query = query_create(); 150 | assert(cat); 151 | assert(scanner); 152 | assert(parser); 153 | assert(query); 154 | assert(parser_parse(parser, scanner, query)); 155 | 156 | assert(!validate(cat, query)); 157 | 158 | query_destroy(query); 159 | parser_destroy(parser); 160 | scanner_destroy(scanner); 161 | catalogue_destroy(cat); 162 | } 163 | 164 | } 165 | 166 | static void select_validate_test(void) 167 | { 168 | /* Select from a non-existing table */ 169 | { 170 | const char *query_str = "SELECT a1 FROM rel1;"; 171 | 172 | catalogue_t *cat = catalogue_create(); 173 | scanner_t *scanner = scanner_create(query_str); 174 | parser_t *parser = parser_create(); 175 | query_t *query = query_create(); 176 | assert(cat); 177 | assert(scanner); 178 | assert(parser); 179 | assert(query); 180 | assert(parser_parse(parser, scanner, query)); 181 | 182 | assert(!validate(cat, query)); 183 | 184 | query_destroy(query); 185 | parser_destroy(parser); 186 | scanner_destroy(scanner); 187 | catalogue_destroy(cat); 188 | } 189 | 190 | /* Select attributes not present in tables */ 191 | { 192 | const char *query_str = "SELECT n1 FROM rel1;"; 193 | 194 | catalogue_t *cat = catalogue_create(); 195 | { 196 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 197 | const size_t attr_num = ARRAY_SIZE(attr_names); 198 | relation_t *rel1 = relation_create(attr_names, attr_num); 199 | catalogue_add_relation(cat, "rel1", rel1); 200 | } 201 | scanner_t *scanner = scanner_create(query_str); 202 | parser_t *parser = parser_create(); 203 | query_t *query = query_create(); 204 | assert(cat); 205 | assert(scanner); 206 | assert(parser); 207 | assert(query); 208 | assert(parser_parse(parser, scanner, query)); 209 | 210 | assert(!validate(cat, query)); 211 | 212 | query_destroy(query); 213 | parser_destroy(parser); 214 | scanner_destroy(scanner); 215 | catalogue_destroy(cat); 216 | } 217 | 218 | /* Order by attributes not listed */ 219 | { 220 | const char *query_str = "SELECT attr1 FROM rel1 ORDER BY n1;"; 221 | 222 | catalogue_t *cat = catalogue_create(); 223 | { 224 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 225 | const size_t attr_num = ARRAY_SIZE(attr_names); 226 | relation_t *rel1 = relation_create(attr_names, attr_num); 227 | catalogue_add_relation(cat, "rel1", rel1); 228 | } 229 | scanner_t *scanner = scanner_create(query_str); 230 | parser_t *parser = parser_create(); 231 | query_t *query = query_create(); 232 | assert(cat); 233 | assert(scanner); 234 | assert(parser); 235 | assert(query); 236 | assert(parser_parse(parser, scanner, query)); 237 | 238 | assert(!validate(cat, query)); 239 | 240 | query_destroy(query); 241 | parser_destroy(parser); 242 | scanner_destroy(scanner); 243 | catalogue_destroy(cat); 244 | } 245 | 246 | /* Predicates should only use attributes listed */ 247 | { 248 | const char *query_str = "SELECT attr1 FROM rel1 WHERE n1=10;"; 249 | 250 | catalogue_t *cat = catalogue_create(); 251 | { 252 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 253 | const size_t attr_num = ARRAY_SIZE(attr_names); 254 | relation_t *rel1 = relation_create(attr_names, attr_num); 255 | catalogue_add_relation(cat, "rel1", rel1); 256 | } 257 | scanner_t *scanner = scanner_create(query_str); 258 | parser_t *parser = parser_create(); 259 | query_t *query = query_create(); 260 | assert(cat); 261 | assert(scanner); 262 | assert(parser); 263 | assert(query); 264 | assert(parser_parse(parser, scanner, query)); 265 | 266 | assert(!validate(cat, query)); 267 | 268 | query_destroy(query); 269 | parser_destroy(parser); 270 | scanner_destroy(scanner); 271 | catalogue_destroy(cat); 272 | } 273 | 274 | /* A correct query */ 275 | { 276 | const char *query_str = "SELECT attr1 FROM rel1 WHERE attr1=10 ORDER BY attr1 DESC;"; 277 | 278 | catalogue_t *cat = catalogue_create(); 279 | { 280 | const attr_name_t attr_names[] = {"id", "attr1", "attr2"}; 281 | const size_t attr_num = ARRAY_SIZE(attr_names); 282 | relation_t *rel1 = relation_create(attr_names, attr_num); 283 | catalogue_add_relation(cat, "rel1", rel1); 284 | } 285 | scanner_t *scanner = scanner_create(query_str); 286 | parser_t *parser = parser_create(); 287 | query_t *query = query_create(); 288 | assert(cat); 289 | assert(scanner); 290 | assert(parser); 291 | assert(query); 292 | assert(parser_parse(parser, scanner, query)); 293 | 294 | assert(validate(cat, query)); 295 | 296 | query_destroy(query); 297 | parser_destroy(parser); 298 | scanner_destroy(scanner); 299 | catalogue_destroy(cat); 300 | } 301 | } 302 | 303 | int main(int argc, char *argv[]) 304 | { 305 | (void) argc; (void) argv; 306 | 307 | /* Block stderr output to avoid err msg spamming */ 308 | int null_fd = open("/dev/null", O_WRONLY); 309 | int stderr_fd = dup(2); 310 | dup2(null_fd, 2); 311 | 312 | create_validate_test(); 313 | insert_validate_test(); 314 | select_validate_test(); 315 | 316 | /* Get back normal stderr */ 317 | dup2(stderr_fd, 2); 318 | 319 | return 0; 320 | } 321 | -------------------------------------------------------------------------------- /pigletql-validate.c: -------------------------------------------------------------------------------- 1 | #include "pigletql-validate.h" 2 | 3 | static bool attr_in_attr_names(const attr_name_t attr_name, const attr_name_t *attr_names, const uint16_t attr_num) 4 | { 5 | for (size_t attr_i = 0; attr_i < attr_num; attr_i++) { 6 | if (0 != strncmp(attr_names[attr_i], attr_name, MAX_ATTR_NAME_LEN)) 7 | continue; 8 | return true; 9 | } 10 | return false; 11 | 12 | } 13 | 14 | static bool attr_names_unique(const attr_name_t *attr_names, const uint16_t attr_num) 15 | { 16 | for (size_t self_i = 0; self_i < attr_num; self_i++) 17 | for (size_t other_i = 0; other_i < attr_num; other_i++) { 18 | if (self_i == other_i) 19 | continue; 20 | if (0 != strncasecmp(attr_names[self_i], attr_names[other_i], MAX_ATTR_NAME_LEN)) 21 | continue; 22 | 23 | const char *msg = "Error: duplicate attribute name '%s' at %zu and %zu\n"; 24 | fprintf(stderr, msg, attr_names[self_i], self_i, other_i); 25 | 26 | return false; 27 | } 28 | return true; 29 | } 30 | 31 | static bool rel_names_unique(const rel_name_t *rel_names, const uint16_t rel_num) 32 | { 33 | for (size_t self_i = 0; self_i < rel_num; self_i++) 34 | for (size_t other_i = 0; other_i < rel_num; other_i++) { 35 | if (self_i == other_i) 36 | continue; 37 | if (0 != strncasecmp(rel_names[self_i], rel_names[other_i], MAX_REL_NAME_LEN)) 38 | continue; 39 | 40 | const char *msg = "Error: duplicate relation name '%s' at %zu and %zu\n"; 41 | fprintf(stderr, msg, rel_names[self_i], self_i, other_i); 42 | 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | static bool validate_select(catalogue_t *cat, const query_select_t *query) 49 | { 50 | /* All the relations should exist */ 51 | for (size_t rel_i = 0; rel_i < query->rel_num; rel_i++) { 52 | if (catalogue_get_relation(cat, query->rel_names[rel_i])) 53 | continue; 54 | 55 | fprintf(stderr, "Error: relation '%s' does not exist\n", query->rel_names[rel_i]); 56 | return false; 57 | } 58 | 59 | /* Relation names should be unique */ 60 | if (!rel_names_unique(query->rel_names,query->rel_num)) 61 | return false; 62 | 63 | /* Attribute names should be unique */ 64 | if (!attr_names_unique(query->attr_names, query->attr_num)) 65 | return false; 66 | 67 | /* Attributes should be present in relations listed */ 68 | for (size_t attr_i = 0; attr_i < query->attr_num; attr_i++) { 69 | bool attr_found = false; 70 | for (size_t rel_i = 0; rel_i < query->rel_num; rel_i++) { 71 | relation_t *rel = catalogue_get_relation(cat, query->rel_names[rel_i]); 72 | if (!relation_has_attr(rel, query->attr_names[attr_i])) 73 | continue; 74 | attr_found = true; 75 | break; 76 | } 77 | if (attr_found) 78 | continue; 79 | 80 | const char *msg = "Error: unknown attribute name '%s'\n"; 81 | fprintf(stderr, msg, query->attr_names[attr_i]); 82 | return false; 83 | } 84 | 85 | /* Order by attribute should be available in the list of attributes chosen */ 86 | if (query->has_order) { 87 | if (!attr_in_attr_names(query->order_by_attr, query->attr_names, query->attr_num)) { 88 | const char *msg = "Error: unknown order by attribute '%s'\n"; 89 | fprintf(stderr, msg, query->order_by_attr); 90 | return false; 91 | } 92 | } 93 | 94 | /* Predicate attributes should be available in the list of attributes projected */ 95 | for (size_t pred_i = 0; pred_i < query->pred_num; pred_i++) { 96 | const query_predicate_t *predicate = &query->predicates[pred_i]; 97 | 98 | /* Attribute on the left should always be there */ 99 | { 100 | token_t token = predicate->left; 101 | char attr_name_buf[512] = {0}; 102 | strncpy(attr_name_buf, token.start, (size_t)token.length); 103 | 104 | if (!attr_in_attr_names(attr_name_buf, query->attr_names, query->attr_num)) { 105 | const char *msg = "Error: unknown left-hand side attribute name '%s' in predicate %zu\n"; 106 | fprintf(stderr, msg, attr_name_buf, pred_i); 107 | return false; 108 | } 109 | } 110 | 111 | /* Attribute on the right? */ 112 | { 113 | token_t token = predicate->right; 114 | if (token.type == TOKEN_IDENT) { 115 | char attr_name_buf[512] = {0}; 116 | strncpy(attr_name_buf, token.start, (size_t)token.length); 117 | 118 | if (!attr_in_attr_names(attr_name_buf, query->attr_names, query->attr_num)) { 119 | const char *msg = "Error: unknown right-hand side attribute name '%s' in predicate %zu\n"; 120 | fprintf(stderr, msg, attr_name_buf, pred_i); 121 | return false; 122 | } 123 | } 124 | } 125 | } 126 | 127 | return true; 128 | } 129 | 130 | static bool validate_create_table(catalogue_t *cat, const query_create_table_t *query) 131 | { 132 | /* A relation should not exists */ 133 | if (catalogue_get_relation(cat, query->rel_name)) { 134 | fprintf(stderr, "Error: relation '%s' already exists\n", query->rel_name); 135 | return false; 136 | } 137 | 138 | /* Attribute names are unique */ 139 | if (!attr_names_unique(query->attr_names, query->attr_num)) 140 | return false; 141 | 142 | return true; 143 | } 144 | 145 | static bool validate_insert(catalogue_t *cat, const query_insert_t *query) 146 | { 147 | /* A relation should exists */ 148 | relation_t *target_rel = catalogue_get_relation(cat, query->rel_name); 149 | if (!target_rel) { 150 | fprintf(stderr, "Error: relation '%s' does not exist\n", query->rel_name); 151 | return false; 152 | } 153 | 154 | /* Number of attribute values to be inserted should be correct */ 155 | uint16_t rel_attr_num = relation_get_attr_num(target_rel); 156 | uint16_t query_attr_num = query->value_num; 157 | if (rel_attr_num != query_attr_num) { 158 | fprintf(stderr, "Error: relation '%s' has %"PRIu16" attributes, only %"PRIu16" supplied\n", 159 | query->rel_name, rel_attr_num, query_attr_num); 160 | return false; 161 | } 162 | 163 | return true; 164 | } 165 | 166 | bool validate(catalogue_t *cat, const query_t *query) 167 | { 168 | switch (query->tag) { 169 | case QUERY_SELECT: 170 | return validate_select(cat, &query->as.select); 171 | case QUERY_CREATE_TABLE: 172 | return validate_create_table(cat, &query->as.create_table); 173 | case QUERY_INSERT: 174 | return validate_insert(cat, &query->as.insert); 175 | } 176 | assert(false); 177 | } 178 | -------------------------------------------------------------------------------- /pigletql-validate.h: -------------------------------------------------------------------------------- 1 | #ifndef PIGLETQL_VALIDATE_H 2 | #define PIGLETQL_VALIDATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "pigletql-catalogue.h" 9 | #include "pigletql-parser.h" 10 | 11 | bool validate(catalogue_t *cat, const query_t *query); 12 | 13 | #endif //PIGLETQL-VALIDATE_H 14 | -------------------------------------------------------------------------------- /pigletql.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "pigletql-parser.h" 7 | #include "pigletql-eval.h" 8 | #include "pigletql-catalogue.h" 9 | #include "pigletql-validate.h" 10 | 11 | void dump_predicate(const query_predicate_t *predicate) 12 | { 13 | char buf[1024] = { 0 }; 14 | strncat(buf, predicate->left.start, (size_t)predicate->left.length); 15 | strncat(buf, " ", 1); 16 | strncat(buf, predicate->op.start, (size_t)predicate->op.length); 17 | strncat(buf, " ", 1); 18 | strncat(buf, predicate->right.start, (size_t)predicate->right.length); 19 | printf(" %s,\n", buf); 20 | } 21 | 22 | void dump_select(const query_select_t *query) 23 | { 24 | printf("SELECT\n"); 25 | 26 | for (size_t i = 0; i < query->attr_num; ++i) 27 | printf(" %s,\n", query->attr_names[i]); 28 | 29 | printf("FROM\n"); 30 | for (size_t i = 0; i < query->rel_num; ++i) 31 | printf(" %s,\n", query->rel_names[i]); 32 | 33 | if (!query->pred_num) 34 | goto order; 35 | 36 | printf("WHERE\n"); 37 | for (size_t i = 0; i < query->pred_num; ++i) 38 | dump_predicate(&query->predicates[i]); 39 | 40 | order: 41 | if (!query->has_order) 42 | return; 43 | 44 | printf("ORDER BY\n"); 45 | printf(" %s\n", query->order_by_attr); 46 | printf(query->order_type == SORT_ASC ? " ASC\n" : " DESC\n"); 47 | } 48 | 49 | void dump_create_table(const query_create_table_t *query) 50 | { 51 | printf("CREATE TABLE \n"); 52 | 53 | printf(" %s\n", query->rel_name); 54 | 55 | printf("(\n "); 56 | for (size_t i = 0; i < query->attr_num; ++i) 57 | printf("%s,", query->attr_names[i]); 58 | printf("\n)\n"); 59 | } 60 | 61 | void dump_insert(const query_insert_t *query) 62 | { 63 | printf("INSERT INTO \n"); 64 | 65 | printf(" %s\n", query->rel_name); 66 | 67 | printf("(\n "); 68 | for (size_t i = 0; i < query->value_num; ++i) 69 | printf("%"PRI_VALUE",", query->values[i]); 70 | printf("\n)\n"); 71 | } 72 | 73 | 74 | void dump(const query_t *query) 75 | { 76 | switch (query->tag) { 77 | case QUERY_SELECT: 78 | dump_select(&query->as.select); 79 | break; 80 | case QUERY_CREATE_TABLE: 81 | dump_create_table(&query->as.create_table); 82 | break; 83 | case QUERY_INSERT: 84 | dump_insert(&query->as.insert); 85 | break; 86 | } 87 | } 88 | 89 | operator_t *compile_select(catalogue_t *cat, const query_select_t *query) 90 | { 91 | /* Current root operator */ 92 | operator_t *root_op = NULL; 93 | 94 | /* 1. Scan ops */ 95 | /* 2. Join ops*/ 96 | 97 | { 98 | size_t rel_i = 0; 99 | relation_t *rel = catalogue_get_relation(cat, query->rel_names[rel_i]); 100 | root_op = scan_op_create(rel); 101 | rel_i += 1; 102 | 103 | for (; rel_i < query->rel_num; rel_i++) { 104 | rel = catalogue_get_relation(cat, query->rel_names[rel_i]); 105 | operator_t *scan_op = scan_op_create(rel); 106 | root_op = join_op_create(root_op, scan_op); 107 | } 108 | } 109 | 110 | /* 3. Project */ 111 | root_op = proj_op_create(root_op, query->attr_names, query->attr_num); 112 | 113 | /* 4. Select */ 114 | if (query->pred_num > 0) { 115 | operator_t *select_op = select_op_create(root_op); 116 | for (size_t pred_i = 0; pred_i < query->pred_num; pred_i++) { 117 | query_predicate_t predicate = query->predicates[pred_i]; 118 | 119 | /* On the left we always get an identifier */ 120 | assert(predicate.left.type == TOKEN_IDENT); 121 | 122 | attr_name_t left_attr_name = {0}; 123 | strncpy(left_attr_name, predicate.left.start, (size_t)predicate.left.length); 124 | 125 | select_predicate_op pred_op = 0; 126 | switch (predicate.op.type) { 127 | case TOKEN_GREATER: 128 | pred_op = SELECT_GT; 129 | break; 130 | case TOKEN_LESS: 131 | pred_op = SELECT_LT; 132 | break; 133 | case TOKEN_EQUAL: 134 | pred_op = SELECT_EQ; 135 | break; 136 | default: 137 | /* Uknown predicate type */ 138 | assert(false); 139 | } 140 | 141 | /* On the right it's either a constant or another identifier */ 142 | if (predicate.right.type == TOKEN_IDENT) { 143 | attr_name_t right_attr_name = {0}; 144 | strncpy(right_attr_name, predicate.right.start, (size_t)predicate.right.length); 145 | 146 | select_op_add_attr_attr_predicate(select_op, left_attr_name, pred_op, right_attr_name); 147 | } else if (predicate.right.type == TOKEN_NUMBER) { 148 | char buf[128] = {0}; 149 | strncpy(buf, predicate.right.start, (size_t)predicate.right.length); 150 | 151 | value_type_t right_const = 0; 152 | sscanf(buf, "%" SCN_VALUE, &right_const); 153 | 154 | select_op_add_attr_const_predicate(select_op, left_attr_name, pred_op, right_const); 155 | } else { 156 | /* Invalid token */ 157 | assert(false); 158 | } 159 | } 160 | root_op = select_op; 161 | } 162 | 163 | /* 5. Sort */ 164 | if (query->has_order) 165 | root_op = sort_op_create(root_op, query->order_by_attr, query->order_type); 166 | 167 | return root_op; 168 | } 169 | 170 | void dump_tuple_header(tuple_t *tuple) 171 | { 172 | const uint16_t attr_num = tuple_get_attr_num(tuple); 173 | 174 | for (uint16_t attr_i = 0; attr_i < attr_num; attr_i++) { 175 | const char *attr_name = tuple_get_attr_name_by_i(tuple, attr_i); 176 | if (attr_i != attr_num - 1) 177 | printf("%s ", attr_name); 178 | else 179 | printf("%s\n", attr_name); 180 | } 181 | } 182 | 183 | void dump_tuple(tuple_t *tuple) 184 | { 185 | const uint16_t attr_num = tuple_get_attr_num(tuple); 186 | 187 | /* attribute values for all rows */ 188 | for (uint16_t attr_i = 0; attr_i < attr_num; attr_i++) { 189 | value_type_t attr_val = tuple_get_attr_value_by_i(tuple, attr_i); 190 | if (attr_i != attr_num - 1) 191 | printf("%u ", attr_val); 192 | else 193 | printf("%u\n", attr_val); 194 | } 195 | } 196 | 197 | bool eval_select(catalogue_t *cat, const query_select_t *query) 198 | { 199 | /* Compile the operator tree: */ 200 | operator_t *root_op = compile_select(cat, query); 201 | 202 | 203 | /* Eval the tree: */ 204 | { 205 | root_op->open(root_op->state); 206 | 207 | size_t tuples_received = 0; 208 | tuple_t *tuple = NULL; 209 | while((tuple = root_op->next(root_op->state))) { 210 | /* attribute list for the first row only */ 211 | if (tuples_received == 0) 212 | dump_tuple_header(tuple); 213 | 214 | /* A table of tuples */ 215 | dump_tuple(tuple); 216 | 217 | tuples_received++; 218 | } 219 | printf("rows: %zu\n", tuples_received); 220 | 221 | root_op->close(root_op->state); 222 | } 223 | 224 | root_op->destroy(root_op); 225 | 226 | return true; 227 | } 228 | 229 | bool eval_create_table(catalogue_t *cat, const query_create_table_t *query) 230 | { 231 | relation_t *rel = relation_create(query->attr_names, query->attr_num); 232 | if (!rel) 233 | goto rel_err; 234 | 235 | if (!catalogue_add_relation(cat, query->rel_name, rel)) 236 | goto cat_err; 237 | 238 | return true; 239 | 240 | cat_err: 241 | relation_destroy(rel); 242 | 243 | rel_err: 244 | return false; 245 | } 246 | 247 | bool eval_insert(catalogue_t *cat, const query_insert_t *query) 248 | { 249 | relation_t *rel = catalogue_get_relation(cat, query->rel_name); 250 | assert(rel); /* should be validated by now */ 251 | 252 | relation_append_values(rel, query->values); 253 | 254 | return true; 255 | } 256 | 257 | bool eval(catalogue_t *cat, const query_t *query) 258 | { 259 | switch (query->tag) { 260 | case QUERY_SELECT: 261 | return eval_select(cat, &query->as.select); 262 | case QUERY_CREATE_TABLE: 263 | return eval_create_table(cat, &query->as.create_table); 264 | case QUERY_INSERT: 265 | return eval_insert(cat, &query->as.insert); 266 | } 267 | assert(false); 268 | } 269 | 270 | void run(catalogue_t *cat, const char *query_str) 271 | { 272 | scanner_t *scanner = scanner_create(query_str); 273 | parser_t *parser = parser_create(); 274 | query_t *query = query_create(); 275 | 276 | if (parser_parse(parser, scanner, query)) { 277 | /* dump(query); */ 278 | if (validate(cat, query)) 279 | eval(cat, query); 280 | } 281 | 282 | scanner_destroy(scanner); 283 | parser_destroy(parser); 284 | query_destroy(query); 285 | } 286 | 287 | int main(int argc, char *argv[]) 288 | { 289 | (void) argc; (void) argv; 290 | 291 | catalogue_t *cat = catalogue_create(); 292 | 293 | while (true) { 294 | char line[1024]; 295 | 296 | printf("> "); 297 | 298 | if (!fgets(line, sizeof(line), stdin)) { 299 | printf("\n"); 300 | break; 301 | } 302 | 303 | /* strip a newline at the end of the line */ 304 | line[strlen(line) - 1] = '\0'; 305 | 306 | run(cat, line); 307 | } 308 | 309 | catalogue_destroy(cat); 310 | 311 | return 0; 312 | } 313 | --------------------------------------------------------------------------------