English | 中文 | 한국어 | Español | Français | 日本語
filesql — это SQL-драйвер для Go, который позволяет запрашивать файлы CSV, TSV, LTSV, Parquet и Excel (XLSX), используя синтаксис SQL SQLite3. Запрашивайте ваши файлы данных напрямую без импорта или трансформации!
Хотите попробовать возможности filesql? Оцените sqly — инструмент командной строки, который использует filesql для лёгкого выполнения SQL-запросов к файлам CSV, TSV, LTSV и Excel прямо из вашего shell. Это идеальный способ испытать мощь 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:
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() // Автосохранение происходит здесь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→ таблицаusersdata.tsv.gz→ таблицаdata/path/to/sales.csv→ таблицаsalesproducts.ltsv.bz2→ таблицаproductsanalytics.parquet→ таблицаanalytics
Поскольку 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 // Это вызовет состояния гонки- Чтение: Полная поддержка файлов Apache Parquet со сложными типами данных
- Запись: Функциональность экспорта реализована (внешнее сжатие не поддерживается, используйте встроенное сжатие Parquet)
- Отображение типов: Типы Parquet отображаются в типы SQLite
- Сжатие: Используется встроенное сжатие Parquet вместо внешнего сжатия
- Большие данные: Файлы Parquet эффективно обрабатываются с помощью колонночного формата Arrow
- Структура 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-файл с несколькими листами:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Лист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;
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 для подробностей.
