利用DeepSeek编写基于DuckDB的数据缓存系统

基于两篇前文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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值