自己动手写数据库: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 "", errors.New("Field Not Found")
}

func (p *ProjectScan) GetVal(fldName string) (*Constant, error) {
	if p.scan.HasField(fldName) {
		return p.scan.GetVal(fldName), nil
	}

	return nil, errors.New("Field Not Found")
}

func (p *ProjectScan) HasField(fldName string) bool {
	for _, s := range p.fieldList {
		if s == fieldName {
			return true
		}
	}

	return false
}

func (p *ProjectScan) Close() {
	p.scan.Close()
}

最后我们再看看product操作的实现,创建product_scan.go,实现代码如下:
```go
package query

type ProductScan struct {
scan1 Scan
scan2 Scan
}

func NewProductScan(s1 Scan, s2 Scan) *ProductScan {
p := &ProductScan{
scan1: s1,
scan2: s2,
}

p.scan1.Next()
return p 

}

func (p *ProductScan) BeforeFirst() {
p.scan1.BeforeFirst()
p.scan1.Next()
p.scan2.BeforeFirst()
}

func (p *ProductScan)Next() bool {
if p.scan2.Next() {
return true
} else {
p.scan2.BeforeFirst()
return p.scan2.Next() && p.scan1.Next()
}
}

func (p *ProductScan) GetInt(fldName string) int {
if p.scan1.HasField(fldName) {
return p.scan1.GetInt(fldName)
} else {
return p.scan2.GetInt(fldName)
}
}

func (p *ProductScan) GetString(fldName string) string {
if p.scan1.HasField(fldName) {
return p.scan1.GetString(fldName)
} else {
return p.scan2.GetString(fldName)
}
}

func (p *ProductScan) GetVal(fldName string) *Constant {
if p.scan1.HasField(fldName) {
return p.scan1.GetVal(fldName)
} else {
return p.scan2.GetVal(fldName)
}
}

func (p *ProductScan) HasField(fldName string) bool {
return p.scan1.HasField(fldName) || p.scan2.HasField(fldName)
}

func (p *ProductScan) Close() {
p.scan1.Close()
p.scan2.Close()
}

```
本节我们给出的代码主要是被后续SQL解析执行时被调用,因此在没有后面内容基础前,这些代码的逻辑不好理解,在后续我们完成SQL解析执行的描述后,我们会调用上面代码,到时候逻辑就会更加清晰,更多内容请在B站搜索"coding迪斯尼"。

你可能感兴趣的:(数据库,sql,算法)