Write your own database system: implement a small SQL interpreter (Part 2)

We continue to complete the code analysis work of the SQL interpreter following the previous section. Below we implement the analysis of the update statement, the syntax is as follows:
UpdateCmd -> INSERT | DELETE | MODIFY | CREATE
Create -> CreateTable | CreateView | CreateIndex
Insert -> INSERT INTO ID LEFT_PARAS FieldList RIGHT_PARAS VALUES LEFT_PARS ConstList RIGHT_PARAS
FieldList -> Fie ld (COMMA FieldList)?
ConstList -> Constant (COMMA ConstList)?
Delete -> DELETE FROM ID [WHERE Predicate)?
Modify -> UPDATE ID SET Field ASSIGN_OPERATOR Expression (WHERE Predicate)?
CreateTable -> CREATE TABLE ID (FieldDefs)?
FieldDefs -> FieldDef ( COMMA FieldDefs )?
FieldDef -> ID TypeDef
TypeDef -> INT | VARCHAT LEFT_PARAS NUM RIGHT_PARAS
CreateView -> CREATE VIEW ID AS Query
CreateIndex -> CREATE INDEX ID ON ID LEFT_PARAS Field RIGHT_PARAS

Let's make some basic explanations for the above syntax:
UpdateCmd -> INSERT | DELETE | MODIFY | CREATE
This syntax indicates that the statement used to update the table in the SQL language must start with several commands such as insert, delete, modify, and create. The insert statement starts with the keyword insert, followed by the two keywords insert into, followed by a left parenthesis, followed by a string of column names separated by commas, followed by a right parenthesis, followed by The keyword VALUES, followed by a left parenthesis, followed by a series of constants and commas, and finally ended with another parenthesis. For other syntax, you can refer to SQL related commands to understand. Let’s look at the implementation of the code and continue in parser.go Add the following code in:

func (p *SQLParser) UpdateCmd() interface{
    
    } {
    
    
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}

	if tok.Tag == lexer.INSERT {
    
    
		p.sqlLexer.ReverseScan()
		return p.Insert()
	} else if tok.Tag == lexer.DELETE {
    
    
		p.sqlLexer.ReverseScan()
		return p.Delete()
	} else if tok.Tag == lexer.UPDATE {
    
    
		p.sqlLexer.ReverseScan()
		return p.Update()
	} else {
    
    
		p.sqlLexer.ReverseScan()
		return p.Create()
	}
}

func (p *SQLParser) Create() interface{
    
    } {
    
    
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}
	if tok.Tag != lexer.CREATE {
    
    
		panic("token is not create")
	}

	tok, err = p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}

	if tok.Tag == lexer.TABLE {
    
    
		return p.CreateTable()
	} else if tok.Tag == lexer.VIEW {
    
    
		return p.CreateView()
	} else {
    
    
		return p.CreateIndex()
	}
}

func (p *SQLParser) CreateView() interface{
    
    } {
    
    
	return nil
}

func (p *SQLParser) CreateIndex() interface{
    
    } {
    
    
	return nil
}

func (p *SQLParser) CreateTable() interface{
    
    } {
    
    
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}
	if tok.Tag != lexer.ID {
    
    
		panic("token should be ID for table name")
	}

	tblName := p.sqlLexer.Lexeme
	tok, err = p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}
	if tok.Tag != lexer.LEFT_BRACKET {
    
    
		panic("missing left bracket")
	}
	sch := p.FieldDefs()
	tok, err = p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}
	if tok.Tag != lexer.RIGHT_BRACKET {
    
    
		panic("missing right bracket")
	}

	return NewCreateTableData(tblName, sch)
}

func (p *SQLParser) FieldDefs() *record_manager.Schema {
    
    
	schema := p.FieldDef()
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}
	if tok.Tag == lexer.COMMA {
    
    
		schema2 := p.FieldDefs()
		schema.AddAll(schema2)
	} else {
    
    
		p.sqlLexer.ReverseScan()
	}

	return schema
}

func (p *SQLParser) FieldDef() *record_manager.Schema {
    
    
	_, fldName := p.Field()
	return p.FieldType(fldName)
}

func (p *SQLParser) FieldType(fldName string) *record_manager.Schema {
    
    
	schema := record_manager.NewSchema()
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}

	if tok.Tag == lexer.INT {
    
    
		schema.AddIntField(fldName)
	} else if tok.Tag == lexer.VARCHAR {
    
    
		tok, err := p.sqlLexer.Scan()
		if err != nil {
    
    
			panic(err)
		}
		if tok.Tag != lexer.LEFT_BRACKET {
    
    
			panic("missing left bracket")
		}

		tok, err = p.sqlLexer.Scan()
		if err != nil {
    
    
			panic(err)
		}

		if tok.Tag != lexer.NUM {
    
    
			panic("it is not a number for varchar")
		}

		num := p.sqlLexer.Lexeme
		fldLen, err := strconv.Atoi(num)
		if err != nil {
    
    
			panic(err)
		}
		schema.AddStringField(fldName, fldLen)

		tok, err = p.sqlLexer.Scan()
		if err != nil {
    
    
			panic(err)
		}
		if tok.Tag != lexer.RIGHT_BRACKET {
    
    
			panic("missing right bracket")
		}
	}

	return schema
}

