├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── build_insert_statement.h ├── getword.h ├── getwords.h ├── getwordsregex.h ├── options.h ├── pcre_split.c ├── pcre_split.h ├── process_line.h ├── sqlbong.c ├── sqlbong.cfdg ├── sqlbong.png ├── test.csv └── test.txt /.gitignore: -------------------------------------------------------------------------------- 1 | sqlbong 2 | sqlbong.dSYM/ 3 | sqlpipe 4 | sqlpipe.dSYM/ 5 | *.o 6 | *.h.gch 7 | *.db 8 | usage.h 9 | *.zip 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pcre_split"] 2 | path = pcre_split 3 | url = https://github.com/sordina/pcre_split.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Lyndon Maydwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CLANG_OPTS = -I /usr/local/include -L /usr/local/lib 2 | Compiler = clang 3 | Options = -lm -Wall pcre_split.c sqlbong.c -lpcre -lsqlite3 -o sqlbong 4 | 5 | # Targets 6 | 7 | sqlbong: pcre_split/src usage.h *.c *.h 8 | ${Compiler} ${CLANG_OPTS} ${Options} 9 | 10 | .PHONY: install 11 | install: sqlbong 12 | cp sqlbong /usr/local/bin/ 13 | 14 | .PHONY: uninstall 15 | uninstall: 16 | rm /usr/local/bin/sqlbong 17 | 18 | .PHONY: debug 19 | debug: clean usage.h 20 | ${Compiler} ${CLANG_OPTS} -ggdb -DDEBUG=1 ${Options} 21 | 22 | .PHONY: clean 23 | clean: 24 | rm -rf sqlbong *.o sql*SYM/ usage.h 25 | 26 | .PHONY: test 27 | test: sqlbong runtests 28 | 29 | .PHONY: testdebug 30 | testdebug: debug runtests 31 | 32 | pcre_split/src: 33 | git submodule update --init --recursive 34 | 35 | .PHONY: upload 36 | upload: sqlbong 37 | $(eval zipfile := $(shell echo "sqlbong-0.1.0-`git rev-parse --short HEAD`-`sw_vers -productName | sed 's/ //g'`-`sw_vers -productVersion`-`sw_vers -buildVersion`.zip")) 38 | zip $(zipfile) sqlbong README.md 39 | s3cmd put --acl-public $(zipfile) s3://sordina.binaries/$(zipfile) 40 | rm $(zipfile) 41 | 42 | usage.h: 43 | # Convert README.md to a printing function 44 | echo "#ifndef USAGEH" > usage.h 45 | echo "#define USAGEH" >> usage.h 46 | echo "void usage() {" >> usage.h 47 | cat README.md | sed 's/["%\\]/\\&/g;s/^/printf("/;s/$$/\\n");/' | grep -v '> usage.h 48 | echo "}" >> usage.h 49 | echo "#endif" >> usage.h 50 | 51 | .PHONY: runtests 52 | runtests: 53 | # Multi column data 54 | (echo 111 222 333; echo 444 555 666; echo 777 888 999) | ./sqlbong "select c1, c2 from data" 55 | # Increasing columns 56 | (echo 1; echo 1 2; echo 1 2 3; echo 1 2 3 4) | ./sqlbong "select c1, c2, c4 from data" 57 | # Decreasing columns 58 | (echo 1; echo 1 2; echo 1 2 3; echo 1 2; echo 1) | ./sqlbong "select c1, c3, c2 from data" 59 | # Blank lines 60 | (echo 1 3 2 4; echo; echo 1 2 3) | ./sqlbong "select c2 from data" 61 | # Long words 62 | (echo ksjdhfklsjdhfksjhdfkjshdl; \ 63 | echo abdominogenital; \ 64 | echo skfdjlhasldkfhlaskdjfhlaksdjhflkjahs; \ 65 | echo foooooooooooofoo fooooof fooooooooooooo) | ./sqlbong "select c1, c2 from data" 66 | # Not sure why it's throwing an error, but: 67 | echo "95585 ttys002 0:03.11 vim sqlbong.c" | ./sqlbong "select * from data" 68 | # Lots of words 69 | echo a b c d e f g h i j k l m n o p q r s t u v w x y | ./sqlbong "select * from data" 70 | echo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ./sqlbong "select c15 from data" | grep 15 71 | (echo 1_3_2_4; echo 5___6__7_8) | ./sqlbong -d '_+' "select c1,c2 from data" 72 | ./sqlbong -d , 'select * from data' < test.csv > /dev/null 73 | (echo 1 2 3; echo 1 2) | ./sqlbong -d ' ' 'select * from data' | wc -l | grep 2 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | SQLBong 3 | ======= 4 | 5 | 6 | Parse columnized output with SQL - Thanks to the power of [SQLite3](http://www.sqlite.org/). 7 | 8 | The SQL syntax accepted is completely determined by what SQLite3 supports. 9 | 10 | Why the name? Because `SQLPipe` was taken. 11 | 12 | The SQL Bong 13 | 14 | ## Alternatives: 15 | 16 | * 17 | * 18 | * 19 | * 20 | * 21 | * 22 | * 23 | * 24 | 25 | ## Examples: 26 | 27 | > ps | sqlbong "select * from data" 28 | 29 | Acts like cat, but normalises the delimiters to one space - Passthrough. 30 | 31 | > cat data.txt | sqlbong -f foo.db 32 | 33 | Send columnized data from data.txt into an sqlite database foo.db 34 | 35 | Server> nc -l 1234 | sqlbong -f out.db 36 | 37 | Client> nc 127.0.0.1 1234 38 | hello world 39 | how are 40 | you ? 41 | 42 | Server> sqlite3 out.db ".dump" 43 | PRAGMA foreign_keys=OFF; 44 | BEGIN TRANSACTION; 45 | CREATE TABLE data (c1, c2); 46 | INSERT INTO "data" VALUES('hello','world'); 47 | INSERT INTO "data" VALUES('how','are'); 48 | INSERT INTO "data" VALUES('you','?'); 49 | COMMIT; 50 | 51 | Log output to a database on a remote server. 52 | 53 | > cat test.txt | sqlbong "select c2 from data limit 2" "select c1, c3, c2 from data where c2 > '4' order by c3 desc" 54 | 55 | ### test.txt: 56 | 57 | 1 2 3 58 | 2 3 4 59 | 3 4 5 60 | 4 5 6 61 | 5 6 7 62 | 6 7 8 63 | 64 | ### Output: 65 | 66 | 2 67 | 3 68 | 6 8 7 69 | 5 7 6 70 | 4 6 5 71 | 72 | Details: 73 | -------- 74 | 75 | * The table used is called 'data' 76 | * There are as many columns in 'data' as the maximum number of columns in the input data 77 | * The columns are labled (c1 .. c\) 78 | * Data is stored as 'text' type (useful to remember for comparisons, numeric operations) 79 | 80 | Known Bugs 81 | ---------- 82 | 83 | * Not all memory is freed correctly 84 | 85 | To Do 86 | ----- 87 | 88 | * Add options for reusing a database 89 | * Check out for faster table creation 90 | 91 | Usage 92 | ----- 93 | 94 | Accepts data on STDIN, takes SQL statements as arguments (make sure you quote them) and outputs data selected on STDOUT. 95 | 96 | Usage: 97 | 98 | sqlbong [-h] [-f ] [-d ] * 99 | 100 | Options: 101 | 102 | -h Help 103 | -d Regex delimiter 104 | -f On disk database (defaults to in-memory) 105 | 106 | SQL: 107 | 108 | Columns - c1..cn 109 | Table - data 110 | 111 | 112 | Binaries 113 | -------- 114 | 115 | * 116 | * 117 | * 118 | * 119 | -------------------------------------------------------------------------------- /build_insert_statement.h: -------------------------------------------------------------------------------- 1 | #ifndef BUILD_INSERTH 2 | #define BUILD_INSERTH 3 | 4 | #include 5 | #include 6 | 7 | char* insert(int num) { 8 | 9 | #ifdef DEBUG 10 | printf("Inside Builder - Number of items: [%d].\n", num); 11 | #endif 12 | 13 | // TODO: This is quite fragile be careful if modifying 14 | const char dstmt[] = "insert into data()"; 15 | const char vstmt[] = " values()"; 16 | const int dlen = strlen(dstmt); 17 | const int vlen = strlen(vstmt); 18 | const int boiler_len = dlen + vlen; 19 | const int dyn_factor = 1 + (int) log10(num); 20 | const int dyn_len = dyn_factor * num; // represents the size of the column-number text (minus formatting) (overshoots estimate by log(n) factor) 21 | char* dyn_part = malloc(sizeof(char) * (dyn_factor + 3)); // constant is 'c' ',' '\0' 22 | char* result = malloc(sizeof(char) * (boiler_len + (4 * num) + dyn_len) + 2); 23 | char* p = result; // movable pointer for printing 24 | 25 | memcpy(p, dstmt, dlen * sizeof(char)); 26 | p += dlen - 1; 27 | 28 | int i; 29 | for(i = 1; i <= num; i++) { 30 | sprintf(dyn_part, "c%d,", i); 31 | sprintf(p, "%s", dyn_part); 32 | 33 | p += ((int) log10(i)) + 1 + 2; // constant is the length of 'c' ',' the first digit, and one more to advance to next position (where '\0' is) 34 | } 35 | p[-1] = ')'; 36 | 37 | memcpy(p, vstmt, vlen * sizeof(char)); 38 | p += vlen - 1; 39 | 40 | for(i = 0; i < num; i++) { 41 | *p = '?'; p++; 42 | *p = ','; p++; 43 | } 44 | 45 | p[-1] = ')'; 46 | p[ 0] = '\0'; 47 | p[ 1] = '\0'; 48 | p[ 2] = '\0'; 49 | // TODO: Check UTF8 fix 50 | 51 | #ifdef DEBUG 52 | printf("Inside build_insert_statement - Created result [%s].\n", result); 53 | #endif 54 | 55 | free(dyn_part); 56 | 57 | return result; 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /getword.h: -------------------------------------------------------------------------------- 1 | #ifndef GETWORDH 2 | #define GETWORDH 1 3 | 4 | #include 5 | #include 6 | 7 | char* getword(char delim, char** line) { 8 | 9 | // Skip leading delimiters 10 | while(**line == delim) { 11 | #ifdef DEBUG 12 | printf("Inside getword - removing leading delimiter.\n"); 13 | #endif 14 | (*line)++; 15 | } 16 | 17 | char* word_start = *line; 18 | char* word_end = *line - 1; 19 | 20 | // Is this an empty line? 21 | if((!line) || **line == EOF || **line == '\n') { return NULL; } 22 | 23 | // Proceed to the end of the word 24 | while (**line && **line != EOF && **line != '\n' && **line != delim) { 25 | word_end = *line; 26 | (*line)++; 27 | } 28 | 29 | int word_length = 1 + word_end - word_start; 30 | 31 | #if DEBUG 32 | printf("Inside getword - word length: [%d] characters.\n", word_length); 33 | #endif 34 | 35 | if(word_length < 1) { 36 | #ifdef DEBUG 37 | printf("Inside getword - Blank word.\n"); 38 | #endif 39 | return NULL; 40 | } 41 | 42 | char* result = malloc(sizeof(char) * (word_length + 1)); 43 | 44 | memcpy(result, word_start, word_length); 45 | 46 | result[word_length] = '\0'; 47 | 48 | return result; 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /getwords.h: -------------------------------------------------------------------------------- 1 | #ifndef GETWORDSH 2 | #define GETWORDSH 1 3 | 4 | #include "getword.h" 5 | 6 | char** getwords(char* line, int* numwords) { // TODO: Take a size param to make this safe 7 | 8 | char* word; 9 | int cap = 2; // Start with 2 word limit 10 | char** result = malloc(cap * sizeof(char*)); 11 | 12 | *numwords = 0; 13 | 14 | while((word = getword(' ', &line))) { 15 | 16 | #if DEBUG 17 | printf("Inside getwords - Got word: [%s], rest of line: [%s]\n", word, line); 18 | #endif 19 | 20 | if(*numwords >= cap / 2) { 21 | cap = (cap + 2) * 2; // TODO: Why does this need to be cap + 2 ? 22 | result = realloc(result, cap * sizeof(char*)); 23 | #ifdef DEBUG 24 | printf("Inside getwords - Reallocated.\n"); 25 | #endif 26 | } 27 | 28 | #ifdef DEBUG 29 | printf("Inside getwords - Numwords: %d, Cap: %d\n", *numwords, cap); 30 | #endif 31 | 32 | result[*numwords] = word; 33 | 34 | #ifdef DEBUG 35 | printf("Inside getwords - Assigned word to list.\n"); 36 | #endif 37 | 38 | (*numwords)++; 39 | } 40 | 41 | if(0 == *numwords) { 42 | #ifdef DEBUG 43 | printf("Inside getwords - No words\n"); 44 | #endif 45 | free(result); 46 | return NULL; 47 | } 48 | 49 | #ifdef DEBUG 50 | printf("Inside getwords - Finished getting words: %d\n", *numwords); 51 | #endif 52 | 53 | return result; 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /getwordsregex.h: -------------------------------------------------------------------------------- 1 | #ifndef GETWORDSREGEX 2 | #define GETWORDSREGEX 3 | 4 | #include "pcre_split/inc/pcre_split.h" 5 | 6 | char** getwordsregex(char* delimiter, char* line, int* numwords) { 7 | split_t* tokens = pcre_split(delimiter, line); 8 | split_t* start = tokens; 9 | 10 | #ifdef DEBUG 11 | pcre_split_print(tokens); 12 | #endif 13 | 14 | *numwords = 0; 15 | 16 | while(start) { 17 | (*numwords)++; 18 | start = start->next; 19 | } 20 | 21 | start = tokens; 22 | 23 | char** result = malloc(sizeof(char*) * (*numwords)); 24 | char** resultp = result; 25 | 26 | while(start) { 27 | *resultp = start->string; 28 | resultp++; 29 | start = start->next; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /options.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONSH 2 | #define OPTIONSH 3 | 4 | #include 5 | #include 6 | #include "usage.h" 7 | 8 | typedef struct globalArgs_t { 9 | char* file; 10 | int num; 11 | char* delimiter; // TODO: Useful option 12 | // int overwrite; // TODO: Useful option 13 | // char* table_name; // TODO: Useful option 14 | } globalArgs; 15 | 16 | globalArgs getOpts(int argc, char** argv) { 17 | 18 | #ifdef DEBUG 19 | printf("Inside getOpts - Argc [%d].\n", argc); 20 | #endif 21 | 22 | int i; 23 | globalArgs result; 24 | 25 | result.file = NULL; 26 | result.num = 0; 27 | result.delimiter = NULL; 28 | // result.table_name = NULL 29 | // result.overwrite = 1; 30 | 31 | // NOTE: Incrementing i by 2 as all flags take a param 32 | for(i = 0; i < argc; i++) { 33 | #ifdef DEBUG 34 | printf("Inside getOpts - Arg %d - [%s].\n", i, argv[i]); 35 | #endif 36 | if(0 == strcmp("-f", argv[i])) { 37 | result.file = argv[i+1]; 38 | result.num += 2; 39 | i++; 40 | } else 41 | if(0 == strcmp("-d", argv[i])) { 42 | result.delimiter = argv[i+1]; 43 | result.num += 2; 44 | i++; 45 | } else 46 | if(0 == strcmp("-h",argv[i])) { 47 | usage(); 48 | exit(1); 49 | } 50 | } 51 | 52 | return result; 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /pcre_split.c: -------------------------------------------------------------------------------- 1 | pcre_split/src/pcre_split.c -------------------------------------------------------------------------------- /pcre_split.h: -------------------------------------------------------------------------------- 1 | pcre_split/inc/pcre_split.h -------------------------------------------------------------------------------- /process_line.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_LINEH 2 | #define PROCESS_LINEH 3 | 4 | #include 5 | #include 6 | 7 | #include "getwords.h" 8 | #include "build_insert_statement.h" 9 | 10 | // Dangerous and stupid, but we're already doing this stringy stuff, so let's do it anyway! 11 | void remove_newlines(char* line) { 12 | char* p = line; 13 | while(*p) { 14 | if(*p == '\n') { 15 | *p = 0; 16 | break; 17 | } 18 | p++; 19 | } 20 | } 21 | 22 | void process_line(char* line, int* columns, sqlite3* db, globalArgs options) { 23 | 24 | int return_code; 25 | char* zErrMsg; 26 | int numwords; 27 | char command[400]; 28 | 29 | remove_newlines(line); 30 | 31 | #ifdef DEBUG 32 | printf("Inside process_line - Line: %s\n", line); 33 | #endif 34 | 35 | char** words; 36 | 37 | if(options.delimiter) { 38 | words = getwordsregex(options.delimiter, line, &numwords); 39 | } else { 40 | words = getwords(line, &numwords); 41 | } 42 | 43 | #ifdef DEBUG 44 | if( numwords == 0 || ! words ) { fprintf(stderr, "No words on this line.\n"); } 45 | #endif 46 | 47 | #ifdef DEBUG 48 | int j; 49 | for(j = 0; j < numwords; j++) { 50 | printf("Inside process_line - Word: [%s]\n", words[j]); 51 | } 52 | #endif 53 | 54 | // Adjust table size if needed 55 | if(numwords > *columns) { 56 | 57 | while(*columns < numwords) { 58 | (*columns)++; 59 | sprintf(command, "alter table data add column c%d", *columns); 60 | 61 | #ifdef DEBUG 62 | printf("Inside process_line - Adding new column with command [%s]\n", command); 63 | #endif 64 | 65 | // TODO: Ensure that this is okay 66 | return_code = sqlite3_exec(db, command, NULL, NULL, &zErrMsg); 67 | HANDLE_ERROR(return_code); 68 | } 69 | } 70 | 71 | if(numwords < 1) { 72 | return_code = sqlite3_exec(db, "insert into data(c1) values(NULL)", NULL, NULL, &zErrMsg); 73 | HANDLE_ERROR(return_code); 74 | return; 75 | } 76 | 77 | char* insert_statement = insert(numwords); 78 | 79 | #ifdef DEBUG 80 | printf("Inside process_line - Created insert statement [%s].\n", insert_statement); 81 | #endif 82 | 83 | sqlite3_stmt *stmt; 84 | 85 | if ( sqlite3_prepare_v2( db, insert_statement, -1, &stmt, 0 ) != SQLITE_OK ) { 86 | fprintf(stderr, "Could not prepare statement [%s]\n.", insert_statement); 87 | exit(1); 88 | } 89 | 90 | #ifdef DEBUG 91 | printf("Prepared insert statement.\n"); 92 | #endif 93 | 94 | int i; 95 | for(i = 0; i < numwords; i++) { 96 | 97 | // SQLITE_TRANSIENT allows words to be freed 98 | if ( sqlite3_bind_text( stmt, i+1, words[i], -1 /* length of text */, SQLITE_TRANSIENT ) != SQLITE_OK ) { 99 | fprintf(stderr,"\nCould not bind text [%s].\n", words[i]); 100 | exit(1); 101 | } 102 | 103 | #ifdef DEBUG 104 | printf("Bound word [%s].\n", words[i]); 105 | #endif 106 | // TODO: Need to find a way to free the regex_split struct 107 | // 108 | // The fact that this isn't being freed may cause a memory-leak proportional to the 109 | // number of words bound in the query... Which shouldn't be much. 110 | // 111 | if(! options.delimiter) { 112 | free(words[i]); 113 | } 114 | 115 | } 116 | free(words); // words is no longer used 117 | 118 | #ifdef DEBUG 119 | printf("Freed words.\n"); 120 | #endif 121 | 122 | if (sqlite3_step(stmt) != SQLITE_DONE) { 123 | fprintf(stderr,"\nCould not step (execute) stmt.\n"); 124 | exit(1); 125 | } 126 | 127 | sqlite3_finalize(stmt); 128 | free(insert_statement); 129 | } 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /sqlbong.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define HANDLE_ERROR(rc) \ 5 | if( rc!=SQLITE_OK ){ \ 6 | fprintf(stderr, "SQL error: %s\n", zErrMsg); \ 7 | sqlite3_free(zErrMsg); \ 8 | exit(1); \ 9 | } 10 | 11 | #include "getwords.h" 12 | #include "getwordsregex.h" 13 | #include "build_insert_statement.h" 14 | #include "options.h" 15 | #include "process_line.h" 16 | 17 | const char sep_default = ' '; 18 | 19 | static int callback(void* userArg, int count, char** columns, char** column_names){ 20 | 21 | #ifdef DEBUG 22 | printf("Columns in query: %d\n", count); 23 | #endif 24 | 25 | int i; 26 | for(i=0; i] [-d ] *\n", argv[0]); 55 | exit(1); 56 | } 57 | 58 | argv++; 59 | 60 | // TODO: Fixme 61 | globalArgs options = getOpts(argc, argv); 62 | 63 | // Create a database in memory 64 | if(options.file) { 65 | rc = sqlite3_open(options.file, &db); 66 | } else { 67 | rc = sqlite3_open(":memory:", &db); 68 | } 69 | 70 | // Create a table with an inital 9 columns. Why 9? Because. 71 | rc = sqlite3_exec(db, "create table data (c1)", NULL, NULL, &zErrMsg); 72 | HANDLE_ERROR(rc); 73 | 74 | // Parse stdin, expanding table where necessary 75 | while(fgets(line, line_buffer_length, stdin)) { process_line(line, &columns, db, options); } 76 | 77 | // Run all queries 78 | for( i=0 + options.num; i <= argc; i++ ) { 79 | 80 | #ifdef DEBUG 81 | printf("Query: %s\n", argv[i]); 82 | printf("Results:\n"); 83 | #endif 84 | 85 | rc = sqlite3_exec(db, argv[i], callback, NULL, &zErrMsg); 86 | HANDLE_ERROR(rc); 87 | } 88 | 89 | sqlite3_close(db); 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /sqlbong.cfdg: -------------------------------------------------------------------------------- 1 | startshape bong 2 | 3 | CF::Background = [a -1] 4 | 5 | shape bong { 6 | TRIANGLE [] 7 | SQUARE [s 0.4 1.3 y 1.3 r 10 x -0.2] 8 | CIRCLE [s 1.2 y 0.4] 9 | SQUARE [s 1 0.1 x 0.3 y 0.5 r 40] 10 | TRIANGLE [s 0.2 x 0.7 y 0.83 r 10] 11 | S [b 1 s 0.7 y 1.5 r 10 x -0.24] 12 | Q [b 1 s 0.6 y 0.8 r 10 x -0.1] 13 | L [b 1 s 0.8 y 0.15 r 10 x -0.05] 14 | } 15 | 16 | path S { 17 | 18 | MOVETO ( 0.14174454828660435 , 0.5 ) 19 | 20 | 21 | LINETO ( -0.14797507788161993 , 0.38161993769470404 ) 22 | LINETO ( -0.2881619937694704 , 0.13239875389408098 ) 23 | LINETO ( -0.16043613707165108 , -0.08566978193146417 ) 24 | LINETO ( 0.09813084112149532 , -0.14174454828660435 ) 25 | LINETO ( 0.12305295950155763 , -0.2102803738317757 ) 26 | LINETO ( 0.06386292834890965 , -0.29439252336448596 ) 27 | LINETO ( -0.24143302180685358 , -0.34735202492211836 ) 28 | LINETO ( -0.2383177570093458 , -0.5 ) 29 | LINETO ( 0.19470404984423675 , -0.43146417445482865 ) 30 | LINETO ( 0.2881619937694704 , -0.24454828660436137 ) 31 | LINETO ( 0.27258566978193144 , -0.06386292834890965 ) 32 | LINETO ( 0.08878504672897196 , 0.020249221183800622 ) 33 | LINETO ( -0.08878504672897196 , 0.0514018691588785 ) 34 | LINETO ( -0.13239875389408098 , 0.19781931464174454 ) 35 | LINETO ( 0.05763239875389408 , 0.3099688473520249 ) 36 | LINETO ( 0.16978193146417445 , 0.35358255451713394 ) 37 | 38 | 39 | CLOSEPOLY() 40 | FILL [] 41 | 42 | } 43 | 44 | path Q { 45 | 46 | MOVETO ( -0.3260135135135135 , 0.2939189189189189 ) 47 | 48 | 49 | LINETO ( -0.17736486486486486 , 0.47635135135135137 ) 50 | LINETO ( 0.16722972972972974 , 0.5 ) 51 | LINETO ( 0.33614864864864863 , 0.28378378378378377 ) 52 | LINETO ( 0.3564189189189189 , -0.14189189189189189 ) 53 | LINETO ( 0.28547297297297297 , -0.26013513513513514 ) 54 | LINETO ( 0.3766891891891892 , -0.35135135135135137 ) 55 | LINETO ( 0.44425675675675674 , -0.3547297297297297 ) 56 | LINETO ( 0.44425675675675674 , -0.46959459459459457 ) 57 | LINETO ( 0.31925675675675674 , -0.44932432432432434 ) 58 | LINETO ( 0.21452702702702703 , -0.3581081081081081 ) 59 | LINETO ( 0.10641891891891891 , -0.4864864864864865 ) 60 | LINETO ( -0.18412162162162163 , -0.5 ) 61 | LINETO ( -0.38344594594594594 , -0.35135135135135137 ) 62 | LINETO ( -0.44425675675675674 , 0.030405405405405407 ) 63 | LINETO ( -0.36317567567567566 , 0.13851351351351351 ) 64 | LINETO ( -0.2719594594594595 , 0.10472972972972973 ) 65 | LINETO ( -0.2956081081081081 , -0.037162162162162164 ) 66 | LINETO ( -0.21114864864864866 , -0.23648648648648649 ) 67 | LINETO ( -0.05574324324324324 , -0.3141891891891892 ) 68 | LINETO ( 0.07939189189189189 , -0.24324324324324326 ) 69 | LINETO ( 0.015202702702702704 , -0.16891891891891891 ) 70 | LINETO ( 0.11317567567567567 , -0.08445945945945946 ) 71 | LINETO ( 0.18412162162162163 , -0.16216216216216217 ) 72 | LINETO ( 0.22128378378378377 , -0.060810810810810814 ) 73 | LINETO ( 0.17398648648648649 , 0.23648648648648649 ) 74 | LINETO ( 0.03885135135135135 , 0.3141891891891892 ) 75 | LINETO ( -0.15033783783783783 , 0.2939189189189189 ) 76 | LINETO ( -0.22466216216216217 , 0.22635135135135134 ) 77 | 78 | 79 | CLOSEPOLY() 80 | FILL [] 81 | 82 | } 83 | 84 | 85 | path L { 86 | 87 | MOVETO ( -0.3953488372093023 , 0.42441860465116277 ) 88 | 89 | 90 | LINETO ( -0.38953488372093026 , -0.47093023255813954 ) 91 | LINETO ( 0.3953488372093023 , -0.5 ) 92 | LINETO ( 0.3866279069767442 , -0.22093023255813954 ) 93 | LINETO ( -0.13662790697674418 , -0.25872093023255816 ) 94 | LINETO ( -0.21220930232558138 , 0.5 ) 95 | 96 | 97 | CLOSEPOLY() 98 | FILL [] 99 | 100 | } -------------------------------------------------------------------------------- /sqlbong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sordina/SQLBong/153d6f4b1d1c204a7b52ae8eae95f86ab8fcdbe6/sqlbong.png -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | 1 2 3 2 | 2 3 4 3 | 3 4 5 4 | 4 5 6 5 | 5 6 7 6 | 6 7 8 7 | --------------------------------------------------------------------------------