DeepSeek辅助编写连接sqlite的CLI

用go语言编写连接sqlite的CLI,支持如下功能,1.输入create /insert/update/delete/select语句,成功返回影响行数,失败提示错误。2.输出结果时,.header on/off可以控制列名是否显示。.mode table/csv控制输出文本表格还是逗号风格。.read 文件名可以批量执行文件中的多个SQL语句。.maxrows 行数可以控制最大显示行数,默认10行。3.用.timer on/off控制输出按毫秒计算的计时。

他的输出如下

这是一个用 Go 语言实现的 SQLite CLI 工具,覆盖了你要求的 SQL 执行反馈、输出格式控制、批量执行和计时功能。代码基于成熟的 mattn/go-sqlite3 驱动,并利用 database/sql 标准库来区分查询类(SELECT)与非查询类(CREATE/INSERT/UPDATE/DELETE)语句。

核心实现代码

你可以将以下代码保存为 main.go,编译或直接运行。

go 复制代码
package main

import (
	"bufio"
	"database/sql"
	"encoding/csv"
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

// 全局配置状态
var (
	showHeader = false    // .header 默认关闭
	outputMode = "table"  // .mode 默认 table
	maxRows    = 10       // .maxrows 默认 10
	timerOn    = false    // .timer 默认关闭
	db         *sql.DB
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("用法: ./sqlite-cli <数据库文件>")
		os.Exit(1)
	}

	var err error
	// 连接 SQLite 数据库
	db, err = sql.Open("sqlite3", os.Args[1])
	if err != nil {
		fmt.Printf("打开数据库失败: %v\n", err)
		os.Exit(1)
	}
	defer db.Close()

	// 验证连接
	if err := db.Ping(); err != nil {
		fmt.Printf("连接数据库失败: %v\n", err)
		os.Exit(1)
	}

	reader := bufio.NewReader(os.Stdin)
	fmt.Println("SQLite CLI (输入 .help 查看帮助, 输入 .exit 退出)")

	for {
		fmt.Print("sqlite> ")
		line, err := reader.ReadString('\n')
		if err != nil {
			break // EOF
		}
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}

		// 处理点命令
		if strings.HasPrefix(line, ".") {
			if handleDotCommand(line) {
				break // 退出
			}
			continue
		}

		// 执行 SQL
		executeSQL(line)
	}
}

// handleDotCommand 返回 true 表示退出程序
func handleDotCommand(cmd string) bool {
	parts := strings.Fields(cmd)
	if len(parts) == 0 {
		return false
	}

	switch parts[0] {
	case ".exit", ".quit":
		return true
	case ".help":
		printHelp()
	case ".header":
		if len(parts) > 1 {
			if parts[1] == "on" {
				showHeader = true
			} else if parts[1] == "off" {
				showHeader = false
			}
		}
	case ".mode":
		if len(parts) > 1 {
			if parts[1] == "table" || parts[1] == "csv" {
				outputMode = parts[1]
			} else {
				fmt.Println("未知模式,支持: table, csv")
			}
		}
	case ".maxrows":
		if len(parts) > 1 {
			if n, err := strconv.Atoi(parts[1]); err == nil && n >= 0 {
				maxRows = n
			} else {
				fmt.Println("请输入有效的非负整数")
			}
		}
	case ".timer":
		if len(parts) > 1 {
			if parts[1] == "on" {
				timerOn = true
			} else if parts[1] == "off" {
				timerOn = false
			}
		}
	case ".read":
		if len(parts) > 1 {
			readFile(parts[1])
		} else {
			fmt.Println("请指定文件名")
		}
	default:
		fmt.Printf("未知命令: %s\n", parts[0])
	}
	return false
}

func printHelp() {
	fmt.Println(`可用命令:
  .header on|off    显示/隐藏列名 (默认 off)
  .mode table|csv   设置输出格式 (默认 table)
  .maxrows N        设置最大显示行数 (默认 10)
  .timer on|off     开启/关闭计时器 (默认 off)
  .read <文件>      批量执行文件中的 SQL
  .exit / .quit     退出`)
}

