| bin's profileipigPhotosBlog | Help |
|
April 17 关于对齐#include <iostream>
using namespace std; int main()
{ int i = 1; char *a = (char*)&i; for(int j=0;j<=3;j++) { cout<<(int)*(a+j)<<endl; } return 0; } 1。char 1 int 4
2。需要强制类型转换 char *a = (char*)&i;
3。读 int 的第 j 字节是用*(a+j) 而不是*(a+j*8)
4。输出时需要强制类型转换(int)*(a+j)
程序不是我写的。
从这个程序可以看到intel 的字节排列
如果 i = 1,2,3,4
输出是4,3,2,1 先16 位一交换,再8 位一交换 March 31 如何用正确的方法来写出质量好的软件的75条体会1. 你们的项目组使用源代码管理工具了么? 应该用。VSS、CVS、PVCS、ClearCase、CCC/Harvest、FireFly都可以。我的选择是VSS。 2. 你们的项目组使用缺陷管理系统了么? 应该用。ClearQuest太复杂,我的推荐是BugZilla。 3. 你们的测试组还在用Word写测试用例么? 不要用Word写测试用例(Test Case)。应该用一个专门的系统,可以是Test Manager,也可以是自己开发一个ASP.NET的小网站。主要目的是Track和Browse。 4. 你们的项目组有没有建立一个门户网站? 要有一个门户网站,用来放Contact Info、Baselined Schedule、News等等。推荐Sharepoint Portal Server 2003来实现,15分钟就搞定。买不起SPS 2003可以用WSS (Windows Sharepoint Service)。 5. 你们的项目组用了你能买到最好的工具么? 应该用尽量好的工具来工作。比如,应该用VS.NET而不是Notepad来写C#。用Notepad写程序多半只是一种炫耀。但也要考虑到经费,所以说是“你能买到最好的”。 6. 你们的程序员工作在安静的环境里么? 需要安静环境。这点极端重要,而且要保证每个人的空间大于一定面积。 7. 你们的员工每个人都有一部电话么?需要每人一部电话。而且电话最好是带留言功能的。当然,上这么一套带留言电话系统开销不小。不过至少每人一部电话要有,千万别搞得经常有人站起来喊:“某某某电话”。《人件》里面就强烈谴责这种做法。 8. 你们每个人都知道出了问题应该找谁么? 应该知道。任何一个Feature至少都应该有一个Owner,当然,Owner可以继续Dispatch给其他人。 9. 你遇到过有人说“我以为…”么? 要消灭“我以为”。Never assume anything。 10. 你们的项目组中所有的人都坐在一起么? 需要。我反对Virtual Team,也反对Dev在美国、Test在中国这种开发方式。能坐在一起就最好坐在一起,好处多得不得了。 11. 你们的进度表是否反映最新开发进展情况? 应该反映。但是,应该用Baseline的方法来管理进度表:维护一份稳定的Schedule,再维护一份最新更改。Baseline的方法也应该用于其它的Spec。Baseline是变更管理里面的一个重要手段。 12. 你们的工作量是先由每个人自己估算的么? 应该让每个人自己估算。要从下而上估算工作量,而不是从上往下分派。除非有其他原因,比如政治任务工期固定等。 13. 你们的开发人员从项目一开始就加班么? 不要这样。不要一开始就搞疲劳战。从项目一开始就加班,只能说明项目进度不合理。当然,一些对日软件外包必须天天加班,那属于剥削的范畴。 14. 你们的项目计划中Buffer Time是加在每个小任务后面的么? 不要。Buffer Time加在每个小任务后面,很容易轻易的就被消耗掉。Buffer Time要整段的加在一个Milestone或者checkpoint前面。 15. 值得再多花一些时间,从95%做到100%好值得,非常值得。 尤其当项目后期人困马乏的时候,要坚持。这会给产品带来质的区别。 16. 登记新缺陷时,是否写清了重现步骤? 要。这属于Dev和Test之间的沟通手段。面对面沟通需要,详细填写Repro Steps也需要。 17. 写新代码前会把已知缺陷解决么?要。每个人的缺陷不能超过10个或15个,否则必须先解决老的bug才能继续写新代码。 18. 你们对缺陷的轻重缓急有事先的约定么? 必须有定义。Severity要分1、2、3,约定好:蓝屏和Data Lost算Sev 1,Function Error算Sev 2,界面上的算Sev 3。但这种约定可以根据产品质量现状适当进行调整。 19. 你们对意见不一的缺陷有三国会议么?必须要有。要有一个明确的决策过程。这类似于CCB (Change Control Board)的概念。 20. 所有的缺陷都是由登记的人最后关闭的么? Bug应该由Opener关闭。Dev不能私自关闭Bug。 21. 你们的程序员厌恶修改老的代码么? 厌恶是正常的。解决方法是组织Code Review,单独留出时间来。XP也是一个方法。 22. 你们项目组有Team Morale Activity么? 每个月都要搞一次,吃饭、唱歌、Outing、打球、开卡丁车等等,一定要有。不要剩这些钱。 23. 你们项目组有自己的Logo么? 要有自己的Logo。至少应该有自己的Codename。 24. 你们的员工有印有公司Logo的T-Shirt么? 要有。能增强归属感。当然,T-Shirt要做的好看一些,最好用80支的棉来做。别没穿几次就破破烂烂的。 25. 总经理至少每月参加次项目组会议要的。 要让team member觉得高层关注这个项目。 26. 你们是给每个Dev开一个分支么? 反对。Branch的管理以及Merge的工作量太大,而且容易出错。 27. 有人长期不Check-In代码么? 不可以。对大部分项目来说,最多两三天就应该Check-In。 28. 在Check-In代码时都填写注释了么? 要写的,至少一两句话,比如“解决了Bug No.225”。如果往高处拔,这也算做“配置审计”的一部分。 29. 有没有设定每天Check-In的最后期限? 要的,要明确Check-In Deadline。否则会Build Break。 30. 你们能把所有源码一下子编译成安装文件吗? 要的。这是每日编译(Daily Build)的基础。而且必须要能够做成自动的。 31. 你们的项目组做每日编译么? 当然要做。有三样东西是软件项目/产品开发必备的:1. bug management; 2. source control; 3. daily build。 32. 你们公司有没有积累一个项目风险列表? 要。Risk Inventory。否则,下个项目开始的时候,又只能拍脑袋分析Risk了。 33. 设计越简单越好越简单越好。 设计时候多一句话,将来可能就带来无穷无尽的烦恼。应该从一开始就勇敢的砍。这叫scope management。 34. 尽量利用现有的产品、技术、代码千万别什么东西都自己Coding。BizTalk和Sharepoint就是最好的例子,有这两个作为基础,可以把起点提高很多。或者可以尽量多用现成的Control之类的。或者尽量用XML,而不是自己去Parse一个文本文件;尽量用RegExp,而不是自己从头操作字符串,等等等等。这就是“软件复用”的体现。 35. 你们会隔一段时间就停下来夯实代码么? 要。最好一个月左右一次。传言去年年初Windows组在Stevb的命令下停过一个月增强安全。Btw,“夯”这个字念“hang”,第一声。 36. 你们的项目组每个人都写Daily Report么? 要写。五分钟就够了,写10句话左右,告诉自己小组的人今天我干了什么。一则为了沟通,二则鞭策自己(要是游手好闲一天,自己都会不好意思写的)。 37. 你们的项目经理会发出Weekly Report么? 要。也是为了沟通。内容包括目前进度,可能的风险,质量状况,各种工作的进展等。 38. 你们项目组是否至少每周全体开会一次? 要。一定要开会。程序员讨厌开会,但每个礼拜开会时间加起来至少应该有4小时。包括team meeting, spec review meeting, bug triage meeting。千万别大家闷头写code。 39. 你们项目组的会议、讨论都有记录么? 会前发meeting request和agenda,会中有人负责主持和记录,会后有人负责发meeting minutes,这都是effective meeting的要点。而且,每个会议都要形成agreements和action items。 40. 其他部门知道你们项目组在干什么么? 要发一些Newsflash给整个大组织。Show your team’s value。否则,当你坐在电梯里面,其他部门的人问:“你们在干嘛”,你回答“ABC项目”的时候,别人全然不知,那种感觉不太好。 41. 通过Email进行所有正式沟通 Email的好处是免得抵赖。但也要避免矫枉过正,最好的方法是先用电话和当面说,然后Email来确认。 42. 为项目组建立多个Mailing Group 如果在AD+Exchange里面,就建Distribution List。比如,我会建ABC Project Core Team,ABC Project Dev Team,ABC Project All Testers,ABC Project Extended Team等等。这样发起Email来方便,而且能让该收到email的人都收到、不该收到不被骚扰。 43. 每个人都知道哪里可以找到全部的文档么? 应该每个人都知道。这叫做知识管理(Knowledge Management)。最方便的就是把文档放在一个集中的File Share,更好的方法是用Sharepoint。 44. 你做决定、做变化时,告诉大家原因了么? 要告诉大家原因。Empower team member的手段之一是提供足够的information,这是MSF一开篇的几个原则之一。的确如此,tell me why是人之常情,tell me why了才能有understanding。中国人做事喜欢搞限制,限制信息,似乎能够看到某一份文件的人就是有身份的人。大错特错。权威、权力,不在于是不是能access information/data,而在于是不是掌握资源。 45. Stay agile and expect change 要这样。 需求一定会变的,已经写好的代码一定会被要求修改的。做好心理准备,对change不要抗拒,而是expect change。 46. 你们有没有专职的软件测试人员? 要有专职测试。如果人手不够,可以peer test,交换了测试。千万别自己测试自己的。 47. 你们的测试有一份总的计划来规定做什么和怎么做么?这就是Test Plan。要不要做性能测试?要不要做Usability测试?什么时候开始测试性能?测试通过的标准是什么?用什么手段,自动的还是手动的?这些问题需要用Test Plan来回答。 48. 你是先写Test Case然后再测试的么? 应该如此。应该先设计再编程、先test case再测试。当然,事情是灵活的。我有时候在做第一遍测试的同时补上test case。至于先test case再开发,我不喜欢,因为不习惯,太麻烦,至于别人推荐,那试试看也无妨。 49. 你是否会为各种输入组合创建测试用例? 不要,不要搞边界条件组合。当心组合爆炸。有很多test case工具能够自动生成各种边界条件的组合——但要想清楚,你是否有时间去运行那么多test case。 50. 你们的程序员能看到测试用例么? 要。让Dev看到Test Case吧。我们都是为了同一个目的走到一起来的:提高质量。 51. 你们是否随便抓一些人来做易用性测试? 要这么做。自己看自己写的程序界面,怎么看都是顺眼的。这叫做审美疲劳——臭的看久了也就不臭了,不方便的永久了也就习惯了。 52. 你对自动测试的期望正确么? 别期望太高。依我看,除了性能测试以外,还是暂时先忘掉“自动测试”吧,忘掉WinRunner和LoadRunner吧。对于国内的软件测试的现状来说,只能“矫枉必须过正”了。 53. 你们的性能测试是等所有功能都开发完才做的么? 不能这样。性能测试不能被归到所谓的“系统测试”阶段。早测早改正,早死早升天。 54. 你注意到测试中的杀虫剂效应了么? 虫子有抗药性,Bug也有。发现的新Bug越来越少是正常的。这时候,最好大家交换一下测试的area,或者用用看其他工具和手法,就又会发现一些新bug了。 55. 你们项目组中有人能说出产品的当前整体质量情况么? 要有。当老板问起这个产品目前质量如何,Test Lead/Manager应该负责回答。 56. 你们有单元测试么? 单元测试要有的。不过没有单元测试也不是不可以,我做过没有单元测试的项目,也做成功了——可能是侥幸,可能是大家都是熟手的关系。还是那句话,软件工程是非常实践、非常工程、非常灵活的一套方法,某些方法在某些情况下会比另一些方法好,反之亦然。 57. 你们的程序员是写完代码就扔过墙的么? 大忌。写好一块程序以后,即便不做单元测试,也应该自己先跑一跑。虽然有了专门的测试人员,做开发的人也不可以一点测试都不做。微软还有Test Release Document的说法,程序太烂的话,测试有权踢回去。 58. 你们的程序中所有的函数都有输入检查么? 不要。虽然说做输入检查是write secure code的要点,但不要做太多的输入检查,有些内部函数之间的参数传递就不必检查输入了,省点功夫。同样的道理,未必要给所有的函数都写注释。写一部分主要的就够了。 59. 产品有统一的错误处理机制和报错界面么? 要有。最好能有统一的error message,然后每个error message都带一个error number。这样,用户可以自己根据error number到user manual里面去看看错误的具体描述和可能原因,就像SQL Server的错误那样。同样,ASP.NET也要有统一的Exception处理。可以参考有关的Application Block。 60. 你们有统一的代码书写规范么? 要有。Code Convention很多,搞一份来发给大家就可以了。当然,要是有FxCop这种工具来检查代码就更好了。 61. 你们的每个人都了解项目的商业意义么? 要。这是Vision的意思。别把项目只当成工作。有时候要想着自己是在为中国某某行业的信息化作先驱者,或者时不时的告诉team member,这个项目能够为某某某国家部门每年节省多少多少百万的纳税人的钱,这样就有动力了。平凡的事情也是可以有个崇高的目标的。 62. 产品各部分的界面和操作习惯一致么? 要这样。要让用户觉得整个程序好像是一个人写出来的那样。 63. 有可以作为宣传亮点的Cool Feature么? 要。这是增强团队凝聚力、信心的。而且,“一俊遮百丑”,有亮点就可以掩盖一些问题。这样,对于客户来说,会感觉产品从质量角度来说还是acceptable的。或者说,cool feature或者说亮点可以作为质量问题的一个事后弥补措施。 64. 尽可能缩短产品的启动时间要这样。 软件启动时间(Start-Up time)是客户对性能好坏的第一印象。 65. 不要过于注重内在品质而忽视了第一眼的外在印象程序员容易犯这个错误:太看重性能、稳定性、存储效率,但忽视了外在感受。而高层经理、客户正相反。这两方面要兼顾,协调这些是PM的工作。 66. 你们根据详细产品功能说明书做开发么? 要这样。要有设计才能开发,这是必须的。设计文档,应该说清楚这个产品会怎么运行,应该采取一些讲故事的方法。设计的时候千万别钻细节,别钻到数据库、代码等具体实现里面去,那些是后面的事情,一步步来不能着急。 67. 开始开发和测试之前每个人都仔细审阅功能设计么? 要做。Function Spec review是用来统一思想的。而且,review过以后形成了一致意见,将来再也没有人可以说“你看,当初我就是反对这么设计的,现在吃苦头了吧” 68. 所有人都始终想着The Whole Image么?要这样。项目里面每个人虽然都只是在制造一片叶子,但每个人都应该知道自己在制造的那片叶子所在的树是怎么样子的。我反对软件蓝领,反对过分的把软件制造看成流水线、车间。参见第61条。 69. Dev工作的划分是单纯纵向或横向的么? 不能单纯的根据功能模块分,或者单纯根据表现层、中间层、数据库层分。我推荐这么做:首先根据功能模块分,然后每个“层”都有一个Owner来Review所有人的设计和代码,保证consistency。 70. 你们的程序员写程序设计说明文档么? 要。不过我听说微软的程序员1999年以前也不写。所以说,写不写也不是绝对的,偷懒有时候也是可以的。参见第56条。 71. 你在招人面试时让他写一段程序么? 要的。我最喜欢让人做字符串和链表一类的题目。这种题目有很多循环、判断、指针、递归等,既不偏向过于考算法,也不偏向过于考特定的API。 72. 你们有没有技术交流讲座? 要的。每一两个礼拜搞一次内部的Tech Talk或者Chalk Talk吧。让组员之间分享技术心得,这笔花钱送到外面去培训划算。 73. 你们的程序员都能专注于一件事情么? 要让程序员专注一件事。例如说,一个部门有两个项目和10个人,一种方法是让10个人同时参加两个项目,每个项目上每个人都花50%时间;另一种方法是5个人去项目A,5个人去项目B,每个人都100%在某一个项目上。我一定选后面一种。这个道理很多人都懂,但很多领导实践起来就把属下当成可以任意拆分的资源了。 74. 你们的程序员会夸大完成某项工作所需要的时间么? 会的,这是常见的,尤其会在项目后期夸大做某个change所需要的时间,以次来抵制change。解决的方法是坐下来慢慢磨,磨掉程序员的逆反心理,一起分析,并把估算时间的颗粒度变小。 75. 尽量不要用Virtual Heads 最好不要用Virtual Heads。 Virtual heads意味着resource is not secure,shared resource会降低resource的工作效率,容易增加出错的机会,会让一心二用的人没有太多时间去review spec、review design。一个dedicated的人,要强过两个只能投入50%时间和精力的人。我是吃过亏的:7个part time的tester,发现的Bug和干的活,加起来还不如两个full-time的。参见第73条。73条是针对程序员的,75条是针对Resource Manager的。 March 29 寓言?企业管理寓言:猎人与狗
企业管理者常常会遇到这样一些困惑:为什么员工很难像老板一样全力以赴的工作?为什么优秀人才难以留住?为什么员工的工作积极性难以持久?下面的故事也许能为大家解惑。 ■■如何让员工像老板一样卖命地干活 有一天,猎人带着一只猎狗,到森林中打猎,猎狗将一只兔子赶出了窝,追了很久也没有追到。后来兔子一拐弯,不知道跑到哪去了。牧羊犬见了,讥笑猎狗说:“你真没用,竟跑不过一只小小的兔子。”猎狗解释说:“你有所不知,不是我无能,只因为我们两个跑的目标完全不同,我仅仅是为了一顿饭而跑,而它却是为了性命啊。” 这话传到了猎人的耳朵里,猎人想,猎狗说得对呀,我要想得到更多的兔子,就得想个办法,消灭“大锅饭”,让猎狗也为自己的生存而奔跑。猎人思前想后,决定对猎狗实行论功行赏。 于是猎人召开猎狗大会,宣布:在打猎中每抓到一只兔子,就可以得到一根骨头的奖励,抓不到兔子的就没有。 这一招,果然有用,猎狗们抓兔子的积极性大大提高了,每天捉到兔子的数量大大增加,因为谁也不愿看见别人吃骨头,自己却干看。 可是,一段时间过后,一个新的问题出现了:猎人发现猎狗们虽然每天都能捉到很多兔子,但兔子的个头却越来越小。 猎人疑惑不解,于是,他便去问猎狗:“最近你们抓的兔子怎么越来越小了?” 猎狗们说:“大的兔子跑得快,小的兔子跑得慢,所以小兔子比大兔子好抓得多了。反正,按你的规定,大的小的奖励都一样,我们又何必要费那么大的力气,去抓大兔子呢?” 猎人终于明白了,原来是奖励的办法不科学啊!于是,他宣布,从此以后,奖励骨头的多少不再与捉到兔子的只数挂钩,而是与捉到兔子的重量挂钩。 此招一出,猎狗们的积极性再一次高涨,捉到兔子的数量和重量,都远远超过了以往,猎人很开心。 ■■如何让员工保持很高的积极性 遗憾的是,好景不长。一段时间过后,新的问题又出现了:猎人发现,猎狗们捉兔子的积极性在逐渐下降,而且越是有经验的猎狗下降得越历害。 又是咋回事呢?于是猎人又去问猎狗。 猎狗们对猎人说:“主人啊,我们把最宝贵的青春都奉献给您了,等我们以后老了,抓不动兔子了,你还会给我们骨头吃吗?” 猎人一听,明白了,原来猎狗们需要养老保险,于是,他进一步完善激励机制。规定:每只猎狗每月捉到的兔子达到一个规定的量以后,多余部分可以转化为骨头的贮存,将来老了,捉不到兔子了,就可以享用这些贮存。 这个决定宣布之后,猎狗们群情激昂,抓兔子的积极性空前高涨。猎人也无比欣慰,觉得从此可以万事无忧了。 ■■如何留住优秀的员工 就这样,过了一段时间之后,一件意想不到的事情发生了:一些优秀的猎狗开始离开猎人,自己捉兔子去了。 面对这一情况,一开始,猎人以为是思想政治工作没做好。便连续举办了一系列“狗力资源与风险高层猎狗研修班,”培训主题为:缺乏统一指挥所造成的狗力资源浪费,强调猎人的规划对猎狗捕猎的重要性,并有意夸大了其负面影响。这一招对稳定猎狗队伍起到了一定的积极作用,但优秀猎狗流失的状况并未得到有效控制。 猎人有些着急了。他想,难道是奖励的力度不够?于是,他将优秀猎狗的奖励标准提高了一倍。这一招收到了比较明显的效果,优秀猎狗流失的问题得到了暂时缓解,但却无法从根本上得到遏制,一段时间之后,离开猎人,自己去捉兔子的猎狗,又开始逐渐多了起来,而且基本上都是最优秀的。 聪明的猎人这下可犯愁了,他百思不得其解。万般无奈之下,他决定直接去向离开的猎狗们咨询。他用10根骨头的代价把5只猎狗请到一起,他十分动情地对它们说:“猎狗兄弟们,我实在不知道我做了什么对不起你们的事,你们为什么一定要离开我呢?”猎狗们对猎人说:“主人啊,你是天下最好的主人,我们有任何愿望,你都尽力给予满足,没有任何对不起我们的地方。我们离开你,自己去捉兔子,也不仅仅是为了多得几根骨头,更重要的是我们有一个梦想,我们希望有一天我们也能象您一样,成为老板。”猎人听后,恍然大悟,原来他们是想实现自我价值! 怎么解决这一问题呢? 聪明的猎人经过较长一段时间的潜心研究,终于找到了解决方案。于是,他成立了一个猎狗股份有限公司,出台了三条新政策:第一条,实行优者有股。优秀的猎狗可以将贮存的骨头转化为公司的股份,并根据贡献率每年奖励一定数量的股份期权,使优秀的猎狗有机会在公司发财;第二条,实行贤者终身。连续三年惑累计5年被评为优秀猎狗者,可成为终身猎狗,享受一系列诱人的优厚待遇;第三条,实行强者孵化。优秀的猎狗可以随着业绩增长,逐步成为团队经理、业务总监、总经理、董事长,实现做老板的梦想。 这一招十分灵验。从此以后,不仅该公司优秀的猎狗对猎人忠心耿耿,而且其它地方的优秀猎狗纷纷慕名加盟,猎人的公司越办越火,长盛不衰。 ■■总结 这个故事,至少说明了三个重要道理: 第一、一个企业,员工的问题,往往根源在机制,责任在老板,因此,我们要多研究机制,少责备员工,这样,管理才会不断完善,劳资关系也才会更加融洽与和谐; 第二、有没有激励大不一样,激励科学与不科学大不一样; 第三、员工的需求是不断增长的,企业必须满足员工不断增长的物质文化的需要,才能有效激励人才和长久地留住人才。 《中国管理顾问杂志》 February 03 童年?昨天去吃饭,意外的发现了小时候(10岁以前)的很多糗事,我一个都记不得了,都是姐姐伯伯婶婶说的津津有味。
1。还不会走路的时候,妈妈把我扔在澡盆(以前的那种木盆)里,然后我就沿着盆的边缘转来转去 2。头发长长了,伯伯绑着我理了发,我哭了2个多小时,旁边还有个小女孩一直看着。好象从那以后我就再没去过那个伯伯家。 3。上舅舅家吃饭,我总是看电视,舅妈生气了,把电闸拉了。我就叫姐姐送电,姐姐拉上电闸后,我叫姐姐用肥皂洗手防止触电(我小时候真是太聪明了:))。以后再没电,我学会自己去拉电闸了。不过好象他们总不让我看电视,我以后也去得少了。 4。90年,家里起房子,由于太好动的原因,爸爸把我绑在了桌子腿上(:(),还说:再动用钢筋把你从嘴通到屁眼。-__________________________-b 5。小的时候总是好动,特别喜欢从一个桌子上跳到另一个桌子上,喜欢在叠起来的水泥板上跳来跳去,最后两只手一只骨折过一只断过。 下面是一个关于我姐姐的
伯伯买了摩托车,然后一个一个带我们转。轮到姐姐的时候,她开始坐的好好的,途中忽然想倒过来坐(八仙中有个倒骑驴的那种),然后就在后座上动了,车就倒了。 我发现现在的性格和小时候的有很大差别,转变的太严重了。我更喜欢那种性格,也可能是因为失去了才更想重新得到。 September 30 Linux Utilities
Shell: bash。结合了 csh 和 ksh 的优点,并且有 readline 功能,可以随意绑定自己的键盘。 September 23 src/backend/utils/hash/dynahash.c终于搞懂了,用的是Extendible Hashing
/*-------------------------------------------------------------------------
* * dynahash.c * dynamic hash tables * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/utils/hash/dynahash.c,v 1.58 2004/12/31 22:01:37 pgsql Exp $ * *------------------------------------------------------------------------- */ /* * * Dynamic hashing, after CACM April 1988 pp 446-457, by Per-Ake Larson. * Coded into C, with minor code improvements, and with hsearch(3) interface, * by ejp@ausmelb.oz, Jul 26, 1988: 13:16; * also, hcreate/hdestroy routines added to simulate hsearch(3). * * These routines simulate hsearch(3) and family, with the important * difference that the hash table is dynamic - can grow indefinitely * beyond its original size (as supplied to hcreate()). * * Performance appears to be comparable to that of hsearch(3). * The 'source-code' options referred to in hsearch(3)'s 'man' page * are not implemented; otherwise functionality is identical. * * Compilation controls: * DEBUG controls some informative traces, mainly for debugging. * HASH_STATISTICS causes HashAccesses and HashCollisions to be maintained; * when combined with HASH_DEBUG, these are displayed by hdestroy(). * * Modified margo@postgres.berkeley.edu February 1990 * added multiple table interface * Modified by sullivan@postgres.berkeley.edu April 1990 * changed ctl structure for shared memory */ #include "postgres.h"
#include "utils/dynahash.h"
#include "utils/hsearch.h" #include "utils/memutils.h" /*
* Key (also entry) part of a HASHELEMENT */ #define ELEMENTKEY(helem) (((char *)(helem)) + MAXALIGN(sizeof(HASHELEMENT))) /*
* Fast MOD arithmetic, assuming that y is a power of 2 ! */ #define MOD(x,y) ((x) & ((y)-1)) /*
* Private function prototypes */ static void *DynaHashAlloc(Size size); static HASHSEGMENT seg_alloc(HTAB *hashp); static bool element_alloc(HTAB *hashp, int nelem); static bool dir_realloc(HTAB *hashp); static bool expand_table(HTAB *hashp); static void hdefault(HTAB *hashp); static bool init_htab(HTAB *hashp, long nelem); static void hash_corrupted(HTAB *hashp); /* * memory allocation routines */ static MemoryContext DynaHashCxt = NULL; static MemoryContext CurrentDynaHashCxt = NULL; static void *
DynaHashAlloc(Size size) { Assert(MemoryContextIsValid(CurrentDynaHashCxt)); return MemoryContextAlloc(CurrentDynaHashCxt, size); } #define MEM_ALLOC DynaHashAlloc
#undef MEM_FREE /* already in windows header files */ #define MEM_FREE pfree #if HASH_STATISTICS static long hash_accesses, hash_collisions, hash_expansions; #endif /************************** CREATE ROUTINES **********************/ HTAB *
hash_create(const char *tabname, long nelem, HASHCTL *info, int flags) /*
* Set default HASHHDR parameters. */ static void hdefault(HTAB *hashp) static bool init_htab(HTAB *hashp, long nelem) /*
* Estimate the space needed for a hashtable containing the given number * of entries of given size. * NOTE: this is used to estimate the footprint of hashtables in shared * memory; therefore it does not count HTAB which is in local memory. * NB: assumes that all hash structure parameters have default values! */ long hash_estimate_size(long num_entries, Size entrysize) /*
* Select an appropriate directory size for a hashtable with the given * maximum number of entries. * This is only needed for hashtables in shared memory, whose directories * cannot be expanded dynamically. * NB: assumes that all hash structure parameters have default values! * * XXX this had better agree with the behavior of init_htab()... */ long hash_select_dirsize(long num_entries) /********************** DESTROY ROUTINES ************************/ void
hash_destroy(HTAB *hashp) void
hash_stats(const char *where, HTAB *hashp) /*********************SEARCH ROUTINES ************************/
/* Convert a hash value to a bucket number */ static inline uint32 calc_bucket(HASHHDR *hctl, uint32 hash_val) /*----------
* hash_search -- look up key in table and perform action * * action is one of: * HASH_FIND: look up key in table * HASH_ENTER: look up key in table, creating entry if not present * HASH_REMOVE: look up key in table, remove entry if present * HASH_FIND_SAVE: look up key in table, also save in static var * HASH_REMOVE_SAVED: remove entry saved by HASH_FIND_SAVE * * Return value is a pointer to the element found/entered/removed if any, * or NULL if no match was found. (NB: in the case of the REMOVE actions, * the result is a dangling pointer that shouldn't be dereferenced!) * A NULL result for HASH_ENTER implies we ran out of memory. * * If foundPtr isn't NULL, then *foundPtr is set TRUE if we found an * existing entry in the table, FALSE otherwise. This is needed in the * HASH_ENTER case, but is redundant with the return value otherwise. * * The HASH_FIND_SAVE/HASH_REMOVE_SAVED interface is a hack to save one * table lookup in a find/process/remove scenario. Note that no other * addition or removal in the table can safely happen in between. *---------- */ void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr) /*
* hash_seq_init/_search * Sequentially search through hash table and return * all the elements one by one, return NULL when no more. * * NOTE: caller may delete the returned element before continuing the scan. * However, deleting any other element while the scan is in progress is * UNDEFINED (it might be the one that curIndex is pointing at!). Also, * if elements are added to the table while the scan is in progress, it is * unspecified whether they will be visited by the scan or not. */ void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp) void *
hash_seq_search(HASH_SEQ_STATUS *status) /********************************* UTILITIES ************************/ /*
* Expand the table by adding one more hash bucket. */ static bool expand_table(HTAB *hashp) /* * Relocate records to the new bucket. NOTE: because of the way the * hash masking is done in calc_bucket, only one old bucket can need * to be split at this point. With a different way of reducing the * hash value, that might not be true! */ static bool dir_realloc(HTAB *hashp) static HASHSEGMENT seg_alloc(HTAB *hashp) /*
* allocate some new elements and link them into the free list */ static bool element_alloc(HTAB *hashp, int nelem) /* complain when we have detected a corrupted hashtable */
static void hash_corrupted(HTAB *hashp) /* calculate ceil(log base 2) of num */ int my_log2(long num) September 19 31.11. 用户定义类型PostgreSQL 可以扩展为支持新数据类型。
本节描述如何定义新的基本类型,这些类型是那些定义在 SQL 语言之下的数据类型。 创建一个新的基本类型要求实现函数在一种低层语言的类型上操作,通常是 C。
本节的例子可以在源码发布中 src/tutorial 目录的 complex.sql 和 complex.c 里找到。
一个用户定义的类型总是有输入和输出函数。 这些函数决定该类型如何在字串里出现(让用户输入和输出给用户)以及类型如何在存储器里组织。 输入函数以一个以空(null)结尾的字符串为参数并且返回该类型的内部(内存里)的表现形式。 输出类型以该类型的内部表现形式为参数并且返回一个以空(null)结尾的字符串。
假设我们要定义一个类型 complex 用来表示复数。 通常,我们选用下面的 C 结构来在存储器里表现复数:
typedef struct Complex {
double x; double y; } Complex; 对于该类型的外部表现形式,我们选择形如 (x,y) 的字串。 输入输出函数通常并不难写,尤其是输出函数。但是, 在定义你的外部(字符串)表现形式时,要注意你最后必须为该表现形式写一个完整而且健壮的分析器作为你的输入函数。比如:
PG_FUNCTION_INFO_V1(complex_in);
Datum
complex_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); double x, y; Complex *result; if (sscanf(str, " ( %lf , %lf )", &x, &y) != 2)
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for complex: \"%s\"", str))); result = (Complex *) palloc(sizeof(Complex));
result->x = x; result->y = y; PG_RETURN_POINTER(result); } 输出函数可以简单的就是: PG_FUNCTION_INFO_V1(complex_out);
Datum
complex_out(PG_FUNCTION_ARGS) { Complex *complex = (Complex *) PG_GETARG_POINTER(0); char *result; result = (char *) palloc(100);
snprintf(result, 100, "(%g,%g)", complex->x, complex->y); PG_RETURN_CSTRING(result); } 你应该把你的输入和输出函数做的互为逆(函数)。如果你不这样做, 你就可能在需要把数据输出来在装载回去时碰到很严重的问题,当涉及到浮点数时,这是非常普遍的问题。
另外,一个用户定义类型可以提供二进制输入和输出过程。 二进制 I/O 通常更快,但是没有文本 I/O 移植性好。 因为对于文本 I/O 而言,完全是由你来定义外部的二进制形式是如何的。 大多数内置的数据类型都尽可能提供一个与机器无关的二进制形式。 对于 complex,我们将把二进制 I/O 建立在 float8 的基础上。
PG_FUNCTION_INFO_V1(complex_recv);
Datum
complex_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); Complex *result; result = (Complex *) palloc(sizeof(Complex));
result->x = pq_getmsgfloat8(buf); result->y = pq_getmsgfloat8(buf); PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(complex_send);
Datum
complex_send(PG_FUNCTION_ARGS) { Complex *complex = (Complex *) PG_GETARG_POINTER(0); StringInfoData buf; pq_begintypsend(&buf);
pq_sendfloat8(&buf, complex->x); pq_sendfloat8(&buf, complex->y); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } 要定义 complex 类型,我们要在创建该类型前先创建用户定义的 I/O 函数:
CREATE FUNCTION complex_in(cstring)
RETURNS complex AS 'filename' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION complex_out(complex)
RETURNS cstring AS 'filename' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION complex_recv(internal)
RETURNS complex AS 'filename' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION complex_send(complex)
RETURNS bytea AS 'filename' LANGUAGE C IMMUTABLE STRICT; 请注意输入和输出函数的声明必须引用还没定义的类型。 这是允许的,但是会导致一些警告,你可以忽略这些警告。 必须先定义输入函数。 最后,我们可以声明数据类型:
CREATE TYPE complex (
internallength = 16, input = complex_in, output = complex_out, receive = complex_recv, send = complex_send, alignment = double ); 当你定义一种新的基本类型时, PostgreSQL 自动提供对该类型的数组的支持。 因为历史原因, 数组类型的类型名是与类型同名字串前面加个下划线字符(_)。
一旦数据类型存在,我们就可以声明额外的函数以提供在该数据类型上的有用的操作。 然后就可以在这些函数上定义操作符,如果需要,还可以创建操作符表支持该数据类型的索引。 这些额外的层在后面的章节介绍。
如果你的数据类型的大小可能超过几百个字节(内部形式), 那么你应该很仔细地把它们标记为可 TOAST 的(参阅 Section 49.2)。 要做到这一点, 该类型地内部形式必需遵循变长数据内部形式地标准布局: 头四个字节必需是一个 int32,包含数据地全长(包括长度自身)。 在该类型上操作的 C 函数必须通过使用 PG_DETOAST_DATUM 小心地解开它们处理的任何“烘烤”过的数值(这些细节通常都可以通过定义类型相关的 GETARG 宏掩盖)。 最后,在使用 CREATE TYPE 命令的时候, 声明内部长度为 variable 并且选择恰当的存储选项。
每个操作符都是对真正干活的对应函数的"语义修饰"; 所以你在创建操作符之前必须先创建对应的函数。 不过,一个操作符也并不仅仅是语义修饰, 因为它还带着可以帮助查询规划器优化使用该操作符的查询的附加信息。
PostgreSQL 支持左目,右目,和双目操作符。 操作符可以重载; 也就是说,同一个操作符名字可以用于不同的, 拥有不同的数目和类型的操作数的操作符。在执行一个查询的时候,系统从名字和提供的操作符类型上判断需要调用哪个操作符。
下面是一个创建用于两个复数相加的操作符的例子。 我们假设已经创建了complex 类型的定义。首先我们需要做(相加)工作的函数;然后我们就可以定义操作符:
CREATE FUNCTION complex_add(complex, complex)
RETURNS complex AS 'filename', 'complex_add' LANGUAGE C IMMUTABLE STRICT; CREATE OPERATOR + (
leftarg = complex, rightarg = complex, procedure = complex_add, commutator = + ); 现在我们可以执行像下面这样的查询:
SELECT (a + b) AS c FROM test_complex;
c
----------------- (5.2,6.05) (133.42,144.95) 我们在这里已经演示了如何创建双目操作符。要创建单目操作符, 只需要省略leftarg(对左目操作符)或者rightarg (对右操作符)即可。只有procedure子句和参数(argument)子句是 CREATE OPERATOR 里需要的条目。 例子里演示的 commutator 子句是一个给查询优化器的可选的暗示。
操作符优化信息
PostgreSQL 的操作符定义可以包括几个可选的子句, 这些子句告诉系统一些关于该操作符的特性的有用信息。 在可能的情况下,我们都应该提供这些子句, 因为它们可能为使用这个操作符的查询带来可观的速度提升。 不过要注意如果你声明了这些子句,你必须确保它们是正确的! 对优化子句的错误使用将导致服务器的崩溃, 微小的输出错误或者其他糟糕事情。如果你对这些事情不确定的话, 你可以总是忽略优化子句;唯一的后果是查询可能比需要的运行的慢一些。 附加的优化子句可能在今后的 PostgreSQL版本里出现。 这里描述的都是版本 8.0.0 可以理解的。
31.13.1. COMMUTATOR
如果提供了 COMMUTATOR子句,则命名一个操作符是被定义的操作符的交换符。 如果有两个操作符A,B,对于任何可能的输入数值 x,y 都有 A,B,对于任何可能的输入数值都有 (x A y) 等于 (y B x),那么我们就说 A 是 B 的交换符, 同样 B 也是 A 的交换符。 例如,操作符 < 和 > 对于所使用的一定的数据类型通常都是对方的交换符, 而操作符 '+' 通常是它自身的交换符。但是操作符 '-' 通常没有交换符。 给那些会载索引和连接子句里面使用的操作符提供交换符是非常关键的, 因为这样就允许查询优化器"移动"这样的子句,形成所需要的不同的规划类型的形式。 比如,考虑一个有类似 tab1.x = tab2.y 的 WHERE 子句的查询, 这里 tab1.x 和 tab2.y 是用户定义类型,并且假设 tab2.y 上面有索引。 除非优化器知道如何载 tab2.y = tab1.x 周围四处移动该子句, 否则它不能生成索引扫描,因为索引扫描机制期望看到索引字段在给出的操作符上左边。 PostgreSQL不会简单地假设这是一个合法的转换 — = 的创建者必须声明这是有效的,方法是给这个操作符标记交换器信息。
当你定义一对交换符操作符时,事情就有一点棘手: 怎样定义一个操作符的交换符指向另一个你还没有定义的操作符呢? 我们对这个问题有两个解决方法:
一个方法是省略你定义的第一个操作符的COMMUTATOR子句, 然后在第二个操作符的定义里提供一个 (COMMUTATOR子句)。因为 PostgreSQL 知道换向操作符是成对出现的, 所以当它看到第二个定义时它会自动折回并填充第一个定义里空缺的COMMUTATOR子句。
另一个更直接的方法是在两个定义里面都包含COMMUTATOR子句。当 PostgreSQL 处理第一个定义并意识到 COMMUTAOTR 指向一个不存在的操作符, 系统会在系统表里面为该操作符记录一个虚拟的记录。 这个虚拟的记录只有操作符名,左和右操作数类型以及结果类型是有效的, 因为这些是到目前为止 PostgreSQL 可以推导出来的东西。第一个操作符表记录将和这个虚拟记录联接。 稍后,当你定义第二个操作符时,系统将用来自第二个操作符的信息更新该虚拟记录。 如果你试图在虚拟操作符被填充之前使用它, 你将只能收到一条错误信息。
31.13.2. NEGATOR 如果提供了NEGATOR子句,则命名一个操作符是被定义的操作符的否定符。 如果有两个都返回布尔变量的操作符 A 和 B,对任何可能的输入 x 和 y, 都有 (x A y) 等于 NOT (x B y),那么我们说 A 是 B 的否定符。 当然 B 也是 A 的否定符。例如,< 和 >= 对大多数数据类型是一对否定符。 一个操作符不可能是它自身的有效操作符。 提供否定符对查询优化器是非常有帮助的, 因为这样就允许象NOT (x = y)这样的表达式简化成 x <> y。这样的情况比你想象的要频繁的多, 因为NOT可能因为其他的重排列而被引入。
否定符对可以用上面换向符对中解释的相同的方法来定义。
31.13.3. RESTRICT
如果提供了RESTRICT子句,则为操作符命名一个选择性限制计算函数 (注意这里是一个函数名,而不是一个操作符名)。 RESTRICT子句只是对返回boolean变量的双目操作符有意义。 选择性限制计算符的概念是猜测一个表中所有行的哪 一部分对于目前的操作符和特定的常量将满足一个象下面这样形式的 WHERE 条件子句 column OP constant
它可以给出这种类型的WHERE子句可以删除多少行的一个概念, 这将帮助优化器进行优化。(你可能会说, 如果该常量(constant)在左边怎么办?哦,那是COMMUTATOR 干的事...) 书写新的选择性限制计算函数远远超出了本章的范围, 不过很幸运的是,通常你对自己的操作符只需要使用系统标准的计算器之一就行了。下面是一些标准限制计算器:
eqsel for = neqsel for <> scalarltsel for < or <= scalargtsel for > or >= 这些都是分类,看起来有点奇怪,不过如果你仔细想想,就会觉得有道理。 '=' 大多将只接受表中的一小部分行; '<>' 大多将拒绝一小部分行。 '<' 将接受的行取决于给出的常量落在表的该列数据值的哪一个范围里 (该值碰巧是 ANALYZE 收集并且提供给选择性计算器的信息)。 '<=' 在同样的常量时会接受比 '<' 略微大一些的行, 不过它们也非常接近,几乎不值得区别开来, 尤其是无论如何我们也比做盲猜好得多。类似的情况也适用于 '>' and '>='。 你可能常习惯于把eqsel或者neqsel 用于那些非常高或者非常低选择性的操作符,即使它们并非真正相等或者不相等。例如, 几何操作符约等于就使用eqsel,它是基于这样的假设: 它们只会匹配整个表中的一小部分记录。
你可以把scalarltsel和scalargtsel 用于比较那些为进行范围比较被转化为数字尺度后有明显意义的数据类型。 如果可能,把该数据类型增加到可以被文件 src/backend/utils/adt/selfuncs.c里的函数 convert_to_scalar() 理解的部分。(最终,这个过程将被放到由pg_type 表里的一个列标识的每种类型一个的函数代替,不过目前还没有这么做。) 如果你没有做这些,系统仍然能工作,不过优化器的估计不会象想象的那么好。
在src/backend/utils/adt/geo_selfuncs.c 里还有为几何操作符设计的额外的选择性评估函数:areasel, positionsel, 和contsel。在我写这些的时候,它们都只是存根, 但是你还是可以使用(或者更好的是,改良它们)它们。
31.13.4. JOIN
如果提供了JOIN子句, 则为操作符命名一个连接选择性函数。(注意这里是函数名,不是操作符名。) JOIN子句只是对返回boolean的双目操作符有意义。 一个连接选择性计算器后面的概念是猜测一对表上的哪一部分 行对目前的操作符将满足下面形式的WHERE子句的条件 table1.field1 OP table2.field2
和RESTRICT子句一样, 这些很有可能帮助优化器用最少的处理勾画出要采取可能的连接顺序中的哪一个。 和前面一样,本节不会试图解释如何书写一个连接选择性计算器函数, 但是会建议你在有一个可用的情况下,使用一个标准的计算器:
eqjoinsel for = neqjoinsel for <> scalarltjoinsel for < or <= scalargtjoinsel for > or >= areajoinsel for 2D area-based comparisons positionjoinsel for 2D position-based comparisons contjoinsel for 2D containment-based comparisons 31.13.5. HASHES
如果出现了HASHES子句, 则告诉系统对于一个基于此操作符的连接可以使用哈希(散列)连接。 HASHES只对返回boolean的双目操作符有意义, 并且实际上该操作符最好是对某种数据类型的相等操作符。 哈希(散列)连接的假设是: 对于一对哈希(散列)到同样的哈希(散列)代码的左和右操作数值,该连接操作符只能返回真。 如果两个值被放到不同的哈希桶里, 连接将根本不比较它们,隐含地意味着连接操作符的结果一定是假。 所以对于不代表相等的操作符,声明HASHES是没有意义的。
实际上,逻辑相等还不够好;该操作符最好是代表完全的按位相等, 因为哈希函数将对该值的内存表现形式进行计算而不管这些位的含义是什么。 比如,多边形操作符 ~=,它检查两个多边形是否相等, 它就不是按位相等的,因为即使两个多边形的定点声明的顺序不同, 我们也可以认为它们是相等的。 这就意味着对于一个用 ~= 在对边形域之间的连接, 如果用哈希连接实现将会和用别的连接实现生成不同的结果, 因为可以匹配的大部分数据对将被哈希成不同的值因而不会被哈希连接进行比较。 但是如果优化器选择使用不同的连接方法, 那么所有~=操作符说相等的数据对都会被找出来。我们不想出现那种不一致性, 所以我们没有标记~=为可哈希的。
同时还有一些硬件相关的因素会导致一个哈希连接的计算错误。 例如,如果你的数据类型是一个结构,结构里可能有不引人注意的填充位, 这时把这个等号操作符标记为HASHES也是不安全的。 (除非你书写你的其他操作符以确保这些未用的位总是零。这是我们建议的策略。) 另一个例子是浮点数据类型对哈希连接也是不安全的。 在符合IEEE浮点标准的机器上,负零和正零是不同的值(不同的位模式), 但是它们被定义为比较相等。所以,如果浮点等号被标记为HASHES, 一个负零和一个正零可能不被哈希连接匹配,但是用其他连接处理, 它们应该是匹配的。
底线是: 你可能只能把HASHES用于用(或可以用)memcmp() 实现的等号操作符。
注意: 在一个 hashjoinable (可散列连接)的操作符下层的函数必须 标记为 immutable (永久的)或者 stable(稳定的)。如果它是 volatile(易失的),那么系统 将从不用这些操作符于散列连接中。
注意: 如果一个 hashjoinable (可散列连接)有一个下层函数标记为严格的(strict), 那么该函数必须完整:也就是说,对于任何非 NULL 输入,它应该返回 TRUE 或者 FALSE,决不能是 NULL。 如果不遵循这个规则,IN 操作的散列优化可能会生成错误的结果。 (特别是,根据规范,正确的答案可能是 NULL 的时候,IN 可能会返回 FALSE; 或者它可能生成一个错误,抱怨说它对 NULL 结果没有思想准备。)
31.13.6. MERGES (SORT1, SORT2, LTCMP, GTCMP)
如果出现了SORT子句, 则告诉系统对基于目前的操作符可以使用融合连接方法。 MERGES只是对返回 boolean 的双目操作符有意义, 实际上这个操作符对于某些数据类型或者某对数据类型必须表示相等。 融合连接是以这样的概念为基础的: 对左边和右边的表进行排序,然后并行地扫描它们。所以,两种数据类型都必须是能够完全排序的, 并且连接操作符必须只对那些落在排序顺序中的"某个位置"的数值对成功。 实际上这意味着连接操作符必须表现得象等于。 但是和哈希连接不同,哈希连接里左边和右边的数据类型最好是相同的(至少是按位相等), 可以对两种不同数据类型进行融合连接 -- 只要他们逻辑相等即可。 例如,smallint对integer的相等操作符是可以用融合连接的。 我们只需要可以把两种数据类型排列成逻辑可比的序列的排序操作符即可。
融合连接地执行要求系统可以标识四种与融和连接相等性操作符相关的操作符: 用于左手边操作数数据类型地小于比较,用于右手边操作数数据类型的小于比较, 在两种数据类型之间的小于比较,以及在两种数据类型之间的大于比较。 (如果可以融和连接的操作符有两个不同的操作数数据类型,那么这里实际上有四种不同的操作符; 但是如果操作数类型相同,那么三个小于操作符都是相同的操作符。) 我们可以通过名字逐个声明这些操作符,分别是 SORT1, SORT2,LTCMP,以及 GTCMP 选项。 如果在声明了 MERGES 的同时却省略了其中的任何一个, 那么系统将填充缺省的名字 <,<,<, >。同样,如果这四种操作符选项中的任何出现, 那么将假设MERGES 为隐含的, 因此我们可以只声明其中一部分操作符然后让系统填充其它的。
四种比较操作符的操作数类型可以从可融合连接的操作符的操作数类型归纳出来, 因此,和 COMMUTATOR 一样,我们只需要在这些子句中给出操作符名。 除非你使用了特定选取的操作符名,那么写一个 MERGES 然后让系统填充细节就足够了。(和 COMMUTATOR 以及 NEGATOR 一样,如果你碰巧在其它操作符前定义了相等性操作符,那么系统可以制作伪操作符记录。)
还有一些对你标记为可融合连接的操作符的附加限制。 这些限制目前没有被CREATE OPERATOR检查, 但是如果下面之一不为真的话,融合连接会在运行时失败:
可融合连接的相等操作符必须有一个交换符 (如果两个操作数数据类型相同则是它自身,如果不同则是一个相关的相等操作符)。
如果有一种可融合连接的操作符与两种数据类型 A 和 B 相关, 并且另外一个可融合连接的操作符与 B 和任意第三种数据类型 C 相关,那么 A 和 C 也一定有一种可融合连接的操作符;换句话来说, 一个可融合的操作符必须是可传递的。
如果你命名的这四种操作符不能完全对数据值排序,那么在运行时就会发生非常怪异的结果。
注意: 在一个可融合连接(mergejoinable)的操作符下层的函数必须标记为永久(immutable)或者稳定(stable)。 如果它是易失的,那么系统将从不使用它们用于融合连接。 注意: GROUP BY 和 DISTINCT 操作要求每个被分组或者比较的数据类型都有一个可融合连接的相等性操作符,叫做 =。 相等性操作符和他的关联 SORT1 操作符用于实现这些操作。 同样,相关的 SORT1 操作符是 ORDER BY 的缺省排序操作符。
注意: 注意,在 PostgreSQL 版本 7.3 以前, 所写 MERGES 并不存在:要写一个可融合连接的操作符, 我们必须明确写出 SORT1 和 SORT2。同样, LTCMP 和 GTCMP 选项并不存在; 这些操作符分别写成了硬代码 < 和 >。
扩展索引接口
首先,我们需要一个操作符集合。 对这个用于 B-tree 的 complex_abs_ops 操作符表, 我们需要的操作符是: absolute-value less-than (strategy 1)
absolute-value less-than-or-equal (strategy 2) absolute-value equal (strategy 3) absolute-value greater-than-or-equal (strategy 4) absolute-value greater-than (strategy 5) 用于相等操作的 C 代码看起来像这样:
#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y)
bool
complex_abs_eq(Complex *a, Complex *b) { double amag = Mag(a), bmag = Mag(b); return (amag==bmag); } 其它四个操作符非常类似.你可以在源代码的 src/tutorial/complex.c 和 src/tutorial/complex.sql 获取代码。 然后基于这些函数声明函数和操作符:
CREATE FUNCTION complex_abs_eq(complex, complex) RETURNS boolean
AS 'filename', 'complex_abs_eq' LANGUAGE C; CREATE OPERATOR = (
leftarg = complex, rightarg = complex, procedure = complex_abs_eq, restrict = eqsel, join = eqjoinsel ); CREATE OPERATOR < ( leftarg = complex,
rightarg = complex,
procedure = complex_abs_lt,
commutator = > ,
negator = >= ,
restrict = scalarltsel,
join = scalarltjoinsel
);
声明限制和连接选择性函数是非常重要的,否则优化器将无法有效地 利用索引。请注意,小于,等于和大于的情况下应该使用不同的选择性 函数。 其它几个值得一提的问题问题要在这里出现∶
我们可以只有一个命名了的操作符名,就是 =, 并把类型 complex 做为其两个操作数.这种情况下我们没有其它用于 complex 的 = 操作符,但是如果我们要制作一个实用的数据类型, 我们可能需要=做为用于复数的普通等于操作的操作符. 这种情况,我们可能需要使用一些其他操作符名称来命名 complex_abs_eq.
尽管PostgreSQL可以处理同名操作符, 只要它们的输入数据类型不同, 而 C 只能处理一个具有给出名称的全局过程. 因此我们不能把 C 函数命名为象 abs_eq 这样简单的名字. 通常在 C 函数名里面包含数据类型名称是一个好习惯, 这样就不会和用于其它数据类型的函数冲突.
我们可以制作叫 abs_eq 的 PostgreSQL 函数, 依靠 PostgreSQL 通过输入数据类型的不同区分任何其他同名 PostgreSQL函数. 为了令例子简单,我们做的函数在 C 层次和 PostgreSQL层次都有相同的名称.
下一步是注册 B-tree 需要的比较"支持过程"。 实现这个的例子 C 代码在包含操作符过程的同一个文件中, 下面是定义函数的方法: CREATE FUNCTION complex_abs_cmp(complex, complex)
RETURNS integer AS 'filename' LANGUAGE C; 既然我们已经有了需要的操作符何支持过程, 我们就可以最后创建这个操作符表了:
CREATE OPERATOR CLASS complex_abs_ops
DEFAULT FOR TYPE complex USING btree AS OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 complex_abs_cmp(complex, complex); 这样我们就完成了!现在我们可以在一个 complex 列上创建和使用 B-tree 索引了.
我们可以把操作符记录写得更冗余一些,象
OPERATOR 1 < (complex, complex) ,
但是如果该操作符接受的数据类型是我们定义的操作符表处理的东西, 那就没必要这么做。 上面的例子假设你象把这个新操作符表作为 complex 数据类型 的缺省的 B-tree 操作符表。如果你不想这么做,只要去掉关键字 DEFAULT 就行了。
|
|||
|
|