自己动手写数据库: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 ""自己动手写把”锁”之---锁的作用

自己动手写SQL执行引擎

自己动手写SQL执行引擎

自己动手写ORM(01):解析表达式树生成Sql碎片

:自己动手写区块链之最小可行区块链

Docker Api 实测