برنامه نویسی

DBChat قسمت 3 – پیکربندی، اتصال و تخلیه پایگاه های داده

سلام! من شرجیت ونکاتراما، بنیانگذار Hexmos هستم. در حال حاضر، من در حال ساخت LiveAPI هستم، یک ابزار فوق العاده راحت که با تولید اسناد API عالی از کد شما در عرض چند دقیقه، گردش کار مهندسی را ساده می کند.

در این مجموعه آموزشی، من در سفری هستم تا برای خودم DBChat بسازم – ابزاری ساده برای استفاده از چت هوش مصنوعی برای کاوش و تکامل پایگاه‌های داده.

برای دریافت متن بیشتر به پست های قبلی مراجعه کنید:

  1. ساخت DBChat – کاوش و توسعه DB خود با چت ساده (قسمت 1)
  2. DBChat: گرفتن یک اسباب بازی REPL رفتن در Golang (قسمت 2)

مشکل – چگونه می‌توان پایگاه‌هایی را که می‌خواهیم با آنها سر و کار داشته باشیم، مشخص کنیم؟

فکر اولیه من این بود که – ما باید یک connect فرمت پشتیبانی شده در REPL ما.

کاربر می تواند REPL را شروع کرده و پیکربندی را وارد کند.

اما در بررسی بیشتر – به نظر می رسید که داشتن نام های دوستانه برای پایگاه های داده رویکرد بهتری باشد.

بنابراین، فعلاً به روش زیر استناد کردم:

  1. ~/.dbchat.toml – یک فایل پیکربندی برای dbchat در پوشه خانه. در ابتدا، ساده خواهد بود – فقط یک بخش “اتصالات” در آن، لیست کردن آدرس های مختلف پایگاه داده.
  2. یک جدید connect فرمان در داخل پوسته می توان به هر دو اتصالات پایگاه داده ذخیره شده یا اتصالات پایگاه داده تحت اللفظی متصل شد. یعنی connect یا connect هر دو پشتیبانی خواهند شد

در قسمت های بعدی نحوه اجرای موارد فوق (2) را توضیح خواهم داد

پیکربندی اتصالات پایگاه داده در ~/.dbchat.toml

پیکربندی نمونه در ابتدا چیزی شبیه به این فرض می شود:

# DBChat Sample Configuration File
# Copy this file to ~/.dbchat.toml and modify as needed

[connections]
# Format: name = "connection_string"
local = "postgresql://postgres:postgres@localhost:5432/postgres"
liveapi = "postgresql://user:password@ip:port/database_name" 
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اجرا کردم cmd/dbchat/utils/config.go برای خواندن و لیست کردن اتصالات از پیکربندی مانند زیر:

package utils

import (
    "fmt"
    "os"
    "path/filepath"
    "strings"

    "github.com/BurntSushi/toml"
)

// Config holds the application configuration
type Config struct {
    Connections map[string]string `toml:"connections"`
}

// LoadConfig reads the configuration from ~/.dbchat.toml
func LoadConfig() (*Config, error) {
    home, err := os.UserHomeDir()
    if err != nil {
        return nil, fmt.Errorf("error getting home directory: %v", err)
    }

    configPath := filepath.Join(home, ".dbchat.toml")
    var config Config

    if _, err := toml.DecodeFile(configPath, &config); err != nil {
        // Return empty config if file doesn't exist
        if os.IsNotExist(err) {
            return &Config{Connections: make(map[string]string)}, nil
        }
        return nil, fmt.Errorf("error reading config file: %v", err)
    }

    return &config, nil
}

