Postgresql源码结构
Postgresql的使用形态
Postgresql采用C/S(客户机/服务器)模式结构。应用层通过INET或者Unix Socket利用既定的协议与数据库服务器进行通信。
另外,还有一种‘Standalone Backend’使用的方式,虽然通过这种方式也可以启动服务器,但是一般只在数据库的初始化(Postgresql
的cluster的初始化,相当于其他数据库的instance的初始化)、紧急维护的时候使用,所以简单来说可以认为Postgresql是使用C/S的形
式进行访问的。
Postgresql把客户端称为前端(Frontend),把服务器端成为后端(Backend),后端有复数个进程构成,这个在后面会进行说明。
前端和后端通信的协议在Postgresql的官方文档中的《前端和后端的通信协议》一章中有详细的说明。简单来说,大体的工作模式是:
前端向后端发送查询的sql文,然后后端通过复数个报文把结果返回给前端。
由于需要进行连接的初始化、错误等各种各样处理,Postgresql的协议的处理也是相当复杂,如果要自己从头实现这些协议的处理的话
,还是相当麻烦的,所以Postgresql本身提供了C语言写的libpq这样一个协议处理库,利用这个库可以比较轻松地和后端进行通信。
Postgresql的话除了C以外,还支持Perl和PHP等其他语言,这些语言在内部也调用了libpq.
也有不使用libpq而直接与Postgresql通信的库。比较具有代表性的是Java,Postgresql的JDBC驱动是不依赖于libpq直接与Postgresql
通信的.
另外后端的话,比较核心的是进行数据库处理的数据库引擎(Database Engine)。 数据库引擎可以对用户所编写的函数进行解析和处理
,用户如果能够利用好这个功能的话,可以柔软地扩展Postgresql的功能。 比较经常使用的是存储过程(Postgresql中称为用户自定义
函数),Postgresql支持的用户定义函数的语言如下:
语言 对应的自定义函数
C C函数
sql sql 函数
类似Oracle的PL/sql的语言 PL/pgsql
Perl PL/Perl
Python PL/Python
Postgresql的话,用户可以自定义语言处理引擎。各种服务器脚本语言的解析引擎,以第三方的形式存在,主要的处理语言有Ruby、
Java以及PHP等。
Postgresql的结构
这里的话,再详细看看Postgresql的结构。 后端由几个进程构成。
Potgres(常驻进程)
管理后端的常驻进程,也称为’postmaster’。其默认监听UNIX Domain Socket和TCP/IP(Windows等,一部分的平台只监听tcp/ip)的
5432端口,等待来自前端的的连接处理。监听的端口号可以在Postgresql的设置文件postgresql.conf里面可以改。
一旦有前端连接过来,postgres会通过fork(2)生成子进程。没有Fork(2)的windows平台的话,则利用createProcess()生成新的进程。
这种情形的话,和fork(2)不同的是,父进程的数据不会被继承过来,所以需要利用共享内存把父进程的数据继承过来。
Postgres(子进程)
子进程根据pg_hba.conf定义的安全策略来判断是否允许进行连接,根据策略,会拒绝某些特定的IP及网络,或者也可以只允许某些特定
的用户或者对某些数据库进行连接。
Postgres会接受前端过来的查询,然后对数据库进行检索,最好把结果返回,有时也会对数据库进行更新。更新的数据同时还会记录在
事务日志里面(Postgresql称为WAL日志),这个主要是当停电的时候,服务器当机,重新启动的时候进行恢复处理的时候使用的。另外
,把日志归档保存起来,可在需要进行恢复的时候使用。在Postgresql 9.0以后,通过把WAL日志传送其他的postgresql,可以实时得进
行数据库复制,这就是所谓的‘数据库复制’功能。
其他的进程
Postgres之外还有一些辅助的进程。这些进程都是由常驻postgres启动的进程。
Writer process
Writer process在适当的时间点把共享内存上的缓存写往磁盘。通过这个进程,可以防止在检查点的时候(checkpoint),大量的往磁盘写
而导致性能恶化,使得服务器可以保持比较稳定的性能。Background writer起来以后就一直常驻内存,但是并非一直在工作,它会在工
作一段时间后进行休眠,休眠的时间间隔通过postgresql.conf里面的参数bgwriter_delay设置,默认是200微秒。
这个进程的另外一个重要的功能是定期执行检查点(checkpoint)。
检查点的时候,会把共享内存上的缓存内容往数据库文件写,使得内存和文件的状态一致。通过这样,可以在系统崩溃的时候可以缩短
从WAL恢复的时间,另外也可以防止WAL无限的增长。 可以通过postgresql.conf的checkpoint_segments、checkpoint_timeout指定执行
检查点的时间间隔。
WAL writer process
WAL writer process把共享内存上的WAL缓存在适当的时间点往磁盘写,通过这样,可以减轻后端进程在写自己的WAL缓存时的压力,提
高性能。另外,非同步提交设为true的时候,可以保证在一定的时间间隔内,把WAL缓存上的内容写入WAL日志文件。
Archive process
Archive process把WAL日志转移到归档日志里。如果保存了基础备份以及归档日志,即使实在磁盘完全损坏的时候,也可以回复数据库
到最新的状态。
stats collector process
统计信息的收集进程。收集好统计表的访问次数,磁盘的访问次数等信息。收集到的信息除了能被autovaccum利用,还可以给其他数据
库管理员作为数据库管理的参考信息。
Logger process
把postgresql的活动状态写到日志信息文件(并非事务日志),在指定的时间间隔里面,对日志文件进行rotate.
Autovacuum启动进程
autovacuum launcher process是依赖于postmaster间接启动vacuum进程。而其自身是不直接启动自动vacuum进程的。通过这样可以提高
系统的可靠性。
自动vacuum进程
autovacuum worker process进程实际执行vacuum的任务。有时候会同时启动多个vacuum进程。
wal sender / wal receiver
wal sender 进程和wal receiver进程是实现postgresql复制(streaming replication)的进程。Wal sender进程通过网络传送WAL日志,
而其他Postgresql实例的wal receiver进程则接收相应的日志。Wal receiver进程的宿主Postgresql(也称为Standby)接受到WAL日志
后,在自身的数据库上还原,生成一个和发送端的Postgresql(也称为Master)完全一样的数据库。
后端的处理流程
下面看看数据库引擎postgres子进程的处理概要。为了简单起见下面的说明中,把backend process简称为backend。Backend的main函数
是PostgresMain (tcop/postgres.c)。
接收前端发送过来的查询(sql文)
sql文是单纯的文字,电脑是认识不了的,所以要转换成比较容易处理的内部形式构文树parser tree,这个处理的称为构文解析。构文解
析的模块称为parser.这个阶段只能够使用文字字面上得来的信息,所以只要没语法错误之类的错误,即使是select不存在的表也不会报
错。这个阶段的构文树被称为raw parse tree. 构文处理的入口在raw_parser (parser/parser.c)。
构文树解析完以后,会转换为查询树(Query tree)。这个时候,会访问数据库,检查表是否存在,如果存在的话,则把表名转换为OID。
这个处理称为分析处理(Analyze),进行分析处理的模块是analyzer。 另外,Postgresql的代码里面提到构文树parser tree的时候,更
多的时候是指查询树Query tree。分析处理的模块的入口在parse_analyze (parser/analyze.c)
Postgresql还通过查询语句的重写实现视图(view)和规则(rule),所以需要的时候,在这个阶段会对查询语句进行重写。这个处理称为
重写(rewrite),重写的入口在QueryRewrite (rewrite/rewriteHandler.c)。
通过解析查询树,可以实际生成计划树。生成查询树的处理称为‘执行计划处理’,最关键是要生成估计能在最短的时间内完成的计划
树(plan tree)。这个步骤称为’查询优化’(不叫query optimize,而是optimize),而完成这个处理的模块称为查询优化器(不叫query
optimizer,而是optimizer,或者称为planner)。执行计划处理的入口在standard_planner (optimizer/plan/planner.c)。
按照执行计划里面的步骤可以完成查询要达到的目的。运行执行计划树里面步骤的处理称为执行处理‘execute’,完成这个处理的模块
称为执行器‘Executor’,执行器的入口地址为,ExecutorRun (executor/execMain.c)
执行结果返回给前端。
返回到步骤一重复执行。
Postgresql的源码
现在基本上理解了Postgresql的大体的结构,我们再来看看Postgresql代码的结构。 Postgresql初期的时候,大概只有20万行左右的代
码,现在已经发展到100万行了。这个量来说,没有指导读起来是极为难理解的,这里把大概的代码结构说明一下,让大家对源码的结构
有个理解。
第一级目录结构
进入Postgresql的源码目录后,第一级的结构如下表所示。在这一级里,通过执行如下命令configure;make;make install可以立即进行
简单的安装,实际上从Postgresql源码安装是极为简单的。
文件目录 说明
COPYRIGHT 版权信息
GUNMakefile 第一级目录的 Makefile
GUNMakefile.in Makefile 的雏形
HISTORY 修改历史
INSTALL 安装方法简要说明
Makefile Makefile模版
README 简单说明
aclocal.m4 config 用的文件的一部分
config/ config 用的文件的目录
configure configure 文件
configure.in configure 文件的雏形
contrib/ contribution 程序
doc/ 文档目录
src/ 源代码目录
Postgresql 的src下面有。
文件目录 说明
DEVELOPERS 面向开发人员的注视
Makefile Makefile
Makefile.global make 的设定值(从configure生成的)
Makefile.global.in Configure使用的Makefile.global的雏形
Makefile.port 平台相关的make的设定值,实际是一个到makefile/Makefile的连接. (从configure生成的)
Makefile.shlib 共享库用的Makefile
backend/ 后端的源码目录
bcc32.mak Win32 ポート用の Makefile (Borland C++ 用)
bin/ psql 等 UNIX命令的代码
include/ 头文件
interfaces/ 前端相关的库的代码
makefiles/ 平台相关的make 的设置值
nls-global.mk 信息目录用的Makefile文件的规则
pl/ 存储过程语言的代码
port/ 平台移植相关的代码
template/ 平台相关的设置值
test/ 各种测试脚本
timezone/ 时区相关代码
tools/ 各自开发工具和文档
tutorial/ 教程
win32.mak Win32 ポート用の Makefile (Visual C++ 用)
这里比较核心的是backend,bin,interface这几个目录。Backend是对应于后端,bin和interface对应于前端。
bin里面有pgsql,initdb,pg_dump等各种工具的代码。interface里面有Postgresql的C语言的库libpq,另外可以在C里嵌入sql的ECPG命令
的相关代码。
Backend目录的结构如下:
目录文件 说明
Makefile makefile
access/ 各种存储访问方法(在各个子目录下) common(共同函数)、gin (Generalized Inverted Index通用逆向索引)
gist (Generalized Search Tree通用索引)、 hash (哈希索引)、heap (heap的访问方法)、
index (通用索引函数)、 nbtree (Btree函数)、transam (事务处理)
bootstrap/ 数据库的初始化处理(initdb的时候)
catalog/ 系统目录
commands/ SELECT/INSERT/UPDATE/DELETE以为的sql文的处理
executor/ 执行器(访问的执行)
foreign/ FDW(Foreign Data Wrapper)处理
lib/ 共同函数
libpq/ 前端/后端通信处理
main/ postgres的主函数
nodes/ 构文树节点相关的处理函数
optimizer/ 优化器
parser/ sql构文解析器
port/ 平台相关的代码
postmaster/ postmaster的主函数 (常驻postgres)
replication/ streaming replication
regex/ 正则处理
rewrite/ 规则及视图相关的重写处理
snowball/ 全文检索相关(语干处理)
storage/ 共享内存、磁盘上的存储、缓存等全部一次/二次记录管理(以下的目录)buffer/(缓存管理)、 file/(文件)、
freespace/(Fee Space Map管理) ipc/(进程间通信)、large_object /(大对象的访问函数)、
lmgr/(锁管理)、page/(页面访问相关函数)、 smgr/(存储管理器)
tcop/ postgres (数据库引擎的进程)的主要部分
tsearch/ 全文检索
utils/ 各种模块(以下目录) adt/(嵌入的数据类型)、cache/(缓存管理)、 error/(错误处理)、fmgr/(函数管理)、
hash/(hash函数)、 init/(数据库初始化、postgres的初期处理)、 mb/(多字节文字处理)、
misc/(其他)、mmgr/(内存的管理函数)、 resowner/(查询处理中的数据(buffer pin及表锁)的管理)、
sort/(排序处理)、time/(事务的 MVCC 管理)
backend等的代码的头文件包含在include里面。其组织虽然与backend的目录结构类似,但是并非完全相同,基本上来说下一级的子目录
不再设下一级目录。例如backend的目录下面有utils这个目录,而util下面还有adt这个子目录,但是include里面省略了这个目录,变
成了扁平的结构。
access/
bootstrap/
c.h
catalog/
commands/
dynloader.h
executor/
fmgr.h
foreign/
funcapi.h
getaddrinfo.h
getopt_long.h
lib/
libpq/
mb/
miscadmin.h
nodes/
optimizer/
parser/
pg_config.h
pg_config.h.in
pg_config.h.win32
pg_config_manual.h
pg_config_os.h
pg_trace.h
pgstat.h
pgtime.h
port/
port.h
portability/
postgres.h
postgres_ext.h
postgres_fe.h
postmaster/
regex/
rewrite/
rusagestub.h
snowball/
stamp-h
storage/
tcop/
tsearch/
utils/
windowapi.h
代码的阅读方法
用调试器追踪代码
Postgresql那样的庞大系统,用眼睛来追踪源码并不容易。这里推荐用gdb这样的实际调试器来追踪代码的执行流程。可能有些人畏惧调
试器,但是如果只是简单追踪代码的执行流的话,还是很简单的。
但是多少还是要做一些准备的,Postgresql在编译的时候一定要把调试开关打开。通常在编译的时候configure的时候加上--enable-
debug的选项,然后可能的话可以编辑src/Makefile.global这个文件
CFLAGS = -O2 -Wall -Wmissing-prototypes -Wpointer-arith \
-Wdeclaration-after-statement -Wendif-labels -Wformat-security \
-fno-strict-aliasing -fwrapv
上面的行的"-O2"选项删除,然后加上"-g"
CFLAGS = -g -Wall -Wmissing-prototypes -Wpointer-arith \
-Wdeclaration-after-statement -Wendif-labels -Wformat-security \
-fno-strict-aliasing -fwrapv
"-O2"是编译器的优化选项,如果打开了,代码的执行顺序会改变,使得追踪起代码来比较困难,所以要去除。当然这样的话,编译后的
可执行文件会比较大,而且会比较慢,生产环境不太合适。大家需要理解这个操作仅仅是在学习的时候而设置的。
实际使用gdb试试
下面实际使用gdb来看看比较简单点的select文。
select 1;
select文执行后,至executor的其中一个函数ExecSelect停止,然后我们调查一下实际调用了那些函数。
首先以Postgresql的超级用户登录。我的环境是使用t-ishii这个用户安装Postgresql的,通常一般使用postgres这个用户,大家在阅读
的时候替换一下即可。
然后,用psql和数据库进行连接,连接的状态可以通过ps命令调查。
$ ps x
3714 ? Ss 0:00 postgres: t-ishii test [local] idle
可以看到上面的进程。这个就是后端的进程。这个是后端的进程,还有其他大量用户的Postgresql的连接也显示出来,比较难看清楚,
所以还是准备好测试的环境来进行测试比较好。
启动gdb后,附加到ps里显示的进程号码。
$ gdb postgres 3714
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation,Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY,to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-vine-linux".
For bug reporting instructions,please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/local/pgsql/bin/postgres...done.
Attaching to program: /usr/local/pgsql/bin/postgres,process 3714
Reading symbols from /lib64/libdl.so.2...done.
Loaded symbols for /lib64/libdl.so.2
Reading symbols from /lib64/libm.so.6...done.
Loaded symbols for /lib64/libm.so.6
Reading symbols from /lib64/libc.so.6...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
Reading symbols from /lib64/libnss_files.so.2...done.
Loaded symbols for /lib64/libnss_files.so.2
0x00007fad266f82e2 in __libc_recv (fd=<value optimized out>,buf=0xbe9900,
n=8192,flags=<value optimized out>)
at ../sysdeps/unix/sysv/linux/x86_64/recv.c:30
30 ../sysdeps/unix/sysv/linux/x86_64/recv.c:
in ../sysdeps/unix/sysv/linux/x86_64/recv.c
(gdb)
(gdb) 是gdb的命令行。在这个状态下,可以接受gdb的命令,如果输入b命令的话,在ExecResult可以设置断点。
(gdb) b ExecResult
Breakpoint 1,ExecResult (node=0xd13eb0) at nodeResult.c:75
(gdb)
psql启动以后从终端执行select 1,输入以后,后端就会执行该命令。这个时候,postgres进程已经暂停,所以psql会动不了。要继续执
行的话,可在gdb里执行"c"命令。执行了以后,就会在ExecResult 处停止。
Continuing.
Breakpoint 1,ExecResult (node=0xd13eb0) at nodeResult.c:75
75 econtext = node->ps.ps_ExprContext;
(gdb)
到ExecSelect为止的函数的调用路径可以用bt的命令显示出来。
(gdb) bt
#0 ExecResult (node=0xd13eb0) at nodeResult.c:75
#1 0x00000000005b92a4 in ExecProcNode (node=0xd13eb0) at execProcnode.c:367
#2 0x00000000005b71bb in ExecutePlan (estate=0xd13da0,planstate=0xd13eb0,
operation=CMD_SELECT,sendTuples=1 '\001',numberTuples=0,
direction=ForwardScanDirection,dest=0xcf9938) at execMain.c:1439
#3 0x00000000005b5835 in standard_ExecutorRun (queryDesc=0xc62820,count=0) at execMain.c:313
#4 0x00000000005b5729 in ExecutorRun (queryDesc=0xc62820,count=0) at execMain.c:261
#5 0x00000000006d2f79 in PortalRunSelect (portal=0xc60810,
forward=1 '\001',count=0,dest=0xcf9938) at pquery.c:943
#6 0x00000000006d2c4e in PortalRun (portal=0xc60810,
count=9223372036854775807,isTopLevel=1 '\001',dest=0xcf9938,
altdest=0xcf9938,completionTag=0x7fffa4b0eeb0 "") at pquery.c:787
#7 0x00000000006cd135 in exec_simple_query
(query_string=0xcf8420 "select 1;") at postgres.c:1018
#8 0x00000000006d1144 in PostgresMain (argc=2,argv=0xc42da0,
username=0xc42c40 "t-ishii") at postgres.c:3926
#9 0x0000000000683ced in BackendRun (port=0xc65600) at postmaster.c:3600
#10 0x00000000006833dc in BackendStartup (port=0xc65600) at postmaster.c:3285
#11 0x0000000000680759 in ServerLoop () at postmaster.c:1454
#12 0x000000000067ff4d in PostmasterMain (argc=3,argv=0xc40e00)
at postmaster.c:1115
#13 0x00000000005f7a39 in main (argc=3,argv=0xc40e00) at main.c:199
(gdb)
说明一下看的方法,发起调用的函数在下面,被调用的函数在上面。也就是ExecProcNode调用了ExecResult,ExecutePlan调用了
ExecProcNode,ExecutorRun调用了ExecProcNode,这样的形式来写。特别是中间的第7行。
#7 0x00000000006cd135 in exec_simple_query
(query_string=0xcf8420 "select 1;") at postgres.c:1018
这样可以清楚看到在处理SELECT文。:)仔细看gdb的输出,可以发现这些细节。
gdb是源码调试器,所以可以看到和源代码的对应关系。例如list命令可以看到现在执行的行附近的代码。
(gdb) list
70 TupleTableSlot *resultSlot;
71 PlanState *outerPlan;
72 ExprContext *econtext;
73 ExprDoneCond isDone;
74
75 econtext = node->ps.ps_ExprContext;
76
77 /*
78 * check constant qualifications like (2 > 1),if not already done
79 */
利用up命令可以往上面的函数移动。下面用list命令,可以确认实际调用ExecSelect 的地方。
(gdb) up
#1 0x00000000005b92a4 in ExecProcNode (node=0xd13eb0) at execProcnode.c:367
367 result = ExecResult((ResultState *) node);
(gdb) list
362 {
363 /*
364 * control nodes
365 */
366 case T_ResultState:
367 result = ExecResult((ResultState *) node);
368 break;
369
370 case T_ModifyTableState:
371 result = ExecModifyTable((ModifyTableState *) node);
利用down可以往下面的函数移动。利用up和down的组合,可以调查函数的调用关系。
要退出gdb的话可以用quit。
(gdb) quit
Inferior 1 [process 3714] will be detached.
Quit anyway? (y or n) y
Detaching from program: /usr/local/pgsql/bin/postgres,process 3714
到了这里gdb就结束了,但是后端进程并不会终止。
使用tag来跳转到相应的函数定义文件
我们已经使用了gdb来调查postgresql的运行,另外用gdb的list来追踪源码的话还是相当辛苦的,一般来说用emacs等编辑器一起调查和
浏览代码,可以在边调试边查看代码。
当然,在gdb模式下也可以使用。这个时候,例如如果想看看'exec_simple_query'的定义的话,使用emacs的tags命令可以立刻跳转到函
数定义的地方。要使用tags的话,需要生产tags文件,Postgresql的话,带有生产tags文件的脚本。
$ cd /usr/local/src/postgresql-9.1.1/src
$ tools/make_etags (使用emacs的场合)
$ tools/make_tags (使用vi的场合)
这样就可以拉。 然后在emacs中,在exec_simple_query 处执行'ESC-.'(按了ESC键后输入逗号.),即可打开光标所在文字所在的
exec_simple_query函数的定义文件。
总结
要完全理解Postgresql的话,通过调查源代码还是比较有效果的。要理解代码的话,可以按照目的自己追加必要的功能,改变一些功能
的行为,大家可以最大限度的的享受开源带来的好处。这次为了让大家能够理解Postgresql的源代码,说明了Postgresql 9.1的全体结
构,还有说明了代码树。然后还使用了调试器来追踪Postgresql的动作。
========
Postgresql源码简单分析(1)
Postgresql源码简单分析(by linux_prog@loveopensource.com)Postgresql是一个非常强大的开源数据库,既然使开源,当然,我们可以去修改他的代码做任何事情。
最近,忙着设计一个分布式数据库系统,所以,理所当然,就想到了在postgresql的基础上直接改。因此,
分析其源代码就必不可少了。
简单讲一下分析内容。
源码目录:
$ cd postgresql-8.2.4/src/backend/
$ ls
access catalog executor libpq Makefile nodes parser port postmaster rewrite tcop
bootstrap commands lib main nls.mk optimizer po postgres regex storage utils
其中:main/main.c是程序启动主文件
主文件没有作什么重要的事情,主要是作成为daemon等等一些我们并不关心的事情。
tcop/postgres.c是backend执行入口文件。
请看第3414行:
case ‘Q’: /* simple query */
{
const char *query_string;
/* Set statement_timestamp() */
SetCurrentStatementStartTimestamp();
query_string = pq_getmsgstring(&input_message); //拿到通过libpq传过来的sql语句
pq_getmsgend(&input_message);
exec_simple_query(query_string); //执行这个sql,并把结果通过libpq返回
send_ready_for_query = true;
}
break;
再看看postgres.c的第745行:
static void
exec_simple_query(const char *query_string)
{
CommandDest dest = whereToSendOutput;
MemoryContext oldcontext;
List *parsetree_list;
ListCell *parsetree_item;
bool save_log_statement_stats = log_statement_stats;
bool was_logged = false;
char msec_str[32];
/*
* Report query to varIoUs monitoring facilities.
*/
debug_query_string = query_string;
pgstat_report_activity(query_string);
/*
* We use save_log_statement_stats so ShowUsage doesn’t report incorrect
* results because ResetUsage wasn’t called.
*/
if (save_log_statement_stats)
ResetUsage();
/*
* Start up a transaction command. All queries generated by the
* query_string will be in this same command block,*unless* we find a
* BEGIN/COMMIT/ABORT statement; we have to force a new xact command after
* one of those,else bad things will happen in xact.c. (Note that this
* will normally change current memory context.)
*/
start_xact_command();
/*
* Zap any pre-existing unnamed statement. (While not strictly necessary,
* it seems best to define simple-Query mode as if it used the unnamed
* statement and portal; this ensures we recover any storage used by prior
* unnamed operations.)
*/
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
/*
* Switch to appropriate context for constructing parsetrees.
*/
oldcontext = MemoryContextSwitchTo(MessageContext);
QueryContext = CurrentMemoryContext;
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
*/
// 解析这个sql语句到一个语法树结构中
parsetree_list = pg_parse_query(query_string);
我想做的事情如下:
在postgresql的基础上作一个分布式数据库,但sql parse和backend/frontend的通信都不想自己写,
也就是说要使用postgresql的libpq。
因此做如下实验:
任何sql语句进来后,我会在exec_simple_query里面捷获,如果是一个select语句,
我会返回一行记录:列名—name 列值– lijianghua
继续分析文件: src/access/common/printtup.c
//以下函数使通过libpq发送返回的列的column 描述信息的
void
SendRowDescriptionMessage(TupleDesc typeinfo,List *targetlist,int16 *formats)
{
Form_pg_attribute *attrs = typeinfo->attrs;
int natts = typeinfo->natts;
int proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
int i;
StringInfoData buf;
ListCell *tlist_item = list_head(targetlist);
pq_beginmessage(&buf,‘T’); /* tuple descriptor message type */
pq_sendint(&buf,natts,2); /* # of attrs in tuples */
for (i = 0; i < natts; ++i)
{
Oid atttypid = attrs->atttypid;
int32 atttypmod = attrs->atttypmod;
pq_sendstring(&buf,NameStr(attrs->attname));
/* column ID info appears in protocol 3.0 and up */
if (proto >= 3)
{
/* Do we have a non-resjunk tlist item? */
while (tlist_item &&
((TargetEntry *) lfirst(tlist_item))->resjunk)
tlist_item = lnext(tlist_item);
if (tlist_item)
{
TargetEntry *tle = (TargetEntry *) lfirst(tlist_item);
pq_sendint(&buf,tle->resorigtbl,4);
pq_sendint(&buf,tle->resorigcol,2);
tlist_item = lnext(tlist_item);
}
else
{
/* No info available,so send zeroes */
pq_sendint(&buf,2);
}
}
/* If column is a domain,send the base type and typmod instead */
atttypid = getBaseTypeAndTypmod(atttypid,&atttypmod);
pq_sendint(&buf,(int) atttypid,sizeof(atttypid));
pq_sendint(&buf,attrs->attlen,sizeof(attrs->attlen));
/* typmod appears in protocol 2.0 and up */
if (proto >= 2)
pq_sendint(&buf,atttypmod,sizeof(atttypmod));
/* format info appears in protocol 3.0 and up */
if (proto >= 3)
{
if (formats)
pq_sendint(&buf,formats,2);
else
pq_sendint(&buf,2);
}
}
pq_endmessage(&buf);
}
//下面这个函数是select返回的数据的值,每一行数据都会调用一下这个函数
static void
printtup(TupleTableSlot *slot,DestReceiver *self)
{
TupleDesc typeinfo = slot->tts_tupleDescriptor;
DR_printtup *myState = (DR_printtup *) self;
StringInfoData buf;
int natts = typeinfo->natts;
int i;
/* Set or update my derived attribute info,if needed */
if (myState->attrinfo != typeinfo || myState->nattrs != natts)
printtup_prepare_info(myState,typeinfo,natts);
/* Make sure the tuple is fully deconstructed */
slot_getallattrs(slot);
/*
* Prepare a DataRow message
*/
pq_beginmessage(&buf,‘D’);
pq_sendint(&buf,2);
/*
* send the attributes of this tuple
*/
for (i = 0; i < natts; ++i)
{
PrinttupAttrInfo *thisState = myState->myinfo + i;
Datum origattr = slot->tts_values,
attr;
if (slot->tts_isnull)
{
pq_sendint(&buf,-1,4);
continue;
}
/*
* If we have a toasted datum,forcibly detoast it here to avoid
* memory leakage inside the type’s output routine.
*/
if (thisState->typisvarlena)
attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
else
attr = origattr;
if (thisState->format == 0)
{
/* Text output */
char *outputstr;
outputstr = OutputFunctionCall(&thisState->finfo,attr);
pq_sendcountedtext(&buf,outputstr,strlen(outputstr),false);
pfree(outputstr);
}
else
{
/* Binary output */
bytea *outputbytes;
outputbytes = SendFunctionCall(&thisState->finfo,attr);
pq_sendint(&buf,VARSIZE(outputbytes) - VARHDRSZ,4);
pq_sendbytes(&buf,VARDATA(outputbytes),
VARSIZE(outputbytes) - VARHDRSZ);
pfree(outputbytes);
}
/* Clean up detoasted copy,if any */
if (attr != origattr)
pfree(DatumGetPointer(attr));
}
pq_endmessage(&buf);
}
根据以上分析,我来修改exec_simple_query:
在833行加入如下内容:
//此范例只处理select语句
if(parsetree->type == T_SelectStmt)
{
StringInfoData buf;
pq_beginmessage(&buf,‘T’); /* tuple descriptor message type */
pq_sendint(&buf,1,2); /* number of columns in tuples */
pq_sendstring(&buf,“name”); // column名称
pq_sendint(&buf,4);
pq_sendint(&buf,2);
pq_sendint(&buf,4);
pq_sendint(&buf,2,2);
pq_endmessage(&buf);
pq_beginmessage(&buf,‘D’);
pq_sendint(&buf,2);
pq_sendcountedtext(&buf,“lijianghua”,10,false);
pq_endmessage(&buf);
//此行必须加上,告诉libpq返回结果结束(C代表completed)
pq_puttextmessage(’C',“select return 1 rows”);
return;
}
修改结束,按照正常流程编译Postgresql,并启动。
测试结果:
[mypg@webtrends mypg]$ psql
Welcome to psql 8.2.4,the Postgresql interactive terminal.
Type: \copyright for distribution terms
\h for help with sql commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit
mypg=# \d
List of relations
name
————
lijianghua
(1 row)
mypg=# select * from test2;
name
————
lijianghua
(1 row)
mypg=# select * from test3;
name
————
lijianghua
(1 row)
mypg=# select * from test5;
name
————
lijianghua
(1 row)
可以看到任何select语句都只返回我们预定义的结果,说明我们当初的想法是可行的(\d其实也是一个select语句)。
========
Postgresql存储引擎源码分析一
Postgresql的存储系统作为Postgresql的最低层,向下通过操作系统系统接口访问物理数据,向上为存取系统提供由缓冲区页面及页面上的接口函数。
存储系统的总体架构如下图所示
注释:Lock Manager是锁管理器,IPC是进程间通信,他们实现了存取层对存储层的互
斥访问,操作。
存储系统各子系统功能如下:
Page Manager:对缓冲区页面的结构进行定义并提供页面的相关操作。
Buffer Manager:对共享缓冲区和本地缓冲区进行管理。
Storage Manager:屏蔽不同物理设备接口函数的差异,向Buffer Manager提供统一的接口。
File Manager:一般的操作系统只允许一个进程打开256个文件,而Postgresql服务器在工作时需要打开的文件会很多,因此,其使用
File Manager来封装操作系统文件读写的函数。
下面对Page Manager的一段代码进行分析:Page Manager模块的功能上面已经讲到过,这里便不再赘述,这个模块主要由三个文件组成
:源码根目录下的backend\storage\page路径下的bufpage.c,itemptr.c,以及根目录下include\storage路径的头文件bufpage.h组成。
页面Page的结构大致如下:页面由页首部,页面存储记录的ID,存储的记录以及特殊空间所组成。其中,页面首部定义在bufpage.h文件
中。如下所示:
typedef struct PageHeaderData
{
XLogRecPtr pd_lsn; /* XLogRecPtr是定义在/include/access/xlogdefs.h中的一个结构体,定义了存取层所用的日志文件 ,期待
其他模块同学的完善。*/
uint16 pd_tli; /* 善未搞明白。。。*/
uint16 pd_flags; /* 页首部标志*/
LocationIndex pd_lower; /* 页面空闲区域起始偏移量,LocationIndex 是无符号16位整型数据,下同*/
LocationIndex pd_upper; /* 页面空闲区域结束偏移量 */
LocationIndex pd_special; /* 页面特殊区域起始偏移量 */
uint16 pd_pagesize_version;/* 页面大小*/
TransactionId pd_prune_xid; /* 不重要的XID,如果为空则为0 */
ItemIdData pd_linp[1]; /* ItemidData是定义在/include/storage/itemid.h中的结构体,主要定义了元组项的底层特征:元组在页
面上的偏移量,元组项指针的状态,元组的比特位长度,这里是定义了一个元组项的一个指针,指向页面不同的元组项(也就是记录)
*/
} PageHeaderData;
typedef PageHeaderData *PageHeader;
下面来分析/backend/storage/page/bufpage.c中的PageInit函数(页面初始化)
void PageInit(Page page,Size pageSize,Size specialSize)
{
PageHeader p = (PageHeader) page;
specialSize = MAXALIGN(specialSize);//MAXALIGN是常量表达式
Assert(pageSize == BLCKSZ);//如果页面大小和磁盘块大小相等的话,函数终止,页面初始化失败
Assert(pageSize > specialSize + SizeOfPageHeaderData);//SizeofPageHeaderData是定义在bufpage.h中的宏,即offsetof
(PageHeaderData,pd_linp),功能是获得页面首部中pd_linp数组的偏移量,如果页面大小大于特殊空间大小与偏移量之和的话,函数
终止。
MemSet(p,pageSize);//讲页首部初始化,清零。
p->pd_lower = SizeOfPageHeaderData;//初始化页面空闲区域起始偏移量
p->pd_upper = pageSize - specialSize;//初始化页面空闲区域结束偏移量
p->pd_special = pageSize - specialSize;//初始化特殊区域起始偏移量
PageSetPageSizeAndVersion(page,pageSize,PG_PAGE_LAYOUT_VERSION);//设置页面大小以及页面布局的版本号,这是定义在
bufpage.h下的一个宏原型为:#define PageSetPageSizeAndVersion(page,size,version) \
( \
AssertMacro(((size) & 0xFF00) == (size)),\
AssertMacro(((version) & 0x00FF) == (version)),\
((PageHeader) (page))->pd_pagesize_version = (size) | (version) \
)里面基本上是一些位操作:将页面大小的后16位置0,版本号的前16为置0.
}
========