// executeSQL 执行单条 SQL 并处理结果
func executeSQL(query string) {
	start := time.Now()
	
	// 简单判断是否为查询语句(忽略大小写和首尾空格)
	upperQuery := strings.ToUpper(strings.TrimSpace(query))
	isQuery := strings.HasPrefix(upperQuery, "SELECT") || strings.HasPrefix(upperQuery, "PRAGMA")

	if isQuery {
		rows, err := db.Query(query)
		if err != nil {
			fmt.Printf("错误: %v\n", err)
			return
		}
		defer rows.Close()
		printRows(rows)
	} else {
		// CREATE, INSERT, UPDATE, DELETE 等
		result, err := db.Exec(query)
		if err != nil {
			fmt.Printf("错误: %v\n", err)
			return
		}
		affected, _ := result.RowsAffected()
		fmt.Printf("影响行数: %d\n", affected)
	}

	if timerOn {
		elapsed := time.Since(start)
		fmt.Printf("耗时: %d ms\n", elapsed.Milliseconds())
	}
}

// printRows 根据当前配置输出查询结果
func printRows(rows *sql.Rows) {
	cols, err := rows.Columns()
	if err != nil {
		fmt.Printf("获取列名失败: %v\n", err)
		return
	}

	// 准备扫描容器
	values := make([]interface{}, len(cols))
	scanArgs := make([]interface{}, len(cols))
	for i := range values {
		scanArgs[i] = &values[i]
	}

	// 收集数据(受 maxRows 限制)
	var allData [][]string
	count := 0
	for rows.Next() {
		if maxRows > 0 && count >= maxRows {
			break // 达到最大显示行数
		}
		if err := rows.Scan(scanArgs...); err != nil {
			fmt.Printf("扫描行失败: %v\n", err)
			return
		}
		rowData := make([]string, len(cols))
		for i, v := range values {
			if v == nil {
				rowData[i] = "NULL"
			} else {
				rowData[i] = fmt.Sprintf("%v", v)
			}
		}
		allData = append(allData, rowData)
		count++
	}

	// 根据模式输出
	if outputMode == "csv" {
		printCSV(cols, allData)
	} else {
		printTable(cols, allData)
	}

	// 检查是否有更多行未显示
	if maxRows > 0 {
		var extra int
		for rows.Next() {
			extra++
		}
		if extra > 0 {
			fmt.Printf("... (还有 %d 行未显示,使用 .maxrows 调整)\n", extra)
		}
	}
}

func printTable(cols []string, data [][]string) {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	
	if showHeader {
		fmt.Fprintln(w, strings.Join(cols, "\t"))
		// 打印分隔线
		separators := make([]string, len(cols))
		for i := range cols {
			separators[i] = strings.Repeat("-", len(cols[i]))
		}
		fmt.Fprintln(w, strings.Join(separators, "\t"))
	}
	
	for _, row := range data {
		fmt.Fprintln(w, strings.Join(row, "\t"))
	}
	w.Flush()
}

func printCSV(cols []string, data [][]string) {
	w := csv.NewWriter(os.Stdout)
	defer w.Flush()

	if showHeader {
		w.Write(cols)
	}
	for _, row := range data {
		w.Write(row)
	}
}

// readFile 批量执行文件中的 SQL
func readFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		fmt.Printf("无法打开文件: %v\n", err)
		return
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	var sb strings.Builder
	
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		// 跳过空行和注释
		if line == "" || strings.HasPrefix(line, "--") {
			continue
		}
		sb.WriteString(line)
		sb.WriteString(" ")
		
		// 如果行以分号结尾,执行累积的 SQL
		if strings.HasSuffix(line, ";") {
			query := strings.TrimSpace(sb.String())
			query = strings.TrimSuffix(query, ";")
			if query != "" {
				fmt.Printf("执行: %s\n", truncate(query, 60))
				executeSQL(query)
			}
			sb.Reset()
		}
	}
	
	if err := scanner.Err(); err != nil {
		fmt.Printf("读取文件错误: %v\n", err)
	}
}

func truncate(s string, max int) string {
	if len(s) > max {
		return s[:max] + "..."
	}
	return s
}

功能说明

