基于两篇前文1 2 的成果,把数据缓存系统的后台数据库改成了DuckDB,本来想用简单字符串替换解决map的单引号到JSON的双引号问题,但一来V语言没有这种函数,因为它的字符串都是不可变的,二来map字符串数组不是简单的字符串,虽然println()可以正常显示,作其他用途必须专门处理,所以还是采用convert_quotes函数。简单的字符串处理函数escape_sql用来转义SQL中的单引号。DeepSeek原作在处理cache_db.query的参数时用了太多单引号转义,影响可读性,通过改用" "包裹参数消除。
完整程序如下
import vduckdb
import x.json2
import os
import strings
struct QueryCache {
id int
sql_query string
description string
json_data string
created_at string
}
fn main() {
// 连接缓存数据库
mut cache_db := vduckdb.DuckDB{}
cache_db.open('query_cache.ddb') or {
eprintln('连接缓存数据库失败: ${err}')
return
}
defer {
cache_db.close()
}
// 初始化缓存表
init_cache_db(mut cache_db) or {
eprintln('初始化缓存表失败: ${err}')
return
}
// 主循环
for {
// 用户输入
query := os.input('\n请输入SQL查询语句(输入q退出): ')
if query == 'q' {
println('退出程序')
break
}
description := os.input('请输入查询描述(中文): ')
// 1. 首先检查缓存
cache_db.query("SELECT json_data FROM result_cache WHERE sql_query = '${escape_sql(query)}' LIMIT 1") or {
eprintln('检查缓存失败: ${err}')
continue
}
if cache_db.num_rows > 0 {
println('(从缓存读取结果)')
cached_data := cache_db.get_first_row()['json_data']
display_results(cached_data)
continue
}
println('(执行新查询并缓存结果)')
// 2. 连接源数据库并执行查询(缓存不存在时才执行)
mut source_db := vduckdb.DuckDB{}
source_db.open(':memory:') or {
eprintln('连接DuckDB失败: ${err}')
continue
}
defer {
source_db.close()
}
// 执行查询
source_db.query(query) or {
eprintln('查询执行失败: ${err}')
continue
}
// 3. 获取结果并存入缓存
json_data_o := source_db.get_array_as_string()
// 替换单引号为双引号
json_data :=convert_quotes(json_data_o)
println('\n即将存入的JSON数据:')
println(json_data)
// 存入缓存
cache_db.query("
INSERT INTO result_cache
(sql_query, description, json_data)
VALUES ('${escape_sql(query)}', '${escape_sql(description)}', '${escape_sql(json_data)}')
ON CONFLICT(sql_query) DO UPDATE SET
description = excluded.description,
json_data = excluded.json_data") or {
eprintln('缓存结果失败: ${err}')
continue
}
// 4. 从缓存中读取最新结果并显示
cache_db.query("SELECT json_data FROM result_cache WHERE sql_query = '${escape_sql(query)}' LIMIT 1") or {
eprintln('读取缓存失败: ${err}')
continue
}
if cache_db.num_rows == 0 {
eprintln('意外错误: 缓存写入成功但读取失败')
continue
}
cached_data := cache_db.get_first_row()['json_data']
display_results(cached_data)
}
}
fn convert_quotes(data []map[string]string) string {
mut json_str := strings.new_builder(1024) // 预分配缓冲区
json_str.write_string('[')
for i, row in data {
if i > 0 {
json_str.write_string(', ')
}
json_str.write_string('{')
mut first := true
for key, val in row {
if !first {
json_str.write_string(', ')
}
// 对键和值进行引号转换
mut key_bytes := key.bytes()
mut val_bytes := val.bytes()
// 转换键中的单引号
for i2 in 0..key_bytes.len {
if key_bytes[i2] == `'` {
key_bytes[i2] = `"`
}
}
// 转换值中的单引号
for i3 in 0..val_bytes.len {
if val_bytes[i3] == `'` {
val_bytes[i3] = `"`
}
}
json_str.write_string('"${key_bytes.bytestr()}":"${val_bytes.bytestr()}"')
first = false
}
json_str.write_string('}')
}
json_str.write_string(']')
result := json_str.str()
json_str.free()
return result
}
fn init_cache_db(mut db vduckdb.DuckDB) ! {
db.query("
CREATE TABLE IF NOT EXISTS result_cache (
-- id INTEGER PRIMARY KEY ,
sql_query TEXT UNIQUE,
description TEXT,
json_data TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
")!
db.query("CREATE INDEX IF NOT EXISTS idx_sql_query ON result_cache(sql_query);")!
}
fn escape_sql(s string) string {
mut result := []u8{cap: s.len * 2} // 预分配足够空间(最多可能双倍长度)
for c in s.bytes() {
if c == `\'` { // 遇到单引号
result << `\'` // 添加第一个单引号
result << `\'` // 添加第二个单引号
} else {
result << c // 其他字符原样添加
}
}
return result.bytestr()
}
fn display_results(json_data string) {
raw_data := json2.raw_decode(json_data) or {
println('JSON解析失败: ${err}')
return
}
json_array := raw_data.arr()
if json_array.len == 0 {
println('空结果集')
return
}
first_obj := json_array[0].as_map()
mut headers := []string{}
mut alignments := []string{}
for key, val in first_obj {
headers << key
if val is int || val is f64 {
alignments << '--:'
} else {
alignments << '--'
}
}
println('\n查询结果:')
println(headers.join('|'))
println(alignments.join('|'))
for item in json_array {
obj := item.as_map()
mut row := []string{}
for header in headers {
val := obj[header] or { json2.Null{} }
if val is json2.Null {
row << 'NULL'
} else {
row << val.str()
}
}
println(row.join('|'))
}
}
执行和之前的SQLite版本效果一样,就不举例了。需要说明,Duckdb也支持直接读取SQLite格式数据库,但不支持ON CONFLICT语法,缓存结果失败: Binder Error: ON CONFLICT clause not yet supported for insertion into SQLite table所以去掉了多余的主键列id,消除了以下问题:
缓存结果失败: Binder Error: Conflict target has to be provided for a DO UPDATE operation when the table has multiple UNIQUE/PRIMARY KEY constraints


被折叠的 条评论
为什么被折叠?



