refactor(pg-from): some safety on sqlite usage

This commit is contained in:
2025-02-03 20:13:57 +00:00
parent c733b24216
commit 3a09bafca8
3 changed files with 166 additions and 26 deletions

View File

@@ -35,6 +35,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
@@ -47,6 +57,24 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi",
"windows-targets",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -65,6 +93,12 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
@@ -76,6 +110,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "once_cell"
version = "1.20.2"
@@ -87,6 +127,7 @@ name = "pg-from"
version = "0.1.0"
dependencies = [
"rusqlite",
"tempfile",
]
[[package]]
@@ -127,6 +168,19 @@ dependencies = [
"smallvec",
]
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -150,6 +204,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
@@ -168,6 +236,97 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]
[[package]]
name = "zerocopy"
version = "0.7.35"

View File

@@ -5,3 +5,4 @@ edition = "2021"
[dependencies]
rusqlite = { version = "0.32.0", features = ["bundled"] }
tempfile = "3.16.0"

View File

@@ -1,10 +1,11 @@
use rusqlite::{Connection, Result};
use std::env;
use std::error::Error;
use std::fs;
use std::fs::File;
use std::io::Write;
use tempfile::NamedTempFile;
/// Вывод справки по использованию утилиты.
fn print_help(program: &str) {
println!(
"Usage: {} <sqlite_file> <output_sql_file> <postgres_schema> [--inherit=<inherit_clause>]\n\n\
@@ -14,7 +15,6 @@ fn print_help(program: &str) {
);
}
/// Структура для хранения информации о столбце (результат PRAGMA table_info).
#[derive(Debug)]
struct ColumnInfo {
cid: i32,
@@ -25,9 +25,6 @@ struct ColumnInfo {
pk: i32,
}
/// Преобразует строку типа из SQLite в тип PostgreSQL.
/// Здесь применяется простая логика: если тип содержит "INT" выдаётся bigint,
/// если содержит "CHAR", "TEXT" или "CLOB" text, если "REAL", "FLOA" или "DOUB" double precision, и т.д.
fn convert_sqlite_type_to_postgres(sqlite_type: &str) -> String {
let upper = sqlite_type.to_uppercase();
if upper.contains("INT") {
@@ -43,9 +40,6 @@ fn convert_sqlite_type_to_postgres(sqlite_type: &str) -> String {
}
}
/// Генерирует DDL для создания таблицы в PostgreSQL на основе информации из PRAGMA table_info.
/// Если задан параметр наследования (inherit_clause), то после списка столбцов добавляется
/// конструкция INHERITS (<inherit_clause>).
fn generate_create_table_sql(
table: &str,
conn: &Connection,
@@ -66,12 +60,10 @@ fn generate_create_table_sql(
})?
.collect::<Result<Vec<_>, _>>()?;
// Собираем список столбцов и определяем первичные ключи.
let mut column_defs = Vec::new();
let pk_columns: Vec<&ColumnInfo> = columns.iter().filter(|col| col.pk > 0).collect();
// Если имеется ровно один первичный ключ и его тип начинается с "INTEGER",
// то для него генерируем тип SERIAL (PostgreSQL автоматически создаст sequence).
// Если ровно один PK и его тип начинается с "INTEGER", генерируем SERIAL.
let single_autoinc = if pk_columns.len() == 1 {
let col = pk_columns[0];
col.data_type.to_uppercase().starts_with("INTEGER")
@@ -100,7 +92,6 @@ fn generate_create_table_sql(
column_defs.push(col_def);
}
// Если составной ключ, добавляем ограничение отдельно.
if pk_columns.len() > 1 {
let pk_names: Vec<String> = pk_columns
.iter()
@@ -110,7 +101,6 @@ fn generate_create_table_sql(
column_defs.push(pk_def);
}
// Собираем итоговую инструкцию.
let mut table_sql = format!(
"CREATE TABLE {}.\"{}\" (\n {}\n)",
schema,
@@ -124,9 +114,6 @@ fn generate_create_table_sql(
Ok(table_sql)
}
/// Генерирует DDL для индексов таблицы.
/// Используются PRAGMA index_list и PRAGMA index_info для извлечения информации об индексах.
/// Автоиндексы (имена начинаются с "sqlite_autoindex") пропускаются.
fn generate_indexes_sql(table: &str, conn: &Connection, schema: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut indexes = Vec::new();
let mut stmt = conn.prepare(&format!("PRAGMA index_list(\"{}\")", table))?;
@@ -167,7 +154,6 @@ fn generate_indexes_sql(table: &str, conn: &Connection, schema: &str) -> Result<
}
fn main() -> Result<(), Box<dyn Error>> {
// Обработка аргументов командной строки.
let args: Vec<String> = env::args().collect();
if args.iter().any(|arg| arg == "--help" || arg == "-h") {
print_help(&args[0]);
@@ -182,7 +168,6 @@ fn main() -> Result<(), Box<dyn Error>> {
let output_file = &args[2];
let schema = &args[3];
// Если передана опция наследования, извлекаем её значение.
let mut inherit_clause: Option<String> = None;
for arg in &args[4..] {
if arg.starts_with("--inherit=") {
@@ -190,37 +175,32 @@ fn main() -> Result<(), Box<dyn Error>> {
}
}
// Открываем SQLite БД.
let conn = Connection::open(sqlite_file)?;
let temp_file = NamedTempFile::new()?;
fs::copy(sqlite_file, temp_file.path())?;
let conn = Connection::open(temp_file.path())?;
// Открываем (или создаём) выходной файл.
let mut out = File::create(output_file)?;
// Записываем заголовок.
writeln!(out, "-- PostgreSQL database dump generated from SQLite")?;
writeln!(out, "CREATE SCHEMA IF NOT EXISTS {};\n", schema)?;
writeln!(out, "SET client_encoding = 'UTF8';\n")?;
// Получаем имена таблиц (исключая внутренние).
let mut stmt = conn.prepare(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
)?;
let table_names = stmt.query_map([], |row| row.get(0))?;
for table_name_result in table_names {
let table_name: String = table_name_result?;
// Генерируем DDL для таблицы, передавая также опциональный inherit_clause.
let table_sql = generate_create_table_sql(&table_name, &conn, schema, inherit_clause.as_deref())?;
writeln!(out, "{}\n", table_sql)?;
writeln!(out, "ALTER TABLE {}.\"{}\" OWNER TO postgres;\n", schema, table_name)?;
// Генерируем DDL для индексов.
let indexes = generate_indexes_sql(&table_name, &conn, schema)?;
for idx in indexes {
writeln!(out, "{}\n", idx)?;
}
}
// Обработка таблицы sqlite_sequence (для автоинкрементных значений).
let sqlite_sequence_exists: bool = conn.query_row(
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='sqlite_sequence')",
[],