feat(pg-from): dump schema from sqlite to postgres.sql
This commit is contained in:
189
package/postgres/pg-from/Cargo.lock
generated
Normal file
189
package/postgres/pg-from/Cargo.lock
generated
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.30.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pg-from"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rusqlite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.32.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
7
package/postgres/pg-from/Cargo.toml
Normal file
7
package/postgres/pg-from/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "pg-from"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rusqlite = { version = "0.32.0", features = ["bundled"] }
|
||||||
168
package/postgres/pg-from/legacy_dump.sql
Normal file
168
package/postgres/pg-from/legacy_dump.sql
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
-- PostgreSQL database dump generated from SQLite
|
||||||
|
CREATE SCHEMA IF NOT EXISTS legacy;
|
||||||
|
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
|
||||||
|
CREATE TABLE legacy."promocode" (
|
||||||
|
"promo_name" text NOT NULL,
|
||||||
|
"traffic_amount" bigint NOT NULL,
|
||||||
|
"remaining_activation" bigint NOT NULL,
|
||||||
|
"term" text,
|
||||||
|
"pool" text DEFAULT 'residential'
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."promocode" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."user" (
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"buy_num" bigint DEFAULT 0,
|
||||||
|
"sub_id" bigint,
|
||||||
|
"used_promo_list" text,
|
||||||
|
"ip_list" text,
|
||||||
|
"pool" text
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."user" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."price" (
|
||||||
|
"gb_cost" bigint,
|
||||||
|
"pool" text NOT NULL DEFAULT 'residential',
|
||||||
|
"gb_cost_usd" double precision NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."price" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."all_user" (
|
||||||
|
"user_id" text,
|
||||||
|
"lang" text,
|
||||||
|
"invited_by" text,
|
||||||
|
"ref_balance" bigint NOT NULL DEFAULT 0,
|
||||||
|
"pers_percent" text,
|
||||||
|
"reg_date" text,
|
||||||
|
"username" text,
|
||||||
|
"email" text,
|
||||||
|
"password" text,
|
||||||
|
"role_id" bigint,
|
||||||
|
"confirmed" bigint,
|
||||||
|
"tgcode" text,
|
||||||
|
"tgcode_expires" text,
|
||||||
|
"ref_balance_usd" double precision NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."all_user" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_email ON legacy."all_user" ("email");
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_username ON legacy."all_user" ("username");
|
||||||
|
|
||||||
|
CREATE TABLE legacy."admin_ref" (
|
||||||
|
"value" text,
|
||||||
|
"name" text,
|
||||||
|
"number" bigint DEFAULT 0,
|
||||||
|
"user" text
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."admin_ref" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."disc_promocode" (
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"discount" double precision NOT NULL,
|
||||||
|
"activations" bigint NOT NULL,
|
||||||
|
"first_use" text NOT NULL,
|
||||||
|
"term" text,
|
||||||
|
"user_for" text,
|
||||||
|
"is_global" bigint DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."disc_promocode" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."request" (
|
||||||
|
"com" text,
|
||||||
|
"amount" bigint,
|
||||||
|
"user_id" text,
|
||||||
|
"username" text,
|
||||||
|
"in_id" SERIAL PRIMARY KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."request" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."system" (
|
||||||
|
"key" text,
|
||||||
|
"value" text
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."system" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."banner" (
|
||||||
|
"name" text,
|
||||||
|
"photo_id" text,
|
||||||
|
"link" text
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."banner" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."subuser" (
|
||||||
|
"sub_id" bigint,
|
||||||
|
"owner_sub_id" bigint,
|
||||||
|
"label" text
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."subuser" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."reseller" (
|
||||||
|
"user_id" text,
|
||||||
|
"token" text,
|
||||||
|
"sub_id" bigint
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."reseller" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."available_pay" (
|
||||||
|
"name" text,
|
||||||
|
"is_available" text
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."available_pay" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."payment" (
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"subuser_id" bigint NOT NULL,
|
||||||
|
"paid" bigint,
|
||||||
|
"order_id" text,
|
||||||
|
"amount_gb" bigint NOT NULL,
|
||||||
|
"balance_before" text NOT NULL,
|
||||||
|
"discount" text,
|
||||||
|
"service" text NOT NULL,
|
||||||
|
"date" text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."payment" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."temp_payment" (
|
||||||
|
"result" text,
|
||||||
|
"payment_id" text,
|
||||||
|
"merchant_id" text,
|
||||||
|
"order_id" text,
|
||||||
|
"amount" bigint
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."temp_payment" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."role" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"name" text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."role" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE TABLE legacy."promo_activations" (
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"promo_name" text NOT NULL,
|
||||||
|
"usage_count" bigint NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY ("user_id", "promo_name")
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE legacy."promo_activations" OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE SEQUENCE legacy_request_seq START WITH 4 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
|
||||||
|
CREATE SEQUENCE legacy_role_seq START WITH 3 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
|
||||||
235
package/postgres/pg-from/src/main.rs
Normal file
235
package/postgres/pg-from/src/main.rs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
use rusqlite::{Connection, Result};
|
||||||
|
use std::env;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// Вывод справки по использованию утилиты.
|
||||||
|
fn print_help(program: &str) {
|
||||||
|
println!(
|
||||||
|
"Usage: {} <sqlite_file> <output_sql_file> <postgres_schema>\n\n\
|
||||||
|
Options:\n -h, --help Show this help message\n\n\
|
||||||
|
Example:\n {} mydb.sqlite legacy_dump.sql legacy",
|
||||||
|
program, program
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Структура для хранения информации о столбце (результат PRAGMA table_info).
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ColumnInfo {
|
||||||
|
cid: i32,
|
||||||
|
name: String,
|
||||||
|
data_type: String,
|
||||||
|
notnull: bool,
|
||||||
|
dflt_value: Option<String>,
|
||||||
|
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") {
|
||||||
|
"bigint".to_string()
|
||||||
|
} else if upper.contains("CHAR") || upper.contains("CLOB") || upper.contains("TEXT") {
|
||||||
|
"text".to_string()
|
||||||
|
} else if upper.contains("BLOB") {
|
||||||
|
"bytea".to_string()
|
||||||
|
} else if upper.contains("REAL") || upper.contains("FLOA") || upper.contains("DOUB") {
|
||||||
|
"double precision".to_string()
|
||||||
|
} else {
|
||||||
|
// значение по умолчанию
|
||||||
|
"text".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Генерирует DDL для создания таблицы в PostgreSQL на основе информации из PRAGMA table_info.
|
||||||
|
fn generate_create_table_sql(table: &str, conn: &Connection, schema: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let mut stmt = conn.prepare(&format!("PRAGMA table_info(\"{}\")", table))?;
|
||||||
|
let columns: Vec<ColumnInfo> = stmt
|
||||||
|
.query_map([], |row| {
|
||||||
|
Ok(ColumnInfo {
|
||||||
|
cid: row.get(0)?,
|
||||||
|
name: row.get(1)?,
|
||||||
|
data_type: row.get(2)?,
|
||||||
|
notnull: row.get::<_, i32>(3)? != 0,
|
||||||
|
dflt_value: row.get(4)?,
|
||||||
|
pk: row.get(5)?,
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.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).
|
||||||
|
let single_autoinc = if pk_columns.len() == 1 {
|
||||||
|
let col = pk_columns[0];
|
||||||
|
col.data_type.to_uppercase().starts_with("INTEGER")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
for col in &columns {
|
||||||
|
let mut col_def = format!("\"{}\" ", col.name);
|
||||||
|
if single_autoinc && pk_columns[0].name == col.name {
|
||||||
|
col_def.push_str("SERIAL PRIMARY KEY");
|
||||||
|
} else {
|
||||||
|
let pg_type = convert_sqlite_type_to_postgres(&col.data_type);
|
||||||
|
col_def.push_str(&pg_type);
|
||||||
|
if col.notnull {
|
||||||
|
col_def.push_str(" NOT NULL");
|
||||||
|
}
|
||||||
|
if let Some(default) = &col.dflt_value {
|
||||||
|
// Простейшая обработка значения по умолчанию; при необходимости можно доработать.
|
||||||
|
col_def.push_str(" DEFAULT ");
|
||||||
|
col_def.push_str(default);
|
||||||
|
}
|
||||||
|
// Если имеется ровно один pk и этот столбец является им, можно добавить PRIMARY KEY inline.
|
||||||
|
if pk_columns.len() == 1 && pk_columns[0].name == col.name {
|
||||||
|
col_def.push_str(" PRIMARY KEY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column_defs.push(col_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если составной ключ (несколько столбцов с pk), добавляем ограничение отдельно.
|
||||||
|
if pk_columns.len() > 1 {
|
||||||
|
let pk_names: Vec<String> = pk_columns
|
||||||
|
.iter()
|
||||||
|
.map(|col| format!("\"{}\"", col.name))
|
||||||
|
.collect();
|
||||||
|
let pk_def = format!("PRIMARY KEY ({})", pk_names.join(", "));
|
||||||
|
column_defs.push(pk_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
let table_sql = format!(
|
||||||
|
"CREATE TABLE {}.\"{}\" (\n {}\n);",
|
||||||
|
schema,
|
||||||
|
table,
|
||||||
|
column_defs.join(",\n ")
|
||||||
|
);
|
||||||
|
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))?;
|
||||||
|
let index_list = stmt.query_map([], |row| {
|
||||||
|
// row.get(1): имя индекса, row.get(2): флаг уникальности
|
||||||
|
let name: String = row.get(1)?;
|
||||||
|
let unique: i32 = row.get(2)?;
|
||||||
|
Ok((name, unique))
|
||||||
|
})?;
|
||||||
|
for index_res in index_list {
|
||||||
|
let (index_name, unique) = index_res?;
|
||||||
|
// Пропускаем автоиндексы
|
||||||
|
if index_name.starts_with("sqlite_autoindex") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Получаем столбцы индекса
|
||||||
|
let mut stmt2 = conn.prepare(&format!("PRAGMA index_info(\"{}\")", index_name))?;
|
||||||
|
let cols_iter = stmt2.query_map([], |row| {
|
||||||
|
let col_name: String = row.get(2)?;
|
||||||
|
Ok(col_name)
|
||||||
|
})?;
|
||||||
|
let mut cols = Vec::new();
|
||||||
|
for col_res in cols_iter {
|
||||||
|
cols.push(col_res?);
|
||||||
|
}
|
||||||
|
let unique_str = if unique != 0 { "UNIQUE " } else { "" };
|
||||||
|
let index_sql = format!(
|
||||||
|
"CREATE {}INDEX {} ON {}.\"{}\" ({});",
|
||||||
|
unique_str,
|
||||||
|
index_name,
|
||||||
|
schema,
|
||||||
|
table,
|
||||||
|
cols.iter()
|
||||||
|
.map(|c| format!("\"{}\"", c))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
indexes.push(index_sql);
|
||||||
|
}
|
||||||
|
Ok(indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
if args.len() < 4 {
|
||||||
|
eprintln!("Error: Insufficient arguments.\n");
|
||||||
|
print_help(&args[0]);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let sqlite_file = &args[1];
|
||||||
|
let output_file = &args[2];
|
||||||
|
let schema = &args[3];
|
||||||
|
|
||||||
|
// Открываем SQLite БД.
|
||||||
|
let conn = Connection::open(sqlite_file)?;
|
||||||
|
|
||||||
|
// Открываем (или создаём) выходной файл.
|
||||||
|
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 для таблицы
|
||||||
|
let table_sql = generate_create_table_sql(&table_name, &conn, schema)?;
|
||||||
|
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')",
|
||||||
|
[],
|
||||||
|
|row| row.get(0)
|
||||||
|
)?;
|
||||||
|
if sqlite_sequence_exists {
|
||||||
|
let mut stmt = conn.prepare("SELECT name, seq FROM sqlite_sequence")?;
|
||||||
|
let seq_iter = stmt.query_map([], |row| {
|
||||||
|
let table: String = row.get(0)?;
|
||||||
|
let seq: i64 = row.get(1)?;
|
||||||
|
Ok((table, seq))
|
||||||
|
})?;
|
||||||
|
for seq_result in seq_iter {
|
||||||
|
let (table, seq) = seq_result?;
|
||||||
|
let seq_name = format!("{}_{}_seq", schema, table);
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
"CREATE SEQUENCE {} START WITH {} INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;",
|
||||||
|
seq_name,
|
||||||
|
seq + 1
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user