[手写系列]Go手写db --- --- 第五版(实现数据库操作模块)
第一版文章:[手写系列]Go手写db --- --- 完整教程_go手写数据库-CSDN博客
第二版文章:[手写系列]Go手写db --- --- 第二版-CSDN博客
第三版文章:[手写系列]Go手写db --- --- 第三版(实现分组、排序、聚合函数等)-CSDN博客
第四版文章:[手写系列]Go手写db --- --- 第四版(实现事务、网络模块)
整体项目Github地址:https://github.com/ziyifast/ZiyiDB
- 请大家多多支持,也欢迎大家star⭐️和共同维护这个项目~
本文主要介绍如何在 ZiyiDB 第四版的基础上,实现数据库层面的操作,包括创建、删除、切换数据库以及数据库与表的关联管理等,通过这些功能,将使得ZiyiDB成为一个更完整的数据库系统。
一、功能列表
- 新增对数据库的创建(CREATE DATABASE)支持
- 新增对数据库的删除(DROP DATABASE)支持
- 新增对数据库列表的展示(SHOW DATABASES)支持
- 新增对表列表的展示(SHOW TABELS)支持
- 新增对数据库切换(USE DATABASE)支持
- 实现数据库与表的关联管理。存储引擎操作数据库,然后由数据库结构体间接操作表
二、实现细节
功能点一:实现数据库的创建、删除
实现思路
- internal/lexer/token.go新增DATABASE关键字,因为drop和create关键字之前已经有了
- internal/lexer/lexer.go中的lookupIdentifier方法新增一个case返回,用于将用户输入的字符转换为TokenType
- internal/ast/ast.go语法树中新增create database和drop database语法树结构
- internal/parser/parser.go语法解析器中的create和drop case中新增对database的处理
然后分别实现parseCreateDatabaseStatement、parseDropDatabaseStatement方法,方便后续构建抽象语法树
- internal/storage/memory.go存储引擎
- 新增数据库结构体的定义,同时存储引擎直接管理数据库,数据表交给Database结构体管理
- 实现底层对数据库创建、删除的方法
- cmd/main.go中executor方法新增case处理对用户对数据库创建、删除的操作
PS:network/server.go网络服务端修改同理,这里不做赘述

