自己动手写数据库:SQL查询处理的基础准备工作
Posted tyler_download
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自己动手写数据库:SQL查询处理的基础准备工作相关的知识,希望对你有一定的参考价值。
使用过关系型数据库的同学都会了解SQL语言,它是数据库查询模块的一部分,这门语言能够描述用户希望获取哪些数据,对数据进行怎样的加工等。SQL语言基于一种叫关系代数的逻辑,这种逻辑基于三种底层操作,分别是select, project,和product,三种操作的输入都是一张或若干张表,select处理结果是输出与输入相同的表,不过去除了表中的若干行。project输出结果是去除了输入表的若干列;product输出结果是输入中那些表中所有记录的可能组合。
我们暂时不对关系代数做过多讨论,它的具体内容会在我们后续代码实现中呈现出来。从代码的角度而言,当三种操作执行完毕后,我们使用可以使用一个统一的接口来描述操作的结果,在我们的项目根目录下创建一个文件夹叫query,在里面添加一个名为interface.go的文件,然后增加内容如下:
package query
import (
"record_manager"
)
type Scan interface
BeforeFirst()
Next() bool
GetInt(fldName string) int
GetString(fldName string) string
//本模块会自己定义一个Constant对象
GetVal(fldName string) *Constant
HasField(fldName string) bool
Close()
我们在前面章节中曾经实现过一个叫TableScan的接口,上面的定义跟TableScan没什么大区别,因为这两个接口都是对表的操作.Scan对象其实是对SQL语句执行结果的抽象表示,有过数据库应用和开发的同学会了解到,SQL执行返回的结果可能对应数据库表里面记录,另一种可能返回的就是视图,它实际上是数据记录经过特定处理后的表现形式,它并不对应实际存在在硬盘上的数据,因此SQL执行后的结果有些情况是不能修改,有些就能修改,例如select 语句执行后的结果就能进行修改。
因此我们还需要在Scan的基础上再创建一个新接口用于修改SQL语句执行后的结果,因此我们再创建一个接口叫UpdateScan,代码如下:
type UpdateScan interface
Scan
SetInt(fldName string, val int)
SetString(fldName string, val string)
SetVal(fldName string, val record_manager.Constant)
Insert()
Delete()
GetRid() record_manager.RID
MoveToRid(rid record_manager.RID)
上面接口的具体实现还需要我们了解其他概念,由于他们用于实现SQL语句指定的操作,那么我们首先必须要有接口来对应SQL语句的操作。第一个用于描述SQL语句的对象叫Predicates,它用来表示where 语句后面的查询条件。假设我们有查寝语句如下:
where (GradYear > 2021 or MOD(GradYear, 4) = 0) and MajorId = DId
其中“(GradYear > 2021 or MOD(GradYear, 4) = 0) and MajorId = DId”想要执行的操作,我们在代码中就创建一个Predicate对象来描述。这里问题变得开始有些棘手,因为接下来的分析将涉及到编译原理的内容,首先我们可以发现where 后面的语句可以通过or, and关键字分成几个组成部分,例如GradYear > 2021, MOD(GradYear,4) = 0, MajorId = DId,这些部分我们用一个Term来表示。
我们再看GradYear > 2021, 它由一个操作符>将其分成两部分,分别是左边的GradYear 和 右边的2021。另外MOD(GradYear, 4) = 0则由=号将其分成左右两部分,于是我们给这些部分用expression来表示。
接下来我们再分解expression,对于MOD(GradYear, 4),我们可以分成MOD, GradYear, 4, 其中MOD指定了一种操作,这种成分我们称之为operation,GradYear是一个字段名,用field name表示,然后“4”是一个常量,用constant表示。
为了更好理解,我们看一个具体例子,对于Predicate:
SName = 'joe' and MajorId = DId
我们用一段伪码来看看如何构造一个Predicate对象:
//创建表达式SName = 'joe'
1hs1 := NewExpression("SName")
c := Constant("joe")
rhs1 = NewExpression(c)
t1 = NewTerm(1hs1, rhs1)
//创建表达式MajorId = DId
1hs2 := NewExpression("MajorId")
rhs2 := NewExpression("DId")
t2 = NewTerm(1hs2, rhs2)
pred1 := NewPredicate(t1)
pred2 := NewPredicate(t2)
pred1.ConjoinWith(pred2)
下面我们看看Constant, Expression, Term对应代码实现,首先创建constant.go文件,实现代码如下:
package query
import (
"strconv"
)
type Constant struct
ival *int
sval *string
func NewConstantWithInt(ival *int) *Constant
return &Constant
ival: ival,
sval: nil,
func NewConstantWithString(sval *string) *Constant
return &Constant
ival: nil,
sval: sval,
func (c *Constant) AsInt() int
return *c.ival
func (c *Constant) AsString() string
return *c.sval
func (c *Constant) Equals(obj *Constant) bool
if c.ival != nil && obj.ival != nil
return *c.ival == *obj.ival
if c.sval != nil && obj.sval != nil
return *c.sval == *obj.sval
return false
func (c *Constant) ToString() string
if c.ival != nil
return strconv.FormatInt((int64)(*c.ival), 10)
return *c.sval
下面我们看看Expression的实现,创建文件expression.go,实现代码如下:
package query
import (
"record_manager"
)
type Expression struct
val *Constant
fldName string
func NewExpressionWithConstant(val *Constant) *Expression
return &Expression
val: val,
fldName: "",
func NewExpressionWithString(fldName string) *Expression
return &Expression
val: nil,
fldName: fldName,
func (e *Expression) IsFieldName() bool
return e.fldName != ""
func (e *Expression) AsConstant() *Constant
return e.val
func (e *Expression) AsFieldName() string
return e.fldName
func (e *Expression) Evaluate(s Scan) *Constant
/*
expression 有可能对应一个常量,或者对应一个字段名,如果是后者,那么我们需要查询该字段对应的具体值
*/
if e.val != nil
return e.val
return s.GetVal(e.fldName)
func (e *Expression) AppliesTo(sch *record_manager.Schema) bool
if e.val != nil
return true
return sch.HasFields(e.fldName)
func (e *Expression) ToString() string
if e.val != nil
return e.val.ToString()
return e.fldName
下面我们看看term的实现,创建一个文件叫term.go,实现代码如下:
package query
import (
"math"
"record_manager"
)
type Term struct
lhs *Expression
rhs *Expression
func NewTerm(lhs *Expression, rhs *Expression) *Term
return &Term
lhs,
rhs,
func (t *Term) IsSatisfied(s Scan) bool
lhsVal := t.lhs.Evaluate(s)
rhsVal := t.rhs.Evaluate(s)
return rhsVal.Equals(lhsVal)
func (t *Term) AppliesTo(sch *record_manager.Schema) bool
return t.lhs.AppliesTo(sch) && t.rhs.AppliesTo(sch)
func (t *Term) ReductionFactor(p *Plan) int
//Plan是后面我们研究SQL解析执行时才创建的对象,
lhsName := ""
rhsName := ""
if t.lhs.IsFieldName() && t.rhs.IsFieldName()
lhsName = t.lhs.AsFieldName()
rhsName = t.rhs.AsFieldName()
if p.DistanctValues(lhsName) > p.DistanctValues(rhsName)
return p.DistanctValues(lhsName)
return p.DistanctValues(rhsName)
if t.lhs.IsFieldName()
lhsName = t.lhs.AsFieldName()
return p.DistanctValues(lhsName)
if t.rhs.IsFieldName()
rhsName = t.rhs.AsFieldName()
return p.DistanctValues(rhsName)
if t.lhs.AsConstant().Equals(t.rhs.AsConstant())
return 1
else
return math.MaxInt
func (t *Term) EquatesWithConstant(fldName string) *Constant
if t.lhs.IsFieldName() && t.lhs.AsFieldName() == fldName && !t.rhs.IsFieldName()
return t.rhs.AsConstant()
else if t.rhs.IsFieldName() && t.rhs.AsFieldName() == fldName && !t.lhs.IsFieldName()
return t.lhs.AsConstant()
else
return nil
func (t *Term) EquatesWithField(fldName string) string
if t.lhs.IsFieldName() && t.lhs.AsFieldName() == fldName && t.rhs.IsFieldName()
return t.rhs.AsFieldName()
else if t.rhs.IsFieldName() && t.rhs.AsFieldName() == fldName && t.lhs.IsFieldName()
return t.lhs.AsFieldName()
return ""
func (t *Term) ToString() string
return t.lhs.ToString() + "=" + t.rhs.ToString()
上面实现的Term代码目前不好理解,原因在于它的逻辑需要我们掌握后面的内容才能理解,同时它用到了一个我们后面研究SQL解析执行时才会用到的对象Plan,因此这里的代码先给出,我们研究后续内容后,在回过头来看就会更好掌握。
接下来我们看看Predicate的实现,创建一个文件名为predicate.go,实现代码如下:
package query
import (
"strconv"
)
type Constant struct
ival *int
sval *string
func NewConstantWithInt(ival *int) *Constant
return &Constant
ival: ival,
sval: nil,
func NewConstantWithString(sval *string) *Constant
return &Constant
ival: nil,
sval: sval,
func (c *Constant) AsInt() int
return *c.ival
func (c *Constant) AsString() string
return *c.sval
func (c *Constant) Equals(obj *Constant) bool
if c.ival != nil && obj.ival != nil
return *c.ival == *obj.ival
if c.sval != nil && obj.sval != nil
return *c.sval == *obj.sval
return false
func (c *Constant) ToString() string
if c.ival != nil
return strconv.FormatInt((int64)(*c.ival), 10)
return *c.sval
有了上面的代码后,我们再看看用于处理select语句执行结果的相关代码,这里先给出代码的基本实现,它的逻辑需要我们在后面详解sql语言的解析以及数据库引擎对解析结果的处理后才能更好理解,在本地目录创建一个名为select_scan.go的文件,输入代码如下:
package query
import (
"record_manager"
)
type SelectionScan struct
scan UpdateScan
pred *Predicate
func NewSelectionScan(s UpdateScan, pred *Predicate) *SelectionScan
return &SelectionScan
scan: s,
pred: pred,
func (s *SelectionScan) BeforeFirst()
s.BeforeFirst()
func (s *SelectionScan) Next() bool
for s.scan.Next()
if s.pred.IsSatisfied(s)
return true
return false
func (s *SelectionScan) GetInt(fldName string) int
return s.scan.GetInt(fldName)
func (s *SelectionScan) GetString(fldName sring) string
return s.scan.GetString(fldName)
func (s *SelectionScan) GetVal(fldName String) string
return s.scan.GetVal(fldName)
func (s *SelectionScan) HasField(fldName string) bool
return s.scan.HasField(fldName)
func (s *SelectionScan) Close()
s.scan.Close()
func (s *SelectionScan) SetInt(fldName string, val int)
s.scan.SetInt(fildName, val)
func (s *SelectionScan) SetString(fldName string, val string)
s.scan.SetString(fldName, val)
func (s *SelectionScan) SetVal(fldName string, val *Constant)
s.scan.SetVal(fldName, val)
func (s *SelectionScan) Delete()
s.scan.Delete()
func (s *SelectionScan) Insert()
s.scan.Insert()
func (s *SelectionScan) *record_manager.RID
s.scan.GetRid()
func (s *SelectionScan)MoveToRID(rid *record_manager.RID)
s.scan.MoveToRid(rid)
我们再创建一个文件名为project_scan.go, 它将实现接口Scan,它的内容如下:
package query
import (
"errors"
)
type ProjectScan struct
scan Scan
fieldList []string
func NewProductionScan(s Scan, fieldList []string) *ProjectScan
return &ProjectScan
scan: s,
fieldList: fieldList,
func (p *ProjectScan) BeforeFirst()
p.scan.BeforeFirst()
func (p *ProjectScan) Next() bool
return p.scan.Next()
func (p *ProjectScan) GetInt(fldName string) (int, error)
if p.scan.HasField(fldName)
return p.scan.GetInt(fldName), nil
return 0, errors.New("Field Not Found")
func (p *ProjectScan) GetString(fldName string) (string, error)
if p.scan.HasField(fldName)
return p.scan.GetString(fldName), nil
return ""自己动手写把”锁”之---锁的作用