refactor(pg-from): some safety on sqlite usage
This commit is contained in:
159
package/postgres/pg-from/Cargo.lock
generated
159
package/postgres/pg-from/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -5,3 +5,4 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rusqlite = { version = "0.32.0", features = ["bundled"] }
|
||||
tempfile = "3.16.0"
|
||||
|
||||
@@ -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')",
|
||||
[],
|
||||
|
||||
Reference in New Issue
Block a user