In the above code, we need to define a CreateTableData structure, so add a create_data.go file, add the code as follows:

package parser

import (
	"record_manager"
)

type CreateTableData struct {
    
    
	tblName string
	sch     *record_manager.Schema
}

func NewCreateTableData(tblName string, sch *record_manager.Schema) *CreateTableData {
    
    
	return &CreateTableData{
    
    
		tblName: tblName,
		sch:     sch,
	}
}

func (c *CreateTableData) TableName() string {
    
    
	return c.tblName
}

func (c *CreateTableData) NewSchema() *record_manager.Schema {
    
    
	return c.sch
}

Finally, we add code in main.go and call the above code to achieve:

package main

//import (
//	bmg "buffer_manager"
//	fm "file_manager"
//	"fmt"
//	lm "log_manager"
//	"math/rand"
//	mm "metadata_management"
//	record_mgr "record_manager"
//	"tx"
//)

import (
	"parser"
)

func main() {
    
    
	sql := "create table person (PersonID int, LastName varchar(255), FirstName varchar(255)," +
		"Address varchar(255), City varchar(255) )"
	sqlParser := parser.NewSQLParser(sql)
	sqlParser.UpdateCmd()

}

In main, we define a create table sql statement, and then call the UpdateCmd interface to implement syntax analysis. You can search for "coding Disney" at station b, and view the code debugging demo video, because the logic of the above syntax analysis is a bit complicated and cumbersome , so the single-step debugging process of the code can be tracked through the video to understand the implementation logic more easily and effortlessly.

Let's take a look at the parsing implementation of the insert statement, and add the code in parser.go as follows:

func (p *SQLParser) checkWordTag(wordTag lexer.Tag) {
    
    
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}
	if tok.Tag != wordTag {
    
    
		panic("token is not match")
	}
}

func (p *SQLParser) isMatchTag(wordTag lexer.Tag) bool {
    
    
	tok, err := p.sqlLexer.Scan()
	if err != nil {
    
    
		panic(err)
	}

	if tok.Tag == wordTag {
    
    
		return true
	} else {
    
    
		p.sqlLexer.ReverseScan()
		return false
	}
}


func (p *SQLParser) fieldList() []string {
    
    
	L := make([]string, 0)
	_, field := p.Field()
	L = append(L, field)
	if p.isMatchTag(lexer.COMMA) {
    
    
		fields := p.fieldList()
		L = append(L, fields...)
	}

	return L
}

func (p *SQLParser) constList() []*query.Constant {
    
    
	L := make([]*query.Constant, 0)
	L = append(L, p.Constant())
	if p.isMatchTag(lexer.COMMA) {
    
    
		consts := p.constList()
		L = append(L, consts...)
	}

	return L
}

func (p *SQLParser) Insert() interface{
    
    } {
    
    
	/*
		根据语法规则:Insert -> INSERT INTO ID LEFT_PARAS FieldList RIGHT_PARAS VALUES LEFT_PARS ConstList RIGHT_PARAS
		我们首先要匹配四个关键字,分别为insert, into, id, 左括号,
		然后就是一系列由逗号隔开的field,
		接着就是右括号,然后是关键字values
		接着是常量序列,最后以右括号结尾
	*/
	p.checkWordTag(lexer.INSERT)
	p.checkWordTag(lexer.INTO)
	p.checkWordTag(lexer.ID)
	tblName := p.sqlLexer.Lexeme
	p.checkWordTag(lexer.LEFT_BRACKET)
	flds := p.fieldList()
	p.checkWordTag(lexer.RIGHT_BRACKET)
	p.checkWordTag(lexer.VALUES)
	p.checkWordTag(lexer.LEFT_BRACKET)
	vals := p.constList()
	p.checkWordTag(lexer.RIGHT_BRACKET)

	return NewInsertData(tblName, flds, vals)
}

Let's call the above code to test the parsing effect:

func main() {
    
    
	
	sql := "INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country) " +
		"VALUES (\"Cardinal\", \"Tom B. Erichsen\", \"Skagen 21\", \"Stavanger\", 4006, \"Norway\")"
	sqlParser := parser.NewSQLParser(sql)
	sqlParser.UpdateCmd()

} 

Please search for coding Disney on station b, and you can understand the code logic more straightforwardly and effectively through video debugging demonstrations. Next, let's see how the create command creates two objects, view and index. First, let's look at the creation of view, according to the syntax of create view:

CreateView -> CREATE VIEW ID AS QUERY