代码实现
1. 词法分析器调整
internal/lexer/token.go:
go
// internal/lexer/token.go
package lexer
// TokenType 表示词法单元类型
type TokenType string
const (
...
DATABASE TokenType = "DATABASE"
...
)
// Token 词法单元
// Type:标记的类型(如 SELECT、IDENT 等)
// Literal:标记的实际值(如具体的列名、数字等)
type Token struct {
Type TokenType // 标记类型
Literal string // 标记的实际值
}
internal/lexer/lexer.go:
go
// internal/lexer/lexer.go
package lexer
import (
"bufio"
"bytes"
"io"
"strings"
"time"
"unicode"
)
...
// lookupIdentifier 查找标识符类型
// 将标识符转换为对应的标记类型
// 识别 SQL 关键字
func (l *Lexer) lookupIdentifier(ident string) TokenType {
switch strings.ToUpper(ident) {
...
case "DATABASE":
return DATABASE
...
default:
return IDENT
}
}
...
2. 抽象语法树调整
internal/ast/ast.go:
go
...
// CreateDatabaseStatement 表示CREATE DATABASE语句
type CreateDatabaseStatement struct {
Token lexer.Token
Name string
}
func (cds *CreateDatabaseStatement) statementNode() {}
func (cds *CreateDatabaseStatement) TokenLiteral() string { return cds.Token.Literal }
// DropDatabaseStatement
type DropDatabaseStatement struct {
Token lexer.Token
Name string
}
func (dds *DropDatabaseStatement) statementNode() {}
func (dds *DropDatabaseStatement) TokenLiteral() string { return dds.Token.Literal }
...
3. 语法解析器调整
go
// parseStatement 解析语句
// 根据当前标记类型选择相应的解析方法
func (p *Parser) parseStatement() (ast.Statement, error) {
// 跳过注释
for p.curToken.Type == lexer.COMMENT {
p.nextToken()
}
switch p.curToken.Type {
//需要区分是创建表还是创建数据库
case lexer.CREATE:
if p.peekTokenIs(lexer.TABLE) {
return p.parseCreateTableStatement()
} else if p.peekTokenIs(lexer.DATABASE) {
return p.parseCreateDatabaseStatement()
}
return nil, fmt.Errorf("expected TABLE or DATABASE after CREATE")
...
case lexer.DROP:
if p.peekTokenIs(lexer.TABLE) {
return p.parseDropTableStatement()
} else if p.peekTokenIs(lexer.DATABASE) {
return p.parseDropDatabaseStatement()
}
return nil, fmt.Errorf("expected TABLE or DATABASE after DROP")
case lexer.SEMI:
return nil, nil
default:
return nil, fmt.Errorf("You have an error in your SQL syntax; check the manual that corresponds to your db server version for the right syntax to use near '%s'", p.curToken.Type)
}
}
// parseDropDatabaseStatement 解析DROP DATABASE语句
func (p *Parser) parseDropDatabaseStatement() (*ast.DropDatabaseStatement, error) {
stmt := &ast.DropDatabaseStatement{Token: p.curToken}
if !p.expectPeek(lexer.DATABASE) {
return nil, fmt.Errorf("expected DATABASE keyword")
}
if !p.expectPeek(lexer.IDENT) {
return nil, fmt.Errorf("expected database name")
}
stmt.Name = p.curToken.Literal
return stmt, nil
}
// parseCreateDatabaseStatement 解析CREATE DATABASE语句
func (p *Parser) parseCreateDatabaseStatement() (*ast.CreateDatabaseStatement, error) {
stmt := &ast.CreateDatabaseStatement{Token: p.curToken}
if !p.expectPeek(lexer.DATABASE) {
return nil, fmt.Errorf("expected DATABASE keyword")
}
if !p.expectPeek(lexer.IDENT) {
return nil, fmt.Errorf("expected database name")
}
stmt.Name = p.curToken.Literal
return stmt, nil
}
4. 存储引擎实现操作
go
// Database 表示数据库
type Database struct {
Name string
Tables map[string]*Table
mu sync.RWMutex
}
// MemoryBackend 内存存储引擎,管理所有数据库
type MemoryBackend struct {
Databases map[string]*Database
txnMgr *TransactionManager
Mu sync.RWMutex
}
// Table 数据表,包含列定义、数据行和索引
type Table struct {
Name string
Columns []ast.ColumnDefinition
Rows [][]VersionedCell // 保持为 VersionedCell
Indexes map[string]*Index
RowLocks map[int]*sync.RWMutex // 行级锁
mu sync.RWMutex
}
...
// CreateDatabase 创建数据库
func (b *MemoryBackend) CreateDatabase(stmt *ast.CreateDatabaseStatement) error {
b.Mu.Lock()
defer b.Mu.Unlock()
if _, exists := b.Databases[stmt.Name]; exists {
return fmt.Errorf("database '%s' already exists", stmt.Name)
}
b.Databases[stmt.Name] = &Database{
Name: stmt.Name,
Tables: make(map[string]*Table),
}
return nil
}
// DropDatabase 删除数据库
func (b *MemoryBackend) DropDatabase(stmt *ast.DropDatabaseStatement) error {
b.Mu.Lock()
defer b.Mu.Unlock()
if _, exists := b.Databases[stmt.Name]; !exists {
return fmt.Errorf("database '%s' does not exist", stmt.Name)
}
delete(b.Databases, stmt.Name)
return nil
}
5. 程序入口处调整
需要调整两个地方,一个是本地命令行版(cmd/main.go),一个是网络版(network/server.go)
- cmd/main.go:
go
func executor(t string) {
// 分割多个SQL语句(用分号分隔)
statements := strings.Split(t, ";")
for _, stmt := range statements {
...
// 创建词法分析器
l := lexer.NewLexer(strings.NewReader(stmt))
// 创建语法分析器
p := parser.NewParser(l)
// 解析SQL语句
parsedStmt, err := p.ParseProgram()
if err != nil {
fmt.Printf("Parse error: %v\n", err)
continue
}
// 执行SQL语句
for _, statement := range parsedStmt.Statements {
if currentDatabase == "" {
// 检查是否是非数据库操作语句
_, isCreateDB := statement.(*ast.CreateDatabaseStatement)
_, isDropDB := statement.(*ast.DropDatabaseStatement)
// 如果不是允许的语句类型,则提示需要选择数据库
if !isCreateDB && !isDropDB {
fmt.Println("No database selected. Use 'USE database_name' to select a database.")
continue
}
}
switch s := statement.(type) {
case *ast.CreateDatabaseStatement:
if err := backend.CreateDatabase(s); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Database created successfully")
}
case *ast.DropDatabaseStatement:
if err := backend.DropDatabase(s); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Database dropped successfully")
}
...
default:
fmt.Printf("Unsupported statement type: %T\n", s)
}
}
}
}
- network/server.go:
go
// network/server.go
package network
import (
"bufio"
"fmt"
"net"
"strings"
"sync"
"ziyi.db.com/internal/ast"
"ziyi.db.com/internal/lexer"
"ziyi.db.com/internal/parser"
"ziyi.db.com/internal/storage"
)
const DefaultPort = "3118"
...
func (s *Server) executeCommand(conn net.Conn, command string) string {
...
var result string
for _, statement := range parsedStmt.Statements {
//检查是否选择了数据库
if connCtx.GetDBName() == "" {
// 检查是否是非数据库操作语句
_, isCreateDB := statement.(*ast.CreateDatabaseStatement)
_, isDropDB := statement.(*ast.DropDatabaseStatement)
// 如果不是允许的语句类型,则提示需要选择数据库
if !isCreateDB && !isDropDB {
result += "No database selected. Use 'USE database_name' to select a database."
continue
}
}
switch stmt := statement.(type) {
case *ast.CreateDatabaseStatement:
if err := s.backend.CreateDatabase(stmt); err != nil {
result += fmt.Sprintf("Error: %v\n", err)
} else {
result += "Database created successfully\n"
}
case *ast.DropDatabaseStatement:
if err := s.backend.DropDatabase(stmt); err != nil {
result += fmt.Sprintf("Error: %v\n", err)
} else {
result += "Database dropped successfully\n"
}
case *ast.DropTableStatement:
if err := s.backend.DropTable(connCtx.db, stmt); err != nil {
result += fmt.Sprintf("Error: %v\n", err)
} else {
result += "Table dropped successfully\n"
}
default:
result += fmt.Sprintf("Unsupported statement type: %T\n", stmt)
}
}
return strings.TrimSpace(result)
}
测试
测试命令:
sql
-- 创建test数据库
create database test;
-- 删除test数据库
drop database test;
效果:
功能点二:use选择数据库
实现思路
-
新增internal/context/context.go文件,定义DBContext接口
-
internal/lexer/token.go新增USE关键字:
-
internal/lexer/lexer.go中lookupIdentifier新增case:
-
抽象语法树internal/ast/ast.go新增UseDatabaseStatement
-
语法解析器internal/parser/parser.go的parseStatement新增case,并实现对应case逻辑
-
存储引擎实现UseDatabase选择数据库逻辑
-
网络服务端、程序入口调用UseDatabase方法
代码实现
1. 词法分析器调整
- internal/lexer/token.go:
go
const(
USE TokenType = "USE"
...
)
...
- internal/lexer/lexer.go:
go
...
// lookupIdentifier 查找标识符类型
// 将标识符转换为对应的标记类型
// 识别 SQL 关键字
func (l *Lexer) lookupIdentifier(ident string) TokenType {
switch strings.ToUpper(ident) {
...
case "USE":
return USE
default:
return IDENT
}
}
2. 抽象语法树调整
internal/ast/ast.go:
go
...
type UseDatabaseStatement struct {
Token lexer.Token
Name string
}
func (uds *UseDatabaseStatement) statementNode() {}
func (uds *UseDatabaseStatement) TokenLiteral() string { return uds.Token.Literal }
3. 语法解析器调整
internal/parser/parser.go:
go
// parseStatement 解析语句
// 根据当前标记类型选择相应的解析方法
func (p *Parser) parseStatement() (ast.Statement, error) {
// 跳过注释
for p.curToken.Type == lexer.COMMENT {
p.nextToken()
}
switch p.curToken.Type {
...
case lexer.USE:
return p.parseUseDatabaseStatement()
...
default:
return nil, fmt.Errorf("You have an error in your SQL syntax; check the manual that corresponds to your db server version for the right syntax to use near '%s'", p.curToken.Type)
}
}
func (p *Parser) parseUseDatabaseStatement() (*ast.UseDatabaseStatement, error) {
stmt := &ast.UseDatabaseStatement{Token: p.curToken}
if !p.expectPeek(lexer.IDENT) {
return nil, fmt.Errorf("expected database name")
}
stmt.Name = p.curToken.Literal
return stmt, nil
}
...
4. 存储引擎实现操作
internal/storage/memory.go:
go
// UseDatabase 使用数据库
func (b *MemoryBackend) UseDatabase(stmt *ast.UseDatabaseStatement, connCtx context.DBContext) error {
b.Mu.RLock()
defer b.Mu.RUnlock()
if _, exists := b.Databases[stmt.Name]; !exists {
return fmt.Errorf("database '%s' does not exist", stmt.Name)
}
// 更新连接上下文中的当前数据库
connCtx.SetDBName(stmt.Name)
return nil
}
5. 程序入口处调整
需要调整两个地方,一个是本地命令行版(cmd/main.go),一个是网络版(network/server.go)
- cmd/main.go:
go
func executor(t string) {
// 分割多个SQL语句(用分号分隔)
statements := strings.Split(t, ";")
for _, stmt := range statements {
...
// 创建词法分析器
l := lexer.NewLexer(strings.NewReader(stmt))
// 创建语法分析器
p := parser.NewParser(l)
// 解析SQL语句
parsedStmt, err := p.ParseProgram()
if err != nil {
fmt.Printf("Parse error: %v\n", err)
continue
}
// 执行SQL语句
for _, statement := range parsedStmt.Statements {
if currentDatabase == "" {
// 检查是否是非数据库操作语句
_, isCreateDB := statement.(*ast.CreateDatabaseStatement)
_, isDropDB := statement.(*ast.DropDatabaseStatement)
// 如果不是允许的语句类型,则提示需要选择数据库
if !isCreateDB && !isDropDB {
fmt.Println("No database selected. Use 'USE database_name' to select a database.")
continue
}
}
switch s := statement.(type) {
...
case *ast.UseDatabaseStatement:
if err := backend.UseDatabase(s, &dbContextAdapter{¤tDatabase}); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Database changed to '%s'\n", currentDatabase)
}
...
default:
fmt.Printf("Unsupported statement type: %T\n", s)
}
}
}
}
- network/server.go:
go
// network/server.go
package network
import (
"bufio"
"fmt"
"net"
"strings"
"sync"
"ziyi.db.com/internal/ast"
"ziyi.db.com/internal/lexer"
"ziyi.db.com/internal/parser"
"ziyi.db.com/internal/storage"
)
const DefaultPort = "3118"
...
func (s *Server) executeCommand(conn net.Conn, command string) string {
...
var result string
for _, statement := range parsedStmt.Statements {
...
switch stmt := statement.(type) {
case *ast.UseDatabaseStatement:
if err := s.backend.UseDatabase(stmt, connCtx); err != nil {
result += fmt.Sprintf("Error: %v\n", err)
} else {
result += fmt.Sprintf("Database changed to '%s'\n", stmt.Name)
}
...
default:
result += fmt.Sprintf("Unsupported statement type: %T\n", stmt)
}
}
return strings.TrimSpace(result)
}
测试
测试命令:
sql
-- 创建两个数据库
create database test;
create database test2;
-- 使用数据库test并创建表
use test;
create table users (id INT PRIMARY KEY,name text,age INT);
INSERT INTO users VALUES (1, 'Alice', 20);
select * from users;
-- 预期test2数据库中没有users表
use test2;
select * from users;
效果:
功能点三:实现数据库、表列表展示
实现思路
- internal/lexer/token.go新增关键字
- internal/lexer/lexer.go lookupIdentifier方法新增一个case返回,用于将用户输入的字符转换为TokenType
- internal/ast/ast.go抽象语法树新增ShowDatabasesStatement、ShowTablesStatement,方便后续语法解析器构建抽象语法树
- internal/parser/parser.go语法解析器中实现对ShowDatabasesStatement、ShowTablesStatement抽象语法树的构建
同时在parseStatement方法中新增一个case,用于解析show相关的SQL语句
- internal/storage/memory.go存储引擎中实现show databases、show tables,底层其实就是range遍历,然后将结果放在一个切片中
- 程序入口处调整,新增对应的case,判断到对应Statement之后,通过存储引擎调用对应方法即可
- cmd/main.go:
- network/server.go:
代码实现
1. 词法分析器调整
- internal/lexer/token.go:
go
const(
...
DATABASES TokenType = "DATABASES"
SHOW TokenType = "SHOW"
TABLES TokenType = "TABLES"
)
- internal/lexer/lexer.go:
go
// lookupIdentifier 查找标识符类型
// 将标识符转换为对应的标记类型
// 识别 SQL 关键字
func (l *Lexer) lookupIdentifier(ident string) TokenType {
switch strings.ToUpper(ident) {
...
case "DATABASES":
return DATABASES
case "SHOW":
return SHOW
case "TABLES":
return TABLES
default:
return IDENT
}
}
...
2. 抽象语法树调整
internal/ast/ast.go:
go
// ShowDatabasesStatement
type ShowDatabasesStatement struct {
Token lexer.Token
}
func (sds *ShowDatabasesStatement) statementNode() {}
func (sds *ShowDatabasesStatement) TokenLiteral() string { return sds.Token.Literal }
// ShowTablesStatement
type ShowTablesStatement struct {
Token lexer.Token
}
func (sds *ShowTablesStatement) statementNode() {}
func (sds *ShowTablesStatement) TokenLiteral() string { return sds.Token.Literal }
...
3. 语法解析器调整
internal/parser/parser.go:
go
// parseStatement 解析语句
// 根据当前标记类型选择相应的解析方法
func (p *Parser) parseStatement() (ast.Statement, error) {
// 跳过注释
for p.curToken.Type == lexer.COMMENT {
p.nextToken()
}
switch p.curToken.Type {
//需要区分是创建表还是创建数据库
case lexer.CREATE:
if p.peekTokenIs(lexer.TABLE) {
return p.parseCreateTableStatement()
} else if p.peekTokenIs(lexer.DATABASE) {
return p.parseCreateDatabaseStatement()
}
return nil, fmt.Errorf("expected TABLE or DATABASE after CREATE")
case lexer.SHOW:
if p.peekTokenIs(lexer.DATABASES) {
return p.parseShowDatabasesStatement()
}
if p.peekTokenIs(lexer.TABLES) {
return p.parseShowTablesStatement()
}
return nil, fmt.Errorf("expected DATABASES after SHOW")
...
default:
return nil, fmt.Errorf("You have an error in your SQL syntax; check the manual that corresponds to your db server version for the right syntax to use near '%s'", p.curToken.Type)
}
}
// parseShowDatabasesStatement 解析SHOW DATABASES语句
func (p *Parser) parseShowDatabasesStatement() (*ast.ShowDatabasesStatement, error) {
stmt := &ast.ShowDatabasesStatement{Token: p.curToken}
if !p.expectPeek(lexer.DATABASES) {
return nil, fmt.Errorf("expected DATABASES keyword")
}
return stmt, nil
}
// parseShowTablesStatement 解析SHOW TABLES语句
func (p *Parser) parseShowTablesStatement() (*ast.ShowTablesStatement, error) {
stmt := &ast.ShowTablesStatement{Token: p.curToken}
if !p.expectPeek(lexer.TABLES) {
return nil, fmt.Errorf("expected TABLES keyword")
}
return stmt, nil
}
...
4. 存储引擎实现操作
internal/storage/memory.go:
go
// ShowDatabases 显示所有数据库
func (b *MemoryBackend) ShowDatabases() *Results {
b.Mu.RLock()
defer b.Mu.RUnlock()
results := &Results{
Columns: []ResultColumn{
{Name: "Database", Type: "TEXT"},
},
Rows: make([][]Cell, 0),
}
for dbName := range b.Databases {
results.Rows = append(results.Rows, []Cell{
{Type: CellTypeText, TextValue: dbName},
})
}
// 按名称排序
sort.Slice(results.Rows, func(i, j int) bool {
return results.Rows[i][0].TextValue < results.Rows[j][0].TextValue
})
return results
}
// ShowTables 显示数据库中的所有表
func (b *MemoryBackend) ShowTables(connCtx context.DBContext) *Results {
b.Mu.RLock()
defer b.Mu.RUnlock()
results := &Results{
Columns: []ResultColumn{
{Name: "Tables", Type: "TEXT"},
},
Rows: make([][]Cell, 0),
}
dbName := connCtx.GetDBName()
if dbName == "" {
return results
}
database := b.Databases[dbName]
for tableName := range database.Tables {
results.Rows = append(results.Rows, []Cell{
{Type: CellTypeText, TextValue: tableName},
})
}
// 按名称排序
sort.Slice(results.Rows, func(i, j int) bool {
return results.Rows[i][0].TextValue < results.Rows[j][0].TextValue
})
return results
}
...
5. 程序入口处调整
需要调整两个地方,一个是本地命令行版(cmd/main.go),一个是网络版(network/server.go)
- cmd/main.go:
go
func executor(t string) {
// 分割多个SQL语句(用分号分隔)
statements := strings.Split(t, ";")
for _, stmt := range statements {
...
// 创建词法分析器
l := lexer.NewLexer(strings.NewReader(stmt))
// 创建语法分析器
p := parser.NewParser(l)
// 解析SQL语句
parsedStmt, err := p.ParseProgram()
if err != nil {
fmt.Printf("Parse error: %v\n", err)
continue
}
// 执行SQL语句
for _, statement := range parsedStmt.Statements {
if currentDatabase == "" {
// 检查是否是非数据库操作语句
_, isCreateDB := statement.(*ast.CreateDatabaseStatement)
_, isShowDBs := statement.(*ast.ShowDatabasesStatement)
_, isDropDB := statement.(*ast.DropDatabaseStatement)
_, isUseDB := statement.(*ast.UseDatabaseStatement)
_, isShowTables := statement.(*ast.ShowTablesStatement)
// 如果不是允许的语句类型,则提示需要选择数据库
if !isCreateDB && !isShowDBs && !isDropDB && !isUseDB && !isShowTables {
fmt.Println("No database selected. Use 'USE database_name' to select a database.")
continue
}
}
switch s := statement.(type) {
case *ast.ShowDatabasesStatement:
result := backend.ShowDatabases()
printResults(result)
case *ast.ShowTablesStatement:
result := backend.ShowTables(&dbContextAdapter{¤tDatabase})
printResults(result)
...
default:
fmt.Printf("Unsupported statement type: %T\n", s)
}
}
}
}
- network/server.go:
go
// network/server.go
package network
import (
"bufio"
"fmt"
"net"
"strings"
"sync"
"ziyi.db.com/internal/ast"
"ziyi.db.com/internal/lexer"
"ziyi.db.com/internal/parser"
"ziyi.db.com/internal/storage"
)
const DefaultPort = "3118"
...
func (s *Server) executeCommand(conn net.Conn, command string) string {
...
var result string
for _, statement := range parsedStmt.Statements {
//检查是否选择了数据库
if connCtx.GetDBName() == "" {
// 检查是否是非数据库操作语句
_, isCreateDB := statement.(*ast.CreateDatabaseStatement)
_, isShowDBs := statement.(*ast.ShowDatabasesStatement)
_, isDropDB := statement.(*ast.DropDatabaseStatement)
_, isUseDB := statement.(*ast.UseDatabaseStatement)
_, isShowTables := statement.(*ast.ShowTablesStatement)
// 如果不是允许的语句类型,则提示需要选择数据库
if !isCreateDB && !isShowDBs && !isDropDB && !isUseDB && !isShowTables {
result += "No database selected. Use 'USE database_name' to select a database."
continue
}
}
switch stmt := statement.(type) {
case *ast.ShowDatabasesStatement:
results := s.backend.ShowDatabases()
if err != nil {
result += fmt.Sprintf("Error: %v\n", err)
} else {
result += formatResults(results) + "\n"
}
case *ast.ShowTablesStatement:
results := s.backend.ShowTables(connCtx)
if err != nil {
result += fmt.Sprintf("Error: %v\n", err)
} else {
result += formatResults(results) + "\n"
}
...
default:
result += fmt.Sprintf("Unsupported statement type: %T\n", stmt)
}
}
return strings.TrimSpace(result)
}
测试
测试命令:
sql
-- 测试show databases:
create database test;
create database test2;
show databases;
-- 测试show tables:
use test2;
create table users (id INT PRIMARY KEY,name text,age INT);
create table products (id INT PRIMARY KEY,name text,price FLOAT);
show tables;
效果: