Skip to content

Latest commit

 

History

History
494 lines (389 loc) · 22.6 KB

File metadata and controls

494 lines (389 loc) · 22.6 KB

filesql

Go Reference Go Report Card MultiPlatformUnitTest Coverage

English | 中文 | 한국어 | Español | Français | 日本語

logo

filesql — это SQL-драйвер для Go, который позволяет запрашивать файлы CSV, TSV, LTSV, Parquet и Excel (XLSX), используя синтаксис SQL SQLite3. Запрашивайте ваши файлы данных напрямую без импорта или трансформации!

Хотите попробовать возможности filesql? Оцените sqly — инструмент командной строки, который использует filesql для лёгкого выполнения SQL-запросов к файлам CSV, TSV, LTSV и Excel прямо из вашего shell. Это идеальный способ испытать мощь filesql в действии!

🎯 Зачем filesql?

Эта библиотека родилась из опыта поддержки двух отдельных CLI-инструментов - sqly и sqluv. Оба инструмента имели общую особенность: выполнение SQL-запросов к файлам CSV, TSV и другим форматам файлов.

Вместо поддержки дублирующегося кода в обоих проектах, мы извлекли основную функциональность в этот повторно используемый SQL-драйвер. Теперь любой Go-разработчик может использовать эту возможность в своих собственных приложениях!

✨ Особенности

  • 🔍 Интерфейс SQL SQLite3 - Используйте мощный SQL-диалект SQLite3 для запроса ваших файлов
  • 📁 Множественные форматы файлов - Поддержка файлов CSV, TSV, LTSV, Parquet и Excel (XLSX)
  • 🗜️ Поддержка сжатия - Автоматически обрабатывает сжатые файлы .gz, .bz2, .xz и .zst
  • 🌊 Потоковая обработка - Эффективно обрабатывает большие файлы через потоковую передачу с настраиваемыми размерами блоков
  • 📖 Гибкие источники ввода - Поддержка путей к файлам, каталогов, io.Reader и embed.FS
  • 🚀 Нулевая настройка - Сервер баз данных не требуется, всё работает в памяти
  • 💾 Автосохранение - Автоматически сохраняет изменения в файлы
  • 🌍 Кроссплатформенность - Безупречно работает на Linux, macOS и Windows
  • На основе SQLite3 - Построен на надёжном движке SQLite3 для надёжной обработки SQL

📋 Поддерживаемые форматы файлов

Расширение Формат Описание
.csv CSV Значения, разделённые запятыми
.tsv TSV Значения, разделённые табуляцией
.ltsv LTSV Помеченные значения, разделённые табуляцией
.parquet Parquet Колонночный формат Apache Parquet
.xlsx Excel XLSX Формат рабочей книги Microsoft Excel
.csv.gz, .tsv.gz, .ltsv.gz, .parquet.gz, .xlsx.gz Сжатие Gzip Файлы, сжатые Gzip
.csv.bz2, .tsv.bz2, .ltsv.bz2, .parquet.bz2, .xlsx.bz2 Сжатие Bzip2 Файлы, сжатые Bzip2
.csv.xz, .tsv.xz, .ltsv.xz, .parquet.xz, .xlsx.xz Сжатие XZ Файлы, сжатые XZ
.csv.zst, .tsv.zst, .ltsv.zst, .parquet.zst, .xlsx.zst Сжатие Zstandard Файлы, сжатые Zstandard

📦 Установка

go get github.com/nao1215/filesql

🔧 Требования

  • Версия Go: 1.24 или новее
  • Поддерживаемые ОС:
    • Linux
    • macOS
    • Windows

🚀 Быстрый старт

Простое использование

Рекомендуемый способ начать работу — с OpenContext для правильной обработки таймаутов:

package main

import (
    "context"
    "fmt"
    "log"
    "time"
    
    "github.com/nao1215/filesql"
)

func main() {
    // Создать контекст с таймаутом для операций с большими файлами
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    // Открыть CSV-файл как базу данных
    db, err := filesql.OpenContext(ctx, "data.csv")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // Запросить данные (имя таблицы = имя файла без расширения)
    rows, err := db.QueryContext(ctx, "SELECT * FROM data WHERE age > 25")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    
    // Обработать результаты
    for rows.Next() {
        var name string
        var age int
        if err := rows.Scan(&name, &age); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Имя: %s, Возраст: %d\n", name, age)
    }
}

Множественные файлы и форматы

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Открыть несколько файлов одновременно (включая Parquet)
db, err := filesql.OpenContext(ctx, "users.csv", "orders.tsv", "logs.ltsv.gz", "analytics.parquet")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Объединить данные из разных форматов файлов
rows, err := db.QueryContext(ctx, `
    SELECT u.name, o.order_date, l.event, a.metrics
    FROM users u
    JOIN orders o ON u.id = o.user_id
    JOIN logs l ON u.id = l.user_id
    JOIN analytics a ON u.id = a.user_id
    WHERE o.order_date > '2024-01-01'
`)

Работа с каталогами

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Загрузить все поддерживаемые файлы из каталога (рекурсивно)
db, err := filesql.OpenContext(ctx, "/path/to/data/directory")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Посмотреть, какие таблицы доступны
rows, err := db.QueryContext(ctx, "SELECT name FROM sqlite_master WHERE type='table'")

🔧 Расширенное использование

Паттерн Builder

Для продвинутых сценариев используйте паттерн builder:

package main

import (
    "context"
    "embed"
    "log"
    
    "github.com/nao1215/filesql"
)

//go:embed data/*.csv
var embeddedFiles embed.FS

func main() {
    ctx := context.Background()
    
    // Настроить источники данных с помощью builder
    validatedBuilder, err := filesql.NewBuilder().
        AddPath("local_file.csv").      // Локальный файл
        AddFS(embeddedFiles).           // Встроенные файлы
        SetDefaultChunkSize(5000). // 5000 строк на блок
        Build(ctx)
    if err != nil {
        log.Fatal(err)
    }
    
    db, err := validatedBuilder.Open(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // Запросить все источники данных
    rows, err := db.QueryContext(ctx, "SELECT name FROM sqlite_master WHERE type='table'")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
}

Функции автосохранения

Автосохранение при закрытии базы данных

// Автоматически сохранять изменения при закрытии базы данных
validatedBuilder, err := filesql.NewBuilder().
    AddPath("data.csv").
    EnableAutoSave("./backup"). // Сохранить в каталог резервных копий
    Build(ctx)
if err != nil {
    log.Fatal(err)
}

db, err := validatedBuilder.Open(ctx)
if err != nil {
    log.Fatal(err)
}
defer db.Close() // Изменения автоматически сохраняются здесь

// Внести изменения
db.Exec("UPDATE data SET status = 'processed' WHERE id = 1")
db.Exec("INSERT INTO data (name, age) VALUES ('Иван', 30)")

Автосохранение при коммите транзакции

// Автоматически сохранять после каждой транзакции
validatedBuilder, err := filesql.NewBuilder().
    AddPath("data.csv").
    EnableAutoSaveOnCommit(""). // Пустая строка = перезаписать исходные файлы
    Build(ctx)
if err != nil {
    log.Fatal(err)
}

db, err := validatedBuilder.Open(ctx)
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Изменения сохраняются после каждого коммита
tx, _ := db.Begin()
tx.Exec("UPDATE data SET status = 'processed' WHERE id = 1")
tx.Commit() // Автосохранение происходит здесь

Работа с io.Reader и сетевыми данными

import (
    "net/http"
    "github.com/nao1215/filesql"
)

// Загрузить данные из HTTP-ответа
resp, err := http.Get("https://example.com/data.csv")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

validatedBuilder, err := filesql.NewBuilder().
    AddReader(resp.Body, "remote_data", filesql.FileTypeCSV).
    Build(ctx)
if err != nil {
    log.Fatal(err)
}

db, err := validatedBuilder.Open(ctx)
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Запросить удалённые данные
rows, err := db.QueryContext(ctx, "SELECT * FROM remote_data LIMIT 10")

Ручной экспорт данных

Если вы предпочитаете ручное управление сохранением:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

db, err := filesql.OpenContext(ctx, "data.csv")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Внести изменения
db.Exec("UPDATE data SET status = 'processed'")

// Вручную экспортировать изменения
err = filesql.DumpDatabase(db, "./output")
if err != nil {
    log.Fatal(err)
}

// Или с пользовательским форматом и сжатием
options := filesql.NewDumpOptions().
    WithFormat(filesql.OutputFormatTSV).
    WithCompression(filesql.CompressionGZ)
err = filesql.DumpDatabase(db, "./output", options)

// Экспорт в формат Parquet (когда доступен)
parquetOptions := filesql.NewDumpOptions().
    WithFormat(filesql.OutputFormatParquet)
// Примечание: Экспорт Parquet реализован (внешнее сжатие не поддерживается, используйте встроенное сжатие Parquet)

📝 Правила именования таблиц

filesql автоматически выводит имена таблиц из путей к файлам:

  • users.csv → таблица users
  • data.tsv.gz → таблица data
  • /path/to/sales.csv → таблица sales
  • products.ltsv.bz2 → таблица products
  • analytics.parquet → таблица analytics

⚠️ Важные заметки

SQL-синтаксис

Поскольку filesql использует SQLite3 в качестве базового движка, весь SQL-синтаксис следует SQL-диалекту SQLite3. Это включает:

  • Функции (например, date(), substr(), json_extract())
  • Оконные функции
  • Общие табличные выражения (CTE)
  • Триггеры и представления

Изменения данных

  • Операции INSERT, UPDATE и DELETE влияют на базу данных в памяти
  • Исходные файлы остаются неизменными по умолчанию
  • Используйте функции автосохранения или DumpDatabase() для сохранения изменений
  • Это делает безопасным экспериментирование с трансформациями данных

Советы по производительности

  • Используйте OpenContext() с таймаутами для больших файлов
  • Настройте размеры блоков (количество строк) с помощью SetDefaultChunkSize() для оптимизации памяти
  • Одно соединение SQLite работает лучше всего для большинства сценариев
  • Используйте потоковую передачу для файлов больше доступной памяти

Ограничения многопоточности

⚠️ ВАЖНО: Эта библиотека НЕ потокобезопасна и имеет ограничения многопоточности:

  • НЕ делитесь соединениями базы данных между горутинами
  • НЕ выполняйте одновременные операции на одном экземпляре базы данных
  • НЕ вызывайте db.Close() пока запросы активны в других горутинах
  • При необходимости многопоточных операций используйте отдельные экземпляры базы данных
  • Состояния гонки могут вызвать ошибки сегментации или повреждение данных

Рекомендуемый паттерн для многопоточного доступа:

// ✅ ХОРОШО: Отдельные экземпляры базы данных для каждой горутины
func processFileConcurrently(filename string) error {
    db, err := filesql.Open(filename)  // Каждая горутина получает свой экземпляр
    if err != nil {
        return err
    }
    defer db.Close()
    
    // Безопасно использовать в пределах этой горутины
    return processData(db)
}

// ❌ ПЛОХО: Совместное использование экземпляра базы данных между горутинами
var sharedDB *sql.DB  // Это вызовет состояния гонки

Поддержка Parquet

  • Чтение: Полная поддержка файлов Apache Parquet со сложными типами данных
  • Запись: Функциональность экспорта реализована (внешнее сжатие не поддерживается, используйте встроенное сжатие Parquet)
  • Отображение типов: Типы Parquet отображаются в типы SQLite
  • Сжатие: Используется встроенное сжатие Parquet вместо внешнего сжатия
  • Большие данные: Файлы Parquet эффективно обрабатываются с помощью колонночного формата Arrow

Поддержка Excel (XLSX)

  • Структура 1-Лист-1-Таблица: Каждый лист в рабочей книге Excel становится отдельной SQL-таблицей
  • Именование таблиц: Имена SQL-таблиц следуют формату {имя_файла}_{имя_листа} (например, "продажи_Q1", "продажи_Q2")
  • Обработка строки заголовков: Первая строка каждого листа становится заголовками столбцов для этой таблицы
  • Стандартные SQL-операции: Запрашивайте каждый лист независимо или используйте JOIN для объединения данных между листами
  • Требования к памяти: XLSX-файлы требуют полной загрузки в память из-за ZIP-структуры формата, даже при потоковых операциях
  • Полная загрузка в память: XLSX-файлы полностью загружаются в память из-за их ZIP-структуры, и обрабатываются все листы (не только первый). Потоковые парсеры CSV/TSV не применимы к XLSX-файлам
  • Функциональность экспорта: При экспорте в формат XLSX имена таблиц автоматически становятся именами листов
  • Поддержка сжатия: Полная поддержка сжатых XLSX-файлов (.xlsx.gz, .xlsx.bz2, .xlsx.xz, .xlsx.zst)

Пример структуры Excel-файла

Excel-файл с несколькими листами:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│ Лист1       │    │ Лист2       │    │ Лист3       │
│ Имя   Возр. │    │ Товар       │    │ Регион      │
│ Алиса   25  │    │ Ноутбук     │    │ Север       │
│ Борис   30  │    │ Мышь        │    │ Юг          │
└─────────────┘    └─────────────┘    └─────────────┘

Результат: 3 отдельные SQL-таблицы:

продажи_Лист1:          продажи_Лист2:          продажи_Лист3:
┌───────┬──────┐         ┌──────────┐             ┌────────┐
│ Имя   │ Возр.│         │ Товар    │             │ Регион │
├───────┼──────┤         ├──────────┤             ├────────┤
│ Алиса │  25  │         │ Ноутбук  │             │ Север  │
│ Борис │  30  │         │ Мышь     │             │ Юг     │
└───────┴──────┘         └──────────┘             └────────┘

Примеры SQL:
SELECT * FROM продажи_Лист1 WHERE Возраст > 27;
SELECT l1.Имя, l2.Товар FROM продажи_Лист1 l1 
  JOIN продажи_Лист2 l2 ON l1.rowid = l2.rowid;

🎨 Продвинутые примеры

Сложные SQL-запросы

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

db, err := filesql.OpenContext(ctx, "employees.csv", "departments.csv")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Использовать продвинутые возможности SQLite
query := `
    WITH dept_stats AS (
        SELECT 
            department_id,
            AVG(salary) as avg_salary,
            COUNT(*) as emp_count
        FROM employees
        GROUP BY department_id
    )
    SELECT 
        e.name,
        e.salary,
        d.name as department,
        ds.avg_salary as dept_avg,
        RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) as salary_rank
    FROM employees e
    JOIN departments d ON e.department_id = d.id
    JOIN dept_stats ds ON e.department_id = ds.department_id
    WHERE e.salary > ds.avg_salary * 0.8
    ORDER BY d.name, salary_rank
`

rows, err := db.QueryContext(ctx, query)

Контекст и отмена

import (
    "context"
    "time"
)

// Установить таймаут для операций с большими файлами
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

db, err := filesql.OpenContext(ctx, "huge_dataset.csv.gz")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Запрос с контекстом для поддержки отмены
rows, err := db.QueryContext(ctx, "SELECT * FROM huge_dataset WHERE status = 'active'")

🤝 Вклад

Вклады приветствуются! Пожалуйста, ознакомьтесь с Руководством по участию для получения более подробной информации.

💖 Поддержка

Если вы находите этот проект полезным, пожалуйста, рассмотрите возможность:

  • ⭐ Поставить звёзду на GitHub - это помогает другим найти проект
  • 💝 Стать спонсором - ваша поддержка поддерживает проект живым и мотивирует непрерывную разработку

Ваша поддержка, будь то через звёзды, спонсорство или вклады, — это то, что движет этот проект вперёд. Спасибо!

📄 Лицензия

Этот проект лицензирован под лицензией MIT - см. файл LICENSE для подробностей.