显示标签为“C”的博文。显示所有博文
显示标签为“C”的博文。显示所有博文

2008年12月13日星期六

PostgreSql源码学习(7)

PG源码之parser(4)

前文已经大概描述了PGSQL,在解析SQL语句时所做的工作,接下来需要对在这个过程中的一些主要文件进行分析。

主要分析文件:
    // 基础节点定义
    src/include/nodes/nodes.h
    // SQL语句被解析后得到的节点结构体定义
//SQL语句经过parser解析后都先对应该该文件的一个struct
    src/include/nodes/parsenodes.h
    // List定义
    src/include/nodes/pg_list.h
    // List实现
    src/backend/nodes/list.c   
    // utility Stmt运行文件
    src/backend/tcop/utility.c
    // SQL analyze and rewrite
    src/backend/parser/analyze.c

PostgreSQL用一种非常简单的形式实现了类似C++的继承,请看nodes.h :
typedef struct Node
{
  NodeTag  type;
} Node;
然后请看:parsenodes.h
假设有一个create table的sql:
create table test (name varchar(100, pass varchar(100));
将会被解析到如下structure:
typedef struct CreateStmt
{
  NodeTag  type;
  RangeVar   *relation;  /* 关系名*/
  List    *tableElts;  /* 列定义*/
  List    *inhRelations; /*表继承关系(PG里的继承) */
  List    *constraints; /* 约束条件 */
  List    *options;  /*其他的可选条件*/
  OnCommitAction oncommit; /* 提交时做的事情 */
  char    *tablespacename; /* 表空间*/
} CreateStmt;

以下是select语句对应的结构体:
typedef struct SelectStmt
{
NodeTag type;

/*
* These fields are used only in "leaf" SelectStmts.
*/
List *distinctClause; /* 处理distinct关键字 */
IntoClause *intoClause; /*处理select into /create table tbn as select ... */
List *targetList; /* 列*/
List *fromClause; /* from 子句*/
Node *whereClause; /* WHERE条件子句 */
List *groupClause; /* GROUP BY子句*/
Node *havingClause; /* HAVING 子句 */

/*
* In a "leaf" node representing a VALUES list, the above fields are all
* null, and instead this field is set. Note that the elements of the
* sublists are just expressions, without ResTarget decoration. Also note
* that a list element can be DEFAULT (represented as a SetToDefault
* node), regardless of the context of the VALUES list. It's up to parse
* analysis to reject that where not valid.(这段话的意思是valuesLists只在叶查询节点里出 * 现,就是最内层的子查询)
*/
List *valuesLists; /*表达式值列表*/

/*
* These fields are used in both "leaf" SelectStmts and upper-level
* SelectStmts.
*/
List *sortClause; /* 排序*/
Node *limitOffset; /* 从第几个开始limit子句的第一个参数 */
Node *limitCount; /* 到哪里结束limit子句的第二个参数*/
List *lockingClause; /* FOR UPDATE (list of LockingClause's) */

/*
* These fields are used only in upper-level SelectStmts.
*/
SetOperation op; /*set操作的类型*/
bool all; /*因该是处理any关键字的*/
struct SelectStmt *larg; /* left child */
struct SelectStmt *rarg; /* right child */
/* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;

这上面是两个对应于create语句和select语句的结构体,对应于其他语句的结构体,也都可以在这个文件里找到,这两个结构体里面各个成员变量的含义我已经做了注释。这两个struct的第一个元素都是type(NodeTag)。PG的很多struct的第一个元素都是NodeTag,这样在函数中传递指针变量时,可以很简单的把参数设置成:Node*。说简单点,其实有点像是所有的struct都继承了Node这个struct。

在nodes.h中有每个Node的值的定义,比如:上面说的CreateStmt的type的值就是:T_CreateStmt。其他的每个node的定义也可以在这个文件里找到。

接下来需要看看的是链表,链表在pg_list.h中有定义:
typedef struct List
{
  NodeTag  type;   /* T_List, T_IntList, or T_OidList */
  int   length;
  ListCell   *head;
  ListCell   *tail;
} List;
可以看到,List的定义是基于基类Node来进行的。
常用的List操作函数有:
//取List第一个元素
ListCell *y = list_head(List *l);
//得到List的元素个数
list_length(List *l);
// 遍历List
foreach(cell, l)
{
 …
}
其他的很多函数具体可以参考pg_list.h和list.c

2008年11月16日星期日

PostgreSql源码学习(6)

PG源码之parser(3)

以下是几个小例子(有自己写的,有查资料得到的):
select ename from emp e
树的顶端节点是 SelectStmt 节点,没有查询条件,所以没有别的节点。

select ename from emp where empno>2000

树的顶端节点是 SelectStmt 节点,由于有范围查询条件,使用SQL 查询的 where 子句构建的操作符树,这个操作符树附加到了 SelectStmt 节点的字段 qual 上。为在查询里出现的常量(2000)条款创建一个 Const 节点(makeintconst),存放常量值。

select e.ename, d.dname
from EMP e, dept d
where e.empno > 2000 and e.deptno = d.deptno;


树的顶端节点是 SelectStmt 节点。对每个在 SQL 查询的 from子句里出现的元素创建一个 RangeVar 节点,存放 alias(别名)的名称和一个指向一个存放关系名称的 RelExpr 节点的指针。所有 RangeVar 节点都收集到一个列表里,然后附加到 SelectStmt 节点的 fromClause 字段上。

对于 SQL 查询里面的 select列表 里出现的每个元素都创建一个 ResTarget 节点,存放一个指向 Attr 节点的指针。Attr 节点存放元素的关系名称和一个指向存放着字段名称的 Value 节点的指针。所有 ResTarget 节点都收集到一个列表里,然后链接到 SelectStmt 节点的 targetList 字段里。

SQL 查询的 where 子句构建的操作符树,这个操作符树附加到了 SelectStmt 节点的字段 qual 上。操作符树的顶端节点是一个代表 AND 操作的 A_Expr 节点。这个节点有两个后继节点, lexpr 和 rexpr 分别指向两个子树。附加到 lexpr 的子树代表条件 empno > 2000 而附加到 rexpr 的子树代表e.deptno = d.deptno。为每个字段创建一个 Attr 节点,存放关系名和一个指向存放着字段名的 Value 节点的指针。为在查询里出现的常量条款创建一个 Const 节点,存放常量值。

PostgreSql源码学习(5)

PG源码之parser(2)

上文大概描述了pgsql处理查询的第一步,下面我将分析parser部分的原代码,(src/backend/parser里的源代码),可能需要写的东西要比较多一点。
1 scan.l
这个文件包含了几个头文件,其中postgres.h主要是一些在后台进程中经常用到的常量以及自定义变量(这个以后再说);gramparse.h用于避免使用bison做语法分析时的缺省模式(bison使用行列号来标示单词);keywords.h主要是跟关键字相关的内容(要区分开非关键字0,列名关键字1,类型/函数名关键字2,保留关键字3,ScanKeyword结构体就是用于区分关键字的,实际上是一个关键字符号表);scanup.h是与扫描相关的代码(还没弄的太懂);pg_wchar则引入对多字节字符的支持(比如汉语)。
以下是对一些成员变量的解释:
fprintf用于打印出错误。
backslash_quote好像是用于处理跟GNU工具相关的内容。(但是我不明白具体是做什么的)
literalbuf用于在满足多个规则时聚合相应的字面量。
使用startlit重置buffer,使用addlit来添加字符。buffer用palloc来分配,随着每次解析循环重新开始和刷新buffer。
接下来的这部分主要是处理各种情况,比如双引号,单引号(字符串中的),美元符号等等。
从331(8.3.1版本中)行开始就是对各种情况的处理。
从764行开始是几个函数,lexer_errposition用于确定错误的单词位置,yyerror用于报错,scanner_init在实际的解析开始之间做用于分配内存,scanner_finish用于释放内存,addlit来添加字符,addlitchar用于给传入的字符串的长度加1,litbufdup用于对缓冲区重写,unescape_single_char用于检测escape表达式中的特殊字符(like语句的正则表达式部分),check_string_escape_warning和check_escape_warning与escape语句表达式的错误相关(具体是处理什么我没太看明白。)。

2 gram.y
第81行的指针变量parsetree实际上就是gram的返回结果,这个变量在parser.c中定义,这样就可以返回一个解析好的语法树。从151到358行是声明针对各种情况的解析函数。从369行到453行是SQL关键字。从465行到503行是操作符的优先级定义。从514开始到9433行是各种语法定义,包括create/alter/drop role,create/alter/drop user,create/alter/drop group,create schema等等。
makeColumnRef函数的作用不明,欢迎高手指教。
makeTypeCast函数用于SQL数据类型转换。
make...Const这几个函数似乎是用于生成常量节点。
makeOverlaps生成函数调用节点。
check_qualified_name和check_func_name函数的作用不是很清楚。
extractArgTypes函数用于在给定一系列函数参数名的情况下,提取参数的类型。(好像是这样子,不是很确定)
findLeftmostSelect是获取最左端的select语句。
insertSelectOptions是用于处理如下语句:insert into tablename select * from another_tablename。
makeSetOp的作用也不是很明白,似乎是和SET语句的选项有关系。
SystemFuncName用于返回系统函数名(pg_catalog里定义)。SystemTypeName用于返回系统type名。
parser_init用于初始化。
doNegate和doNegateFloat用于处理负数的情况。
makeXmlExpr是用于处理XML表达式和XML函数。

2008年10月20日星期一

PostgreSql源码学习(4)

PG源码之parser(1)

解析sql语句是数据库服务器需要做的第一步工作。通常来说首先需要建立数据库链接,而建立数据库链接的程序可能是psql,jdbc,odbc等等。解析阶段主要是检查应用程序提交的sql的正确性以及创建查询语法树。接下来才是查询重写和执行计划优化,最后才是执行相应的查询。先分析查询解析器的原因主要是它是处理查询的第一步,必须对它了解清楚。还有一点小小的私心,据我所知,parser这部分的内容是当年由两个加大伯克利分销的中国学生完成的(用伟光正的话说是奠定了坚实的基础,当然肯定改进了不少)。

pgsql始终是以一用户一进程的c/s模式来实现建立链接。pgsql的开发者们各处的解释是:”由于我们并不清楚需要建立多少个链接,所以我们必须使用一个管理者进程来为每一个链接请求创建一个新的服务器进程。“这种方式很类似于ORACLE的专用服务器模式,似乎对WEB环境下的应用支持的不是很好,而且由于linux/unix本身的进程开销不大,所以在*nix上使用似乎比在windows上使用pgsql更好。当然在windows上pgsql始终是一个进程,并且使用线程来处理各个问题,性能如何没有详细的测试报告。所谓的管理者进程在pg7的时候是指postmaster进程,这个进程会在指定端口监听数据库链接请求。在pg8以后这个进程是postgres进程,在*nix上通常是:/urs/local/pgsql/bin/postgres -D DATA DIR。比如(在fedora上使用ps -aef |grep postgres):

postgres 4162 1 0 13:44 ? 00:00:00 /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data
postgres 4164 4162 0 13:44 ? 00:00:00 postgres: writer process
postgres 4165 4162 0 13:44 ? 00:00:00 postgres: wal writer process
postgres 4166 4162 0 13:44 ? 00:00:00 postgres: autovacuum launcher process
postgres 4167 4162 0 13:44 ? 00:00:00 postgres: stats collector process
postgres 5269 4162 0 14:04 ? 00:00:00 postgres: fy fangyuan [local] idle

可以看到实际上第一个进程是其他所有进程的父进程,因此这个进程就是管理者进程。每一个postgres进程之间使用共享内存和信号量来保证数据一致性(这个方法有点像ORACLE的SGA,只是SGA里有共享池用于存放数据,而PG只有每个进程的私有数据)。

解析器(parser)首先检查查询语句(实际上是一个ASCII字符串)的语法正确性。如果语法是正确的则会建立一个parse tree,否则就返回语法错误。在这一步里,pgsql的开发者们使用lex和yacc来进行语法解析。文件scan.l(src/bankend/parser目录下)主要用于识别相应的标示,比如sql关键字,操作符,PLSQL关键字等等。lex会为每一个关键字或者标示符创建一个token并且返回给parser。gram.y里定义来parser并且包含了一系列的语法规则和不符合/符合规则就会触发的动作(可以用来给出语法错误或者建立语法树)。在编译的时候scan.l会被编译为scan.c而gram.y则产生gram.c文件,最终被编译入postgres程序中。

由于本文并不是对lex以及yacc的详细介绍,在此不再详述。如果有兴趣的话,可以参考《lex和yacc(第二版)》(机械工业出版社,翻译的老美的书,质量很高,并且专门有一章来讲解析sql),另外IBM的网站上也有好些文章介绍这两个工具,可以搜来看看。要理解parser是如何工作的必须对这两个工具有深入的认识,否则根本无法理解parser部分的源码。另外lex和yacc也是老美讲授编译原理的时候必定会讲到的工具,因此花一点时间来学习这两个工具还是物超所值。

parser还没有写完,下次接着写。
fanngyuan于2008/10/19 15:12

2008年9月10日星期三

PostgreSql源码学习(3)

PostgreSQL的Backend目录
作者:Bruce Momjian

bootstrap - 通过initdb创建最初的数据库模板
几乎PostgreSQL的每一个操作都需要存取系统表,那么如何创建这些系统表呢?不能以通常的方式创建这些系统表并向其中插入数据,因为表的创建和插入要求系统表已经存在。这一部分代码的目的就是使用一种仅仅在bootstrap过程中使用的特殊方法来直接建立系统表

main - 将控制转到postmaster或postgres
检查进程名(argv[0])和各种标志, 然后将控制转到postmaster或postgres

postmaster - 控制postgres服务器启动/终止
创建共享内存,然后进入一个循环等待连接请求。当一个连接请求到达时,启动一个postgres后台服务进程,将连接转给它

libpq - 后台服务器libpq库函数
处理与客户进程间的通讯

tcop - 将请求分派到合适的模块
这是postgres后台服务进程的主要处理部分, 它调用parser, optimizer, executor, 和commands中的函数

parser - 将SQL查询转化为查询树
将来自libpq的SQL查询转换为命令形式的结构供optimizer/executor或commands使用.首先对SQL语句进行词法分析,转换为关键字,标识符和常量,然后进行语法分析。语法分析生成命令形式的结构来表示查询的组成。然后这个命令形式的结构被分离、检查和传送给commands中的处理函数,或者转换为结点链表供optimizer和executor处理

optimizer - 创建查询路径和查询计划
使用parser的输出来为executor生成优化了的查询计划.

optimizer/path - 使用parser的输出创建查询路径
它使用parser的输出生成所有可能的执行方法,它检查表的连接顺序,where子句限制和optimizer的表统计信息来评估每一个可能的执行方法,并赋予每个方法一个代价

optimizer/geqo - 遗传算法查询优化
optimizer/path 对所有连接表的方法进行了评估。但是当表的数目变得很大时,检测测的数目也会变得很大。遗传算法查询优化对每一个表进行考虑,然后计算出最优的顺序来执行连接。如果只有几个表,这种方法花费较长的时间,但对于大量的表,这种方法就比较快了。系统有一个选项用于控制何时使用这个功能

optimizer/plan - 优化path输出
为optimizer/path的输出选择代价最小的路径并创建一个执行计划

optimizer/prep - 处理特殊的查询计划
对特殊的查询计划进行处理

optimizer/util - 优化器支持函数
供优化器其他部分使用的函数

executor - 执行来自optimizer的复杂的节点形式的查询计划
处理select, insert, update,和delete语句. 处理这些语句的操作包括堆扫描、索引扫描、排序、连接表、分组、计算集函数和唯一性处理

commands - 不需要executor执行的命令
执行不需要复杂处理的SQL命令.包括vacuum, copy, alter, create table, create type以及许多其他未能例举的命令。调用这一部分代码时使用由parser生成的结构.大多数的函数先做一些处理,然后就调用catalog目录下的一些低层函数来做实际的工作

catalog - 系统目录操作
包含用于操作系统表和系统目录的函数.表、索引、过程、运算符、类型、和集函数的创建和操纵函数在这里可以找到。它们都是低层的函数,通常由上层将用户请求格式化为预定义格式的函数调用

storage - 管理各种类型的存储系统
支持服务器以统一的方式存取资源

storage/buffer - 共享缓冲区管理
storage/file - 文件管理
storage/ipc - 信号灯和共享内存
storage/large_object - 大对象
storage/lmgr - 锁管理器
storage/page - 页管理器
storage/smgr - 存储/磁盘管理器

access - 各种存取方法
支持堆, 索引, 和事务对数据的存取

access/common - 公共存取函数
access/gist - 可自定义的存取方法
access/hash - hash
access/heap - 堆用于存取表
access/index - 所有索引类型都使用
access/nbtree - Lehman and Yao的btree管理算法
access/rtree - 用于索引2维数据
access/transam - 事务管理器(BEGIN/ABORT/COMMIT)

nodes - 创建/操纵节点和链表
PostgreSQL将SQL查询存储到称为节点的结构中。节点是通用的容器,它包含一个类型字段和一个与类型有关的数据字段.节点通常串成链表。

链表包含一个元素和一个next指针.节点和链表广泛的用于parser,optimizer,和executor中用于存储请求和数据

utils - 支持程序
utils/adt - 内建数据类型
包含所有PostgreSQL自带数据类型

utils/cache - system/relation/function高速缓存
PostgreSQL 支持自定义数据类型,因此没有数据类型是固化在核心当中的。当服务器需要查找一个数据类型时它就到系统表中去找。由于系统表频繁使用,所以设立一个高速缓存来加速查找。系统中包括一个系统关系cache, 一个函数/运算符cache和一个关系信息cache. 关系信息cache中包含所有最近访问过的表的信息而不仅仅包含系统表的信息

utils/error - 错误报告
向前台报告后台错误

utils/fmgr - 函数管理器
处理动态加载函数的调用和在系统表中定义的函数的调用

utils/hash - 内部使用的hash程序
用于cache和内存管理器以便快速查找动态存储的数据结构

utils/init - 各种初始化
utils/misc - 未归类的东东
utils/mmgr - 内存管理器(进程私有内存)
PostgreSQL在显式的内存上下文中分配内存. 上下文可以是语句级、事务级或永久/全局级的.这样,后台服务器可以很容易地在一个语句或者事务结束的时候释放内存

utils/sort - 排序
在内存中或者使用磁盘文件排序

utils/time - 事务时间限定
检查元组是否仍然有效,还是属于未提交事务或者已被新行替代

include - 包含文件
每个子系统有一个目录

lib - 支持库
几个通用的程序

regex - 正规表达式库
用于后台服务器的正规表达式处理如'~'.

rewrite - 规则系统
完成规则系统的处理

tioga - 未用

上面是翻译的PG的文档,原文在~/Postgresql/source/src/tools/backend/index.html

组成postgresql后台进程的源代码都在~/Postgresql/source/src/backend目录下,上面这份文档中的小节标题就是子目录名称。面对着这份文档,该从哪里下手呢?我觉得所有服务器源代码大致可以分为四个组成部分:工具部分,查询处理部分,事务存储管理部分,系统初始化部分。工具部分基本集中于utils目录下,对于新手,可以由此入手感觉一下,例如分析分析mmgr的对外接口和内部实现算法。查询部分主要是 tcop/rewrite/parser/optimizer/executor/commands等等。我打算从事务存储管理部分入手分析,主要的代码集中在storage/access之下。系统初始化部分最好到最后在分析。上面讲的还只是后台服务器,其他还有一些东东如odbc/jdbc、各种语言接口、管理工具如pgAdmin/pgAccess等等

2008年9月8日星期一

PostgreSql源码学习(2)

1 将PG源码编译成附加有调试信息的二进制文件(仅用于linux/unix)

进入解压所得的源码文件夹,运行./configure.然后修改GLOBALmakefile中的CC=gcc选项为:CC=gcc-g,这样在编译的时候gcc就会加入调试信息,然后sudo make install,再按照install文档所说的方法,一步一步安装PG。

2 调试initdb,pg_ctl等程序

由于initdb和pg_ctl等程序并不是后台或者前台一直运行的进程,因此可以使用gdb /usr/local/pgsql/bin/initdb来跟踪调试。

3 调试postgres,psql等程序
由于postgres,psql是后台或者前台的运行程序,因此不可以使用gdb /usr/local/pgsql/bin/postgres 来直接调试,而需要把gdb附加于这几个程序之上来跟踪调试。做法如下:
首先,运行ps -aef | grep postgres
这时会列出很多包含postgres的进程,选择postgres -D 数据所在的目录的那个进程,记录下pid
然后,运行gdb /usr/local/pgsql/bin/postgres pid,这样就可以跟踪调试运行中的postgres程序。跟踪psql的方法与此类似,只是需要事先启动一个psql。

具体的要跟踪哪段代码可以在eclipse里的源代码里寻找,找到相应的代码后设置断点跟踪。(我不太喜欢用vim和emacs来浏览源码,毕竟eclipse提供了很多方便的功能。)当然在ECLIPSE里也可以调试跟踪PG源码。

这次先写到这里,下次准备介绍后台进程。

2008年8月28日星期四

PostgreSql源码学习(1)

源码重建
最近刚下载了PG的源代码,准备学习一下。希望和大家共享学习的经验。

我编译的平台是fedora linux 9,如果是windows用户则有两种选择,一个是使用gnu版的工具,一个是使用VS,可以自己看看PG的说明。

首先从这里下载PG的源代码。解压缩后出现postgres...的文件夹。从终端进入文件夹后,运行./configure,生成MAKEFILE。这时如果是习惯使用EMACS工具或者VI工具的同志们就可以继续运行MAKE,MAKE INSTALL了。但是由于我们还是希望有个强大的IDE来对工程进行管理,因此我们就此打住。注:我选择eclipse CDT作为IDE,主要是因为对eclipse比较熟悉。

接着,下载eclipse CDT,解压启动后。NEW PROJECT,选择代码目录到刚刚解压的目录,选择PROJECT类型为make file project。然后一直next到finish。这样eclipse就会根据指定的makefile来导入相应的源码并且进行编译。这样就完成了导入工作。

将源代码导入eclipse CDT仅仅是为了方便大家阅读源码,毕竟eclipse提供了很多很好的IDE功能。如果需要调试源代码,则需要继续make install,然后使用gdb进行调试。我还不知道在eclipse下怎么调试pg的源代码,有谁知道请告诉我吧。