// ListConnections returns a formatted string of all configured connections
func ListConnections(config *Config) string {
    if len(config.Connections) == 0 {
        return "No connections configured in ~/.dbchat.toml"
    }

    var sb strings.Builder
    for name := range config.Connections {
        sb.WriteString(fmt.Sprintf("- %s\n", name))
    }
    return sb.String()
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این connect اجرای فرمان

دو تغییر در پشتیبانی وجود دارد connect دستور:

  1. connect
  2. connect

بنابراین در REPL Eval handling، یک مورد جدید برای handling connect اضافه کردم:

    case cmd == "connect":
        if len(fields) == 1 {
            return `Usage: connect 

You can either provide a full connection string:
Example: connect postgresql://username:password@localhost:5432/dbname
Format:  postgresql://[user]:[password]@[host]:[port]/[dbname]?[params]

Or use a connection name from ~/.dbchat.toml:
Example: connect local

Available connections in config:
` + utils.ListConnections(h.config)
        }

        connectionStr := strings.Join(fields[1:], " ")

        // Check if the argument matches a configured connection name
        if configStr, exists := h.config.Connections[connectionStr]; exists {
            connectionStr = configStr
        }

        // Close existing connection if it exists
        if h.db != nil {
            h.db.Close()
        }

        // Connect using the connection string
        newDb, err := db.Connect(connectionStr)
        if err != nil {
            return fmt.Sprintf("Failed to connect: %v", err)
        }

        h.db = newDb
        return "Successfully connected to PostgreSQL database! 🎉"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

dump schema دستور دریافت متن پایگاه داده

مهمترین اقدام برای ما این است که کل طرحواره پایگاه داده متصل را دریافت کنیم.

طرحواره مهم است زیرا: ما می توانیم از طرح برای اطلاع دادن به لایه LLM بعدا استفاده کنیم. و این اطلاعات به LLM کمک می کند تا پرس و جوهای مفید و دقیق SQL را ایجاد کند.

بنابراین من ایجاد کردم cmd/dbchat/db/schema.go:

package db

import (
    "database/sql"
    "fmt"
    "strings"
)

const schemaQuery = `
WITH tables AS (
    SELECT 
        t.table_schema,
        t.table_name,
        t.table_type,
        obj_description((quote_ident(t.table_schema)||'.'||quote_ident(t.table_name))::regclass, 'pg_class') as table_comment
    FROM information_schema.tables t
    WHERE t.table_schema NOT IN ('pg_catalog', 'information_schema')
    ORDER BY t.table_schema, t.table_name
),
columns AS (
    SELECT 
        c.table_schema,
        c.table_name,
        c.column_name,
        c.data_type,
        c.is_nullable,
        c.column_default,
        col_description((quote_ident(c.table_schema)||'.'||quote_ident(c.table_name))::regclass, 
                       c.ordinal_position) as column_comment
    FROM information_schema.columns c
    WHERE c.table_schema NOT IN ('pg_catalog', 'information_schema')
    ORDER BY c.table_schema, c.table_name, c.ordinal_position
)
SELECT 
    t.table_schema,
    t.table_name,
    t.table_type,
    t.table_comment,
    string_agg(
        format(
            '  %s %s%s%s%s',
            c.column_name,
            c.data_type,
            CASE WHEN c.is_nullable="NO" THEN ' NOT NULL' ELSE '' END,
            CASE WHEN c.column_default IS NOT NULL THEN ' DEFAULT ' || c.column_default ELSE '' END,
            CASE WHEN c.column_comment IS NOT NULL THEN ' -- ' || c.column_comment ELSE '' END
        ),
        E'\n'
    ) as columns
FROM tables t
LEFT JOIN columns c 
    ON c.table_schema = t.table_schema 
    AND c.table_name = t.table_name
GROUP BY t.table_schema, t.table_name, t.table_type, t.table_comment
ORDER BY t.table_schema, t.table_name;
`

// DumpSchema returns a formatted string containing the database schema
func DumpSchema(db *sql.DB) (string, error) {
    if db == nil {
        return "", fmt.Errorf("database connection required. Use 'connect' command first")
    }

    rows, err := db.Query(schemaQuery)
    if err != nil {
        return "", fmt.Errorf("error querying schema: %v", err)
    }
    defer rows.Close()

    var sb strings.Builder
    var currentSchema string

    for rows.Next() {
        var (
            schema, tableName, tableType, columns string
            tableComment                          sql.NullString
        )

        if err := rows.Scan(&schema, &tableName, &tableType, &tableComment, &columns); err != nil {
            return "", fmt.Errorf("error scanning row: %v", err)
        }

        // Print schema header if we're entering a new schema
        if schema != currentSchema {
            if currentSchema != "" {
                sb.WriteString("\n")
            }
            sb.WriteString(fmt.Sprintf("Schema: %s\n", schema))
            sb.WriteString(strings.Repeat("=", len(schema)+8) + "\n\n")
            currentSchema = schema
        }

        // Print table information
        sb.WriteString(fmt.Sprintf("Table: %s (%s)\n", tableName, tableType))
        if tableComment.Valid {
            sb.WriteString(fmt.Sprintf("Comment: %s\n", tableComment.String))
        }
        sb.WriteString("Columns:\n")
        sb.WriteString(columns)
        sb.WriteString("\n\n")
    }

    if err = rows.Err(); err != nil {
        return "", fmt.Errorf("error iterating rows: %v", err)
    }

    return sb.String(), nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نسخه آزمایشی: پیکربندی، اتصال و حذف طرحواره از پایگاه داده PostgreSQL

استفاده از Predefine Connection در DBChat

مراحل بعدی

از آنجایی که ما اتصال پایگاه داده و مکانیسم تخلیه طرحواره داریم – آماده هستیم تا به مرحله بعدی برویم: ادغام LLM.

در مرحله بعدی، کوئری‌های کاربر را در زبان طبیعی و طرح‌واره پایگاه‌داده برای تولید کوئری‌های SQL ترکیب می‌کنیم.

در مرحله آخر – پرس و جو باید برای تولید نتایج نیز اجرا شود – اما این موضوع فوری نیست.

لایک/اشتراک گذاری/اشتراک گذاری برای تشویق توسعه این سری از پست ها. با تشکر برای خواندن.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا