├── README.md ├── dot └── a ├── node ├── a ├── sample.node └── sample2.node ├── node2dot.c ├── pgNodeGraph └── pic ├── sample.node.jpg └── sample2.node.jpg /README.md: -------------------------------------------------------------------------------- 1 | pgNodeGraph -- a postgreSQL node tree print tool 2 | ============================================= 3 | 4 | > The output of postgreSQL node tree can be very long for the developer to investigate. pgNodeGraph tool can covert the output of the node tree into jpg formatted pictiure which is quite convenient for developing or debugging purposess. 5 | 6 | 7 | ## short tour 8 | 9 | suppose we have a table like this 10 | ``` 11 | postgres=# \d class 12 | Table "public.class" 13 | Column | Type | Modifiers 14 | -----------+-----------------------+----------- 15 | classno | character varying(10) | 16 | classname | character varying(10) | 17 | gno | character varying(10) | 18 | 19 | ``` 20 | by turning the following option on, the postgreSQL will print the node tree after parse and plan the query. 21 | ``` 22 | set debug_print_parse = on; 23 | set debug_print_plan = on; 24 | set debug_pretty_print = on; 25 | ``` 26 | now let's execute a statment. 27 | ``` 28 | postgres=# select * from class; 29 | ``` 30 | the sever will print the parse and plan node tree in the log. according to different type of queries, the out put usually last for hundred or thousand lines. below is an example. 31 | 32 | ![image_1at06c8lbe4r1k4lrcn1o5hll113.png-32.9kB][1] 33 | 34 | the node tree printed by of postgreSQL, as you have seen is hard for the developer or user to see it as a whole. so, is there a way to print the node tree as a picture format? the answer is yes. now, let me show you how to use pgNodeGraph to convert the node tree to a picture format. 35 | 36 | 1. copy and paste the node tree in text form. 37 | 2. put it in node dir 38 | 3. run `$ ./pgNodeGraph ` 39 | ``` 40 | $ ./pgNodeGraph 41 | processing file sample.node ...done 42 | ``` 43 | after a short wait, the node tree in jpg format is generated under the pic directory. 44 | 45 | QueryNodeTree: 46 | ![image_1at05s7f0n6l6i9blt0918kp9.png-421.8kB][2] 47 | 48 | PlanNodeTree: 49 | ![image_1at0639g0ftv1fulrt1ojs105am.png-404.8kB][3] 50 | 51 | 52 | ## A complex example 53 | Now, let's try a much more complex query to see whether pgNodeGraph will work well. 54 | 55 | ``` 56 | select classno, classname, avg(score)::numeric(5,1) as 平均分 57 | from sc, (select * from class where class.gno='一年级') as sub 58 | where 59 | sc.sno in (select sno from student where student.classno=sub.classno) 60 | and 61 | sc.cno in (select course.cno from course where course.cname='数学') 62 | group by classno, classname 63 | having avg(score)>60 64 | order by 平均分 desc; 65 | ``` 66 | following the same procedure in above, we generate the result as below. 67 | QueryNodeTree: 68 | ![image_1at05cvlss4p17abdpk6su88kc.png-2852.3kB][4] 69 | PlanNodeTree: 70 | ![image_1at074oh613aeirvkbifu0gam1g.png-5300.1kB][5] 71 | 72 | 73 | # hack into the code 74 | 75 | pgNodeGraph tool first convert the node tree printed by postgreSQL to dot format. 76 | 77 | the node tree: 78 | ``` 79 | {Node1 80 | :elem1 81 | :elem2 82 | :elem3 83 | {Node2 84 | :elem1 85 | :elem2 86 | :elem3 87 | {Node3 88 | :elem1 89 | :elem2 90 | :elem3 91 | } 92 | :elem4 93 | :elem5 94 | } 95 | :elem4 96 | {Node4 97 | :elem1 98 | :elem2 99 | :elem3 100 | } 101 | :elem5 102 | :elem6 103 | } 104 | 105 | 106 | ``` 107 | the corresponding dot format: 108 | ``` 109 | digraph Query { 110 | size="100000,100000"; 111 | rankdir=LR; 112 | node [shape=record]; 113 | node1 [shape=record, label=" Node1 | elem1 | elem2 | elem3 | elem4 | elem5 | elem6 "]; 114 | node2 [shape=record, label=" Node2 | elem1 | elem2 | elem3 | elem4 | elem5 "]; 115 | node3 [shape=record, label=" Node3 | elem1 | elem2 | elem3 "]; 116 | node4 [shape=record, label=" Node4 | elem1 | elem2 | elem3 "]; 117 | node1:f3 -> node2:f0 118 | node1:f4 -> node4:f0 119 | node2:f3 -> node3:f0 120 | 121 | } 122 | 123 | 124 | ``` 125 | the by using the `dot` tool to genereate the picture. 126 | so, you must have `dot` or `graphvis` tool installed on your system. 127 | 128 | 129 | ![image_1at08ct2v1f1q7mcdrh1g861qi12f.png-47.7kB][6] 130 | 131 | 132 | 133 | [1]: http://static.zybuluo.com/shenyuflying/3pnykidt3h56e4n5ukn6e9bz/image_1at06c8lbe4r1k4lrcn1o5hll113.png 134 | [2]: http://static.zybuluo.com/shenyuflying/5hpdpk2pagv927zqtajj1nu3/image_1at05s7f0n6l6i9blt0918kp9.png 135 | [3]: http://static.zybuluo.com/shenyuflying/6ufsqp1cmsirhumjbt61r1an/image_1at0639g0ftv1fulrt1ojs105am.png 136 | [4]: http://static.zybuluo.com/shenyuflying/cduxtumwqj7nuvvt93en76dk/image_1at05cvlss4p17abdpk6su88kc.png 137 | [5]: http://static.zybuluo.com/shenyuflying/2ktr9kohxtguhc1w5o0o8qc5/image_1at074oh613aeirvkbifu0gam1g.png 138 | [6]: http://static.zybuluo.com/shenyuflying/iht0aj2dj7zyoj1l2wt3isfb/image_1at08ct2v1f1q7mcdrh1g861qi12f.png 139 | -------------------------------------------------------------------------------- /dot/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shenyuflying/pgNodeGraph/b67ac22f04af52ab7a4bffbeff4c15ec592b7cd4/dot/a -------------------------------------------------------------------------------- /node/a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shenyuflying/pgNodeGraph/b67ac22f04af52ab7a4bffbeff4c15ec592b7cd4/node/a -------------------------------------------------------------------------------- /node/sample.node: -------------------------------------------------------------------------------- 1 | {Node1 2 | :elem1 3 | :elem2 4 | :elem3 5 | {Node2 6 | :elem1 7 | :elem2 8 | :elem3 9 | {Node3 10 | :elem1 11 | :elem2 12 | :elem3 13 | } 14 | :elem4 15 | :elem5 16 | } 17 | :elem4 18 | {Node4 19 | :elem1 20 | :elem2 21 | :elem3 22 | } 23 | :elem5 24 | :elem6 25 | } 26 | -------------------------------------------------------------------------------- /node/sample2.node: -------------------------------------------------------------------------------- 1 | {Node1 :elem1 :elem2 :elem3 {Node2 :elem1 :elem2 :elem3 {Node3 :elem1 :elem2 :elem3 } :elem4 :elem5 } :elem4 {Node4 :elem1 :elem2 :elem3 } :elem5 :elem6} 2 | -------------------------------------------------------------------------------- /node2dot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * node2dot 3 | * 4 | * Convert a node tree into dot format 5 | * 6 | * yshen 2016 7 | * 8 | * initial version 2016-9-19 12:00 9 | * last update 2016-9-19 add parmeter --skip-empty --color 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define LENGTH 1024 19 | #define bool char 20 | #define true 1 21 | #define false 0 22 | 23 | typedef struct Elem { 24 | char name[LENGTH]; 25 | } Elem; 26 | 27 | typedef struct Node { 28 | char name[LENGTH]; 29 | Elem elems[LENGTH]; 30 | char links[LENGTH][LENGTH]; 31 | int links_count; 32 | char * color; 33 | } Node; 34 | 35 | /* 36 | * node_num and elem_num 37 | */ 38 | int node_num = 0; 39 | int node_cnt = 0; 40 | int elem_num = 0; 41 | 42 | #define new(x) (++x) 43 | #define set(var,value) (var=value) 44 | #define get(x) (x) 45 | 46 | 47 | /* 48 | * a mini-stack 49 | */ 50 | 51 | int *stack = NULL; 52 | int top = 0; 53 | 54 | int push(int var) 55 | { 56 | stack[top++] = var; 57 | } 58 | int pop() 59 | { 60 | return stack[--top]; 61 | } 62 | 63 | /* 64 | * globals 65 | */ 66 | Node nodes[LENGTH]; 67 | int add_node(int num, char *name); 68 | int add_item(int node_n, int elem_n, char *name); 69 | int add_link(int parent_node_num, int parent_elem_num, int child_node_num); 70 | int print_header(void); 71 | int print_footer(void); 72 | int print_body(void); 73 | char * get_one_name(); 74 | 75 | char *progname = "node2dot"; 76 | 77 | /* 78 | * options 79 | */ 80 | 81 | 82 | bool is_skip_empty = false; 83 | bool is_color = false; 84 | char * skip_node_name = NULL; 85 | void usage() 86 | { 87 | printf("\n\nConvert node tree of postgres to dot format\n" 88 | " yshen 2016\n\n\n" 89 | "Usage:%s [options] fileout\n" 90 | "Valid Options:\n" 91 | "--skip-empty do not print element if is 0 or null or false\n" 92 | "--skip-node nodename do not print node named nodename\n" 93 | "--color enable color output\n" 94 | "--help show this help message\n" 95 | , progname); 96 | } 97 | 98 | 99 | 100 | int main(int argc, char *argv[]) 101 | { 102 | 103 | char buffer[LENGTH]; 104 | 105 | int parent_node_num; 106 | int parent_elem_num; 107 | int child_node_num; 108 | 109 | int c; 110 | int digit_optind = 0; 111 | 112 | /* 113 | * level indicates if we are in a struct context or not 114 | * if we are inside a struct level > 0 else level <= 0 115 | * if we are outside a struct, keep on consuming stdin 116 | * until we found the '{' marking as the beginning of 117 | * a struct 118 | * 119 | * we add this because 120 | * 1. we can parse more than one node tree in a file 121 | * 2. if there are anything else such as LOG: statement 122 | * we can safely ignore then. in the old days, those 123 | * thing have to be removed by hand. 124 | * */ 125 | int level = 0; 126 | 127 | 128 | while (1) 129 | { 130 | int this_option_optind = optind ? optind : 1; 131 | int option_index = 0; 132 | static struct option long_options[] = 133 | { 134 | /* name has_arg flag val*/ 135 | {"help", no_argument, 0, 0 }, 136 | {"skip-empty",no_argument, 0, 0 }, 137 | {"color", no_argument, 0, 0 }, 138 | {"skip-node", required_argument, 0, 0 }, 139 | {0, 0, 0, 0 } 140 | }; 141 | 142 | c = getopt_long(argc, argv, "h", 143 | long_options, &option_index); 144 | 145 | if (c == -1) 146 | break; 147 | 148 | 149 | switch (c) 150 | { 151 | case 0: 152 | 153 | if (strcmp("help",long_options[option_index].name) == 0) 154 | { 155 | usage(); 156 | exit(0); 157 | } 158 | 159 | if (strcmp("skip-empty",long_options[option_index].name) == 0) 160 | { 161 | is_skip_empty = 1; 162 | 163 | fprintf(stderr,"%s:skip empty elements\n",progname); 164 | } 165 | if (strcmp("color",long_options[option_index].name) == 0) 166 | { 167 | is_color = true; 168 | fprintf(stderr,"%s:color output\n",progname); 169 | } 170 | if (strcmp("skip-node",long_options[option_index].name) == 0) 171 | { 172 | skip_node_name = strdup(optarg); 173 | fprintf(stderr,"%s:skip mode named:%s\n",progname,skip_node_name); 174 | } 175 | break; 176 | 177 | case 'h': 178 | usage(); 179 | exit(0); 180 | break; 181 | 182 | case '?': 183 | usage(); 184 | exit(0); 185 | break; 186 | 187 | default: 188 | printf("?? getopt returned character code 0%o ??\n", c); 189 | 190 | } 191 | #if 0 192 | if (optind < argc) 193 | { 194 | printf("non-option ARGV-elements: "); 195 | while (optind < argc) 196 | printf("%s ", argv[optind++]); 197 | printf("\n"); 198 | } 199 | #endif 200 | } 201 | 202 | 203 | /* 204 | * in previous we alloc stack on stack which raise an link error 205 | * relocation truncated to fit: R_X86_64_PC32 against 206 | * symbol `stack' defined in COMMON section 207 | * to fix this alloc it in heap using malloc 208 | */ 209 | stack = (int*)malloc(sizeof(int)*LENGTH); 210 | 211 | print_header(); 212 | while (EOF != (c = getc(stdin))) 213 | { 214 | 215 | char * name = NULL; 216 | switch(c) 217 | { 218 | case '{': /* a new node*/ 219 | level++; 220 | parent_node_num = get(node_num); 221 | parent_elem_num = get(elem_num); 222 | 223 | push(parent_node_num); /* push node_num first*/ 224 | push(parent_elem_num); /* push elem_num second */ 225 | 226 | set(elem_num,0); /* the new node elem_num start at 0 */ 227 | 228 | add_node(new(node_cnt),get_one_name()); /* node_cnt always ++*/ 229 | set(node_num,get(node_cnt)); /* set the node_num */ 230 | child_node_num = get(node_num); 231 | 232 | if (get(node_cnt) != 0) 233 | { 234 | add_link(parent_node_num, 235 | parent_elem_num, 236 | child_node_num); /* if on sub level add links */ 237 | } 238 | break; 239 | case '}': /* end of the node */ 240 | 241 | /* we are not in a struct */ 242 | if (level <= 0) 243 | continue; 244 | 245 | /* restore the parent */ 246 | set(elem_num,pop()); /* pop elem_num first */ 247 | set(node_num,pop()); /* pop node_num second */ 248 | /* we are out if the stack */ 249 | level--; 250 | break; 251 | case ':': /* a new item */ 252 | 253 | /* we are not in a struct */ 254 | if (level <= 0) 255 | continue; 256 | 257 | name = get_one_name(); 258 | 259 | if (name != NULL) 260 | add_item(get(node_num), 261 | new(elem_num), 262 | name); 263 | break; 264 | defalut: 265 | 266 | /* if we are not in struct consume input until we meet one */ 267 | if (level <= 0) 268 | { 269 | while(EOF!= (c = getc(stdin)) && c !='{' ) 270 | ; 271 | /* put it back */ 272 | ungetc('{',stdin); 273 | } 274 | break; 275 | } 276 | 277 | } 278 | 279 | print_body(); 280 | print_footer(); 281 | 282 | } 283 | 284 | int print_header(void) 285 | { 286 | 287 | printf("digraph Query {\n"); 288 | printf("size=\"100000,100000\";\n"); 289 | printf("rankdir=LR;\n"); 290 | printf("node [shape=record];\n"); 291 | } 292 | 293 | 294 | 295 | int add_node(int num, char *name) 296 | { 297 | int i,j; 298 | #ifdef DEBUG 299 | printf("add_node(%s)\n", name); 300 | #endif 301 | memcpy(nodes[num].name, name, LENGTH); 302 | 303 | if (!is_color) 304 | { 305 | nodes[num].color = "black"; 306 | } 307 | else 308 | { 309 | if (strstr(name,"QUERY")) 310 | { 311 | nodes[num].color = "red"; 312 | } 313 | else if (strstr(name,"RTE")) 314 | { 315 | nodes[num].color = "blue"; 316 | } 317 | else if (strstr(name,"TARGETENTRY")) 318 | { 319 | nodes[num].color = "orange"; 320 | } 321 | else if (strstr(name,"RELOPTINFO")) 322 | { 323 | nodes[num].color = "green"; 324 | } 325 | else 326 | { 327 | nodes[num].color = "black"; 328 | } 329 | } 330 | free(name); 331 | for (i = 0; i < LENGTH; i++) 332 | { 333 | nodes[num].links_count = 0; 334 | 335 | /* clear all the item name */ 336 | for (j = 0; j < LENGTH; j++) 337 | memset(nodes[num].elems[j].name,0,LENGTH); 338 | } 339 | 340 | } 341 | 342 | 343 | int print_footer(void) 344 | { 345 | printf("\n}"); 346 | } 347 | 348 | int add_item(int node_n, int elem_n, char *name) 349 | { 350 | #ifdef DEBUG 351 | printf("add_item(node_n=%d, elem_n=%d, name=%s)\n", node_n, elem_n, name); 352 | #endif 353 | memcpy(nodes[node_n].elems[elem_n].name, name, LENGTH); 354 | free(name); 355 | } 356 | 357 | 358 | /* 359 | * get one node or element name from stdin 360 | * if name is ok return the name 361 | * otherwise return NULL 362 | */ 363 | char * get_one_name() 364 | { 365 | char * buffer = (char*)malloc(LENGTH); 366 | int i=0; 367 | int c=0; 368 | int len=0; 369 | bool found_zero=false, found_non_zero=false; 370 | char * pos; 371 | 372 | memset(buffer,0,LENGTH); 373 | while((c=getc(stdin)) != ':' && c!='{' && c!='}') 374 | buffer[i++]=c; 375 | 376 | /* push back the token */ 377 | ungetc(c,stdin); 378 | 379 | 380 | len = strlen(buffer); 381 | 382 | /* remove any illeagal char of dot format */ 383 | 384 | for (i = 0; i < len; i++) 385 | { 386 | if (buffer[i]=='"') 387 | buffer[i]=' '; 388 | 389 | if (buffer[i]=='<' || 390 | buffer[i]=='>') 391 | buffer[i]='-'; 392 | 393 | if (buffer[i] == '0') 394 | found_zero = true; 395 | 396 | if (buffer[i] > '1' && buffer[i] < '9') 397 | found_non_zero = true; 398 | 399 | } 400 | 401 | if ((pos = strstr(buffer,"(")) && !strstr(buffer,")")) 402 | { 403 | pos[0] = ' '; 404 | } 405 | 406 | if (is_skip_empty) 407 | { 408 | if (found_zero && !found_non_zero) 409 | { 410 | free(buffer); 411 | return NULL; 412 | } 413 | } 414 | 415 | if (is_skip_empty && strstr(buffer,"false")) 416 | { 417 | free(buffer); 418 | return NULL; 419 | } 420 | 421 | if (is_skip_empty && strstr(buffer,"--")) 422 | { 423 | free(buffer); 424 | return NULL; 425 | } 426 | 427 | return buffer; 428 | } 429 | int print_body(void) 430 | { 431 | int i, j; 432 | 433 | 434 | /* print the nodes */ 435 | for (i = 0; i <= get(node_cnt); i++) 436 | { 437 | 438 | if (strcmp(nodes[i].name,"") == 0) 439 | continue; 440 | 441 | if (skip_node_name && strstr(nodes[i].name, skip_node_name)) 442 | continue; 443 | 444 | printf("node%d [shape=record, color=%s, label=\" %s | ", 445 | i, nodes[i].color, nodes[i].name); 446 | 447 | 448 | for (j = 1; j < LENGTH; j++) 449 | { 450 | 451 | if (strlen(nodes[i].elems[j].name) != 0) 452 | printf(" %s ", j, nodes[i].elems[j].name); 453 | else 454 | break; 455 | 456 | if (strcmp(nodes[i].elems[j+1].name,"") != 0) 457 | printf("| "); 458 | } 459 | printf("\"];\n"); 460 | 461 | } 462 | 463 | /* print the links */ 464 | for (i = 0; i < get(node_cnt); i++) 465 | { 466 | 467 | if (strcmp(nodes[i].name,"") == 0) 468 | continue; 469 | 470 | if (skip_node_name && strstr(nodes[i].name, skip_node_name)) 471 | continue; 472 | 473 | for (j = 0; j < nodes[i].links_count; j++) 474 | printf("%s", nodes[i].links[j]); 475 | } 476 | 477 | 478 | } 479 | int add_link(int parent_node_num, int parent_elem_num, int child_node_num) 480 | { 481 | sprintf(nodes[parent_node_num].links[nodes[parent_node_num].links_count++], 482 | "node%d:f%d -> node%d:f%d\n", 483 | parent_node_num, 484 | parent_elem_num, 485 | child_node_num, 486 | (int)0); 487 | 488 | } 489 | -------------------------------------------------------------------------------- /pgNodeGraph: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gcc -g node2dot.c -o node2dot 4 | 5 | dir_node="./node/" 6 | dir_dot="./dot/" 7 | dir_pic="./pic/" 8 | 9 | prog_dot=`which dot` 10 | 11 | if [ "x"$prog_dot == "x" ] ; then 12 | echo "program dot not fount, install dot using 'sudo apt install graphviz'" 13 | exit 1 14 | fi 15 | 16 | for file in `ls $dir_node | grep -e "\.node$"` 17 | do 18 | if [ ! -f $dir_pic$file.jpg ] ; then 19 | echo -n "processing file $file ..." 20 | ./node2dot --skip-empty --color < $dir_node$file > $dir_dot$file.dot 2> /dev/null 21 | $prog_dot -Tjpg $dir_dot$file.dot -o $dir_pic$file.jpg 2>/dev/null 22 | echo "done" 23 | fi 24 | done 25 | 26 | rm node2dot 27 | -------------------------------------------------------------------------------- /pic/sample.node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shenyuflying/pgNodeGraph/b67ac22f04af52ab7a4bffbeff4c15ec592b7cd4/pic/sample.node.jpg -------------------------------------------------------------------------------- /pic/sample2.node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shenyuflying/pgNodeGraph/b67ac22f04af52ab7a4bffbeff4c15ec592b7cd4/pic/sample2.node.jpg --------------------------------------------------------------------------------