From c733b24216733aa2985d9405701fd2efa2d59268 Mon Sep 17 00:00:00 2001 From: yukkop Date: Mon, 3 Feb 2025 20:12:09 +0000 Subject: [PATCH] feat(pg-from): inherit option --- package/postgres/pg-from/--inherit=created_at | 0 package/postgres/pg-from/legacy_dump.sql | 32 +++++------ package/postgres/pg-from/src/main.rs | 56 ++++++++++++------- 3 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 package/postgres/pg-from/--inherit=created_at diff --git a/package/postgres/pg-from/--inherit=created_at b/package/postgres/pg-from/--inherit=created_at new file mode 100644 index 0000000..e69de29 diff --git a/package/postgres/pg-from/legacy_dump.sql b/package/postgres/pg-from/legacy_dump.sql index d1d19e0..2d9a7c1 100644 --- a/package/postgres/pg-from/legacy_dump.sql +++ b/package/postgres/pg-from/legacy_dump.sql @@ -9,7 +9,7 @@ CREATE TABLE legacy."promocode" ( "remaining_activation" bigint NOT NULL, "term" text, "pool" text DEFAULT 'residential' -); +) INHERITS (updated_at); ALTER TABLE legacy."promocode" OWNER TO postgres; @@ -20,7 +20,7 @@ CREATE TABLE legacy."user" ( "used_promo_list" text, "ip_list" text, "pool" text -); +) INHERITS (updated_at); ALTER TABLE legacy."user" OWNER TO postgres; @@ -28,7 +28,7 @@ CREATE TABLE legacy."price" ( "gb_cost" bigint, "pool" text NOT NULL DEFAULT 'residential', "gb_cost_usd" double precision NOT NULL DEFAULT 0 -); +) INHERITS (updated_at); ALTER TABLE legacy."price" OWNER TO postgres; @@ -47,7 +47,7 @@ CREATE TABLE legacy."all_user" ( "tgcode" text, "tgcode_expires" text, "ref_balance_usd" double precision NOT NULL DEFAULT 0 -); +) INHERITS (updated_at); ALTER TABLE legacy."all_user" OWNER TO postgres; @@ -60,7 +60,7 @@ CREATE TABLE legacy."admin_ref" ( "name" text, "number" bigint DEFAULT 0, "user" text -); +) INHERITS (updated_at); ALTER TABLE legacy."admin_ref" OWNER TO postgres; @@ -72,7 +72,7 @@ CREATE TABLE legacy."disc_promocode" ( "term" text, "user_for" text, "is_global" bigint DEFAULT 0 -); +) INHERITS (updated_at); ALTER TABLE legacy."disc_promocode" OWNER TO postgres; @@ -82,14 +82,14 @@ CREATE TABLE legacy."request" ( "user_id" text, "username" text, "in_id" SERIAL PRIMARY KEY -); +) INHERITS (updated_at); ALTER TABLE legacy."request" OWNER TO postgres; CREATE TABLE legacy."system" ( "key" text, "value" text -); +) INHERITS (updated_at); ALTER TABLE legacy."system" OWNER TO postgres; @@ -97,7 +97,7 @@ CREATE TABLE legacy."banner" ( "name" text, "photo_id" text, "link" text -); +) INHERITS (updated_at); ALTER TABLE legacy."banner" OWNER TO postgres; @@ -105,7 +105,7 @@ CREATE TABLE legacy."subuser" ( "sub_id" bigint, "owner_sub_id" bigint, "label" text -); +) INHERITS (updated_at); ALTER TABLE legacy."subuser" OWNER TO postgres; @@ -113,14 +113,14 @@ CREATE TABLE legacy."reseller" ( "user_id" text, "token" text, "sub_id" bigint -); +) INHERITS (updated_at); ALTER TABLE legacy."reseller" OWNER TO postgres; CREATE TABLE legacy."available_pay" ( "name" text, "is_available" text -); +) INHERITS (updated_at); ALTER TABLE legacy."available_pay" OWNER TO postgres; @@ -134,7 +134,7 @@ CREATE TABLE legacy."payment" ( "discount" text, "service" text NOT NULL, "date" text NOT NULL -); +) INHERITS (updated_at); ALTER TABLE legacy."payment" OWNER TO postgres; @@ -144,14 +144,14 @@ CREATE TABLE legacy."temp_payment" ( "merchant_id" text, "order_id" text, "amount" bigint -); +) INHERITS (updated_at); ALTER TABLE legacy."temp_payment" OWNER TO postgres; CREATE TABLE legacy."role" ( "id" SERIAL PRIMARY KEY, "name" text NOT NULL -); +) INHERITS (updated_at); ALTER TABLE legacy."role" OWNER TO postgres; @@ -160,7 +160,7 @@ CREATE TABLE legacy."promo_activations" ( "promo_name" text NOT NULL, "usage_count" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("user_id", "promo_name") -); +) INHERITS (updated_at); ALTER TABLE legacy."promo_activations" OWNER TO postgres; diff --git a/package/postgres/pg-from/src/main.rs b/package/postgres/pg-from/src/main.rs index 0aed042..bbb41b4 100644 --- a/package/postgres/pg-from/src/main.rs +++ b/package/postgres/pg-from/src/main.rs @@ -7,9 +7,9 @@ use std::io::Write; /// Вывод справки по использованию утилиты. fn print_help(program: &str) { println!( - "Usage: {} \n\n\ - Options:\n -h, --help Show this help message\n\n\ - Example:\n {} mydb.sqlite legacy_dump.sql legacy", + "Usage: {} [--inherit=]\n\n\ + Options:\n -h, --help Show this help message\n --inherit= Specify parent table(s) to inherit (e.g. \"created_at, updated_at\")\n\n\ + Example:\n {} mydb.sqlite legacy_dump.sql legacy --inherit=\"created_at, updated_at\"", program, program ); } @@ -27,7 +27,7 @@ struct ColumnInfo { /// Преобразует строку типа из SQLite в тип PostgreSQL. /// Здесь применяется простая логика: если тип содержит "INT" – выдаётся bigint, -/// если содержит "CHAR"/"TEXT"/"CLOB" – text, если "REAL" или "FLOA"/"DOUB" – double precision, и т.д. +/// если содержит "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") { @@ -39,13 +39,19 @@ fn convert_sqlite_type_to_postgres(sqlite_type: &str) -> 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> { +/// Если задан параметр наследования (inherit_clause), то после списка столбцов добавляется +/// конструкция INHERITS (). +fn generate_create_table_sql( + table: &str, + conn: &Connection, + schema: &str, + inherit_clause: Option<&str>, +) -> Result> { let mut stmt = conn.prepare(&format!("PRAGMA table_info(\"{}\")", table))?; let columns: Vec = stmt .query_map([], |row| { @@ -60,7 +66,7 @@ fn generate_create_table_sql(table: &str, conn: &Connection, schema: &str) -> Re })? .collect::, _>>()?; - // Собираем список столбцов, а также определяем список первичных ключей. + // Собираем список столбцов и определяем первичные ключи. let mut column_defs = Vec::new(); let pk_columns: Vec<&ColumnInfo> = columns.iter().filter(|col| col.pk > 0).collect(); @@ -84,11 +90,9 @@ fn generate_create_table_sql(table: &str, conn: &Connection, schema: &str) -> Re 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"); } @@ -96,7 +100,7 @@ fn generate_create_table_sql(table: &str, conn: &Connection, schema: &str) -> Re column_defs.push(col_def); } - // Если составной ключ (несколько столбцов с pk), добавляем ограничение отдельно. + // Если составной ключ, добавляем ограничение отдельно. if pk_columns.len() > 1 { let pk_names: Vec = pk_columns .iter() @@ -106,34 +110,36 @@ fn generate_create_table_sql(table: &str, conn: &Connection, schema: &str) -> Re column_defs.push(pk_def); } - let table_sql = format!( - "CREATE TABLE {}.\"{}\" (\n {}\n);", + // Собираем итоговую инструкцию. + let mut table_sql = format!( + "CREATE TABLE {}.\"{}\" (\n {}\n)", schema, table, column_defs.join(",\n ") ); + if let Some(inh) = inherit_clause { + table_sql.push_str(&format!(" INHERITS ({})", inh)); + } + table_sql.push(';'); Ok(table_sql) } /// Генерирует DDL для индексов таблицы. -/// Используется PRAGMA index_list и PRAGMA index_info для извлечения информации об индексах. +/// Используются PRAGMA index_list и PRAGMA index_info для извлечения информации об индексах. /// Автоиндексы (имена начинаются с "sqlite_autoindex") пропускаются. fn generate_indexes_sql(table: &str, conn: &Connection, schema: &str) -> Result, Box> { 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)?; @@ -168,7 +174,7 @@ fn main() -> Result<(), Box> { std::process::exit(0); } if args.len() < 4 { - eprintln!("Error: Insufficient arguments.\n"); + eprintln!("Error: Недостаточно аргументов.\n"); print_help(&args[0]); std::process::exit(1); } @@ -176,6 +182,14 @@ fn main() -> Result<(), Box> { let output_file = &args[2]; let schema = &args[3]; + // Если передана опция наследования, извлекаем её значение. + let mut inherit_clause: Option = None; + for arg in &args[4..] { + if arg.starts_with("--inherit=") { + inherit_clause = Some(arg["--inherit=".len()..].to_string()); + } + } + // Открываем SQLite БД. let conn = Connection::open(sqlite_file)?; @@ -194,19 +208,19 @@ fn main() -> Result<(), Box> { 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)?; + // Генерируем 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 для индексов + // Генерируем DDL для индексов. let indexes = generate_indexes_sql(&table_name, &conn, schema)?; for idx in indexes { writeln!(out, "{}\n", idx)?; } } - // Если имеется таблица sqlite_sequence, можно обработать автоинкрементные значения. + // Обработка таблицы sqlite_sequence (для автоинкрементных значений). let sqlite_sequence_exists: bool = conn.query_row( "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='sqlite_sequence')", [],