First of all, we need to judge whether the first two tokens of the statement correspond to the keywords CREATE, VIEW, and then the following token must be of ID type, followed by the keyword AS, and finally we call the parsing rule corresponding to QUERY to parse the following string, we see Look at the code implementation, add the following code in parser.go:

func (p *SQLParser) CreateView() interface{
    
    } {
    
    
	p.checkWordTag(lexer.ID)
	viewName := p.sqlLexer.Lexeme
	p.checkWordTag(lexer.AS)
	qd := p.Query()

	vd := NewViewData(viewName, qd)
	vdDef := fmt.Sprintf("vd def: %s", vd.ToString())
	fmt.Println(vdDef)
	return vd
}

Then add the file create_view.go and add the following code:

package parser

import "fmt"

type ViewData struct {
	viewName  string
	queryData *QueryData
}

func NewViewData(viewName string, qd *QueryData) *ViewData {
	return &ViewData{
		viewName:  viewName,
		queryData: qd,
	}
}

func (v *ViewData) ViewName() string {
	return v.viewName
}

func (v *ViewData) ViewDef() string {
	return v.queryData.ToString()
}

func (v *ViewData) ToString() string {
	s := fmt.Sprintf("view name %s, viewe def: %s", v.viewName, v.ViewDef())
	return s
}

Finally, we add the following test code in main.go:

func main() {
	//sql := "create table person (PersonID int, LastName varchar(255), FirstName varchar(255)," +
	//	"Address varchar(255), City varchar(255) )"

	sql := "create view Customer as select CustomerName, ContactName from customers where country=\"China\""
	sqlParser := parser.NewSQLParser(sql)
	sqlParser.UpdateCmd()

}

After running the above code, the result is as follows:

vd def: view name Customer, viewe def: select CustomerName, ContactName, from customers, where  and country=China

For more detailed content, please search coding Disney at station b. Let's take a look at the syntax analysis of index creation. The corresponding syntax is:

CreateIndex -> CREATE INDEX ID ON ID LEFT_BRACKET Field RIGHT_BRACKET

It can be seen from the grammar rules that when parsing, we need to judge that the statement must start with the two keywords CREATE INDEX, and then the following string must meet the definition of ID, and then it needs to be followed by the keyword ON, and then the following string To meet the definition of ID, the next character to be read must be a left parenthesis, and then the following content must meet the definition of Field, and finally end with a right parenthesis. Let's see the code implementation and add the following code in parser.go:

func (p *SQLParser) Create() interface{} {
	tok, err := p.sqlLexer.Scan()
	if err != nil {
		panic(err)
	}
	if tok.Tag != lexer.CREATE {
		panic("token is not create")
	}

	tok, err = p.sqlLexer.Scan()
	if err != nil {
		panic(err)
	}

	if tok.Tag == lexer.TABLE {
		return p.CreateTable()
	} else if tok.Tag == lexer.VIEW {
		return p.CreateView()
	} else if tok.Tag == lexer.INDEX {
		return p.CreateIndex()
	}

	panic("sql string with create should not end here")
}

func (p *SQLParser) CreateIndex() interface{} {
	p.checkWordTag(lexer.ID)
	idexName := p.sqlLexer.Lexeme
	p.checkWordTag(lexer.ON)
	p.checkWordTag(lexer.ID)
	tableName := p.sqlLexer.Lexeme
	p.checkWordTag(lexer.LEFT_BRACKET)
	_, fldName := p.Field()
	p.checkWordTag(lexer.RIGHT_BRACKET)

	idxData := NewIndexData(idexName, tableName, fldName)
	fmt.Printf("create index result: %s", idxData.ToString())
	return idxData
}

Create a new create_index_data.go file and add the following code in it:

package parser

import "fmt"

type IndexData struct {
	idxName string
	tblName string
	fldName string
}

func NewIndexData(idxName string, tblName string, fldName string) *IndexData {
	return &IndexData{
		idxName: idxName,
		tblName: tblName,
		fldName: fldName,
	}
}

func (i *IndexData) IdexName() string {
	return i.idxName
}

func (i *IndexData) tableName() string {
	return i.tblName
}

func (i *IndexData) fieldName() string {
	return i.fldName
}

func (i *IndexData) ToString() string {
	str := fmt.Sprintf("index name: %s, table name: %s, field name: %s", i.idxName, i.tblName, i.fldName)
	return str
}

In main.go, we use the create index statement in the sql statement to test the implementation of the above code:

func main() {
	//sql := "create table person (PersonID int, LastName varchar(255), FirstName varchar(255)," +
	//	"Address varchar(255), City varchar(255) )"

	sql := "create index idxLastName on persons (lastname)"
	sqlParser := parser.NewSQLParser(sql)
	sqlParser.UpdateCmd()

}

The result after running the above code is as follows:

create index result: index name: idxLastName, table name: persons, field name: lastname

At this point, all the analysis of the create statement is basically completed. For more debugging demonstrations and code logic explanations, please search for coding Disney at station b

Guess you like

Origin blog.csdn.net/tyler_download/article/details/131292896