功能 实现方式
SQL 执行与影响行数 通过判断语句前缀区分 SELECT/PRAGMA(走 Query)和其他语句(走 Exec)。Exec 后调用 RowsAffected() 获取影响行数。
.header on/off 全局变量 showHeader 控制 printTable 和 printCSV 是否输出列名行。
.mode table/csv printTable 使用 text/tabwriter 实现对齐;printCSV 使用 encoding/csv。
.read 文件名 逐行读取文件,以分号 ; 作为语句分隔符,累积后执行,支持多行 SQL。
.maxrows 行数 默认 10。在 printRows 的扫描循环中计数,达到上限即停止,并提示剩余未显示行数。
.timer on/off 在 executeSQL 前后记录 time.Now(),用 time.Since().Milliseconds() 输出毫秒数。

依赖与编译

驱动 :项目依赖 github.com/mattn/go-sqlite3,它需要 CGO 环境(如 gcc 编译器)。

bash 复制代码
# 初始化模块并获取驱动
go mod init sqlite-cli
go get github.com/mattn/go-sqlite3

# 编译(确保 CGO_ENABLED=1)
CGO_ENABLED=1 go build -o sqlite-cli main.go

运行示例:

bash 复制代码
./sqlite-cli test.db

在交互界面中,你可以先建表,然后测试各种 . 命令组合。例如,执行 .read script.sql 会依次执行文件中的建表、插入语句,并打印每条的影响行数。

编译和测试运行结果如下

复制代码
C:\Users\lt>cd\d\goprj

C:\d\goprj>set GOROOT=C:\d\gowin\go

C:\d\goprj>set GOPATH=c:\d\goprj

C:\d\goprj>set path=%GOROOT%\bin;%GOPATH%\bin;%PATH%;C:\d\mingw64\bin

C:\d\goprj>set CGO_ENABLED=1

C:\d\goprj>go build -o sqlite-cli.exe testcli.go

C:\d\goprj>sqlite-cli test.db
SQLite CLI (输入 .help 查看帮助, 输入 .exit 退出)
sqlite> .help
可用命令:
  .header on|off    显示/隐藏列名 (默认 off)
  .mode table|csv   设置输出格式 (默认 table)
  .maxrows N        设置最大显示行数 (默认 10)
  .timer on|off     开启/关闭计时器 (默认 off)
  .read <文件>      批量执行文件中的 SQL
  .exit / .quit     退出
sqlite> select * from users;
1  张三  30
2  李四  25
sqlite> .timer on
sqlite> select sum(value) from generate_series(1,10000000);
错误: no such table: generate_series
sqlite> .maxrows 1
sqlite> select * from users;
1  张三  30
耗时: 0 ms
sqlite> .header on
sqlite> select * from users;
id  name  age
--  ----  ---
1   张三    30
耗时: 0 ms
sqlite> .mode csv
sqlite> select * from users;
id,name,age
1,张三,30
耗时: 0 ms
sqlite> drop table users;
影响行数: 0
耗时: 27 ms
sqlite> select * from users;
错误: no such table: users
sqlite> .read schema.txt
执行: CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AU...
影响行数: 0
耗时: 0 ms
执行: INSERT INTO users(name, age) VALUES('张三', 30)
影响行数: 1
耗时: 0 ms
执行: INSERT INTO users(name, age) VALUES('李四', 25)
影响行数: 1
耗时: 0 ms
sqlite> select * from users;
id,name,age
1,张三,30
耗时: 1 ms
sqlite>
```
相关推荐
计算机就业还在扩大1 小时前
第2期-cmp.Ordered+类型推断+刻意未支持的特性
后端·面试·golang
翔云1234561 小时前
Gaea中配置如何更新
运维·数据库·mysql
泡海椒2 小时前
JQuick-SQL JQuickTable 模型:XML 动态 SQL 的表封装边界
xml·数据库·sql
ss2733 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 28 PostgreSQL MVCC机制介绍
数据库·笔记·postgresql
奇牙coding3 小时前
doubao-seed-2.1-pro 报 HTTP 401 怎么办?Key 正确也要检查请求头和端点
网络·数据库·http·ai
不脱发的程序猿11 小时前
纳指ETF量化策略
数据库·人工智能·1024程序员节
程序员塔塔喵12 小时前
【MySQL篇|第一篇】索引失效的情况
数据库·mysql·索引失效
北京青蓝智慧马老师13 小时前
比外部黑客更难防的,是那个有权限的自己人(3 个脱敏复盘)
大数据·数据库·人工智能
笃行35013 小时前
拆一台数据集成产品:KFS 的骨架、肌肉和神经
数据库