Commit bec3f283 authored by xiegelin's avatar xiegelin

feat: 支持选择所有表或指定表模式,复用数据库连接

mysql.js 和 postgreSQL.js 新增交互式模式选择(1: 所有表, 2: 指定表),
选择所有表时自动查询 information_schema 获取全部 BASE TABLE 并逐一生成 XML。
同时将数据库连接从循环内提取到循环外复用,避免重复创建连接。
Co-Authored-By: default avatarClaude Opus 4.6 (1M context) <noreply@anthropic.com>
parent 1c12d7bf
...@@ -16,25 +16,28 @@ npm run pgsql # Run PostgreSQL version (interactive CLI) ...@@ -16,25 +16,28 @@ npm run pgsql # Run PostgreSQL version (interactive CLI)
npm run md2drawio # Convert Markdown table file to Draw.io XML npm run md2drawio # Convert Markdown table file to Draw.io XML
``` ```
No test suite or linter is configured. Requires Node.js v18+. No test suite or linter is configured.
## Architecture ## Architecture
Three entry points: Three independent entry points, no shared modules:
- `mysql.js` — MySQL via `mysql2/promise`, queries `information_schema.COLUMNS`. Generates both table-structure XML and sample-data XML. - `mysql.js` — MySQL via `mysql2/promise`, queries `information_schema.COLUMNS` and `information_schema.TABLES`. Generates both table-structure XML (with table comment in label) and sample-data XML.
- `postgreSQL.js` — PostgreSQL via `pg`, joins `information_schema.columns` with `pg_catalog.pg_description` for comments. Generates table-structure XML only. - `postgreSQL.js` — PostgreSQL via `pg`, joins `information_schema.columns` with `pg_catalog.pg_description` for column comments. Generates table-structure XML only (no table comment in label, no sample-data XML).
- `mdToDrawio.js` — Parses a Markdown table file (pipe-delimited) and generates a Draw.io table XML. - `mdToDrawio.js` — Parses a Markdown table file (pipe-delimited) and generates a Draw.io table XML into `sampleDataXml/`.
### Core Flow (mysql.js / postgreSQL.js) ### Core Flow (mysql.js / postgreSQL.js)
1. **Interactive Input**: readline prompts for comma-separated table names (supports Chinese comma `,`) 1. **Interactive Input**: readline prompts for comma-separated table names (supports Chinese comma `,`)
2. **Name Processing**: rejects Chinese characters, converts camelCase → snake_case 2. **Name Processing**: rejects Chinese characters, converts camelCase → snake_case
3. **Database Query**: fetches column names + comments from information_schema 3. **Database Query**: fetches column names + comments from information_schema
4. **XML Generation**: `generateXMLWithTableColumns` builds mxGraph XML with swimlane container and per-column cells. Audit fields (created_at, updated_at, deleted_at, etc.) are styled gray. MySQL version also calls `generateSampleDataXML` to produce a horizontal table layout with header row and empty data row. 4. **XML Generation**: `generateXMLWithTableColumns` builds mxGraph XML with swimlane container and per-column cells. Audit fields (created_at, updated_at, deleted_at, etc.) are styled gray (#B3B3B3). MySQL version also calls `generateSampleDataXML` for a horizontal table layout.
### Key Cross-File Difference
MySQL result rows use uppercase keys (`row.COLUMN_NAME`, `row.COLUMN_COMMENT`) while PostgreSQL uses lowercase (`row.column_name`, `row.column_comment`). This matters when adding shared logic or modifying XML generation.
### Output Directories ### Output Directories
- `tableStructureXml/` — swimlane-style table structure diagrams (mysql.js writes here; postgreSQL.js also writes here) - `tableStructureXml/` — swimlane-style table structure diagrams
- `sampleDataXml/` — horizontal table-layout diagrams for sample data (mysql.js `generateSampleDataXML` and mdToDrawio.js write here) - `sampleDataXml/` — horizontal table-layout diagrams for sample data
### Database Configuration ### Database Configuration
Credentials are hardcoded at the top of each database file. Modify directly in source to point at different databases. Credentials are hardcoded at the top of each database file. Modify directly in source to point at different databases.
...@@ -42,9 +45,9 @@ Credentials are hardcoded at the top of each database file. Modify directly in s ...@@ -42,9 +45,9 @@ Credentials are hardcoded at the top of each database file. Modify directly in s
### Known Constraints ### Known Constraints
- `&` in column comments breaks XML output (no escaping) - `&` in column comments breaks XML output (no escaping)
- Table names must not contain Chinese characters - Table names must not contain Chinese characters
- Fixed geometry: x=340, y=140, width=260 for structure XML - Fixed geometry: x=340, y=140, width=260 for structure XML; colWidth=120 for sample data XML
- PostgreSQL version hardcodes schema to `public` - PostgreSQL version hardcodes schema to `public`
- PostgreSQL version does not generate sample-data XML - PostgreSQL version does not generate sample-data XML or include table comments in the label
## Language ## Language
README and console messages are in Chinese. Code uses English variable/function names. README and console messages are in Chinese. Code uses English variable/function names.
...@@ -29,55 +29,69 @@ async function main() { ...@@ -29,55 +29,69 @@ async function main() {
// const password = 'fxkc@2024'; // const password = 'fxkc@2024';
// const database = 'data_governance_mining'; // const database = 'data_governance_mining';
let tableNames = await askQuestion( const mode = await askQuestion(
'Enter table names (separated by commas): ' '请选择模式 — 1: 所有表, 2: 指定表 (默认2): '
); );
if (!tableNames) {
console.log('表名不得为空!');
return;
}
// Split table names by both Chinese and English commas, and trim whitespace
tableNames = tableNames
.split(/[,,]/)
.map((name) => name.trim())
.filter((name) => name);
// Process each table name
for (const tableName of tableNames) {
// Check if table name contains Chinese characters
if (/[\u4e00-\u9fa5]/.test(tableName)) {
console.error(`Table name "${tableName}" 不能有中文.`);
continue;
}
// Convert camelCase to snake_case // Create connection
let processedTableName = tableName const connection = await mysql.createConnection({
.replace(/([A-Z])/g, '_$1') host,
.toLowerCase(); port: parseInt(port),
// Remove leading underscore if present user,
if (processedTableName[0] === '_') { password,
processedTableName = processedTableName.slice(1); database,
});
console.log('\nConnecting to database...');
console.log('Connected to database successfully!\n');
let tableNames;
if (mode.trim() === '1') {
// Fetch all table names from the database
const [allTables] = await connection.execute(
`SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME`,
[database]
);
tableNames = allTables.map((r) => r.TABLE_NAME);
console.log(`共找到 ${tableNames.length} 张表\n`);
} else {
const input = await askQuestion(
'Enter table names (separated by commas): '
);
if (!input) {
console.log('表名不得为空!');
return;
} }
console.log('Processing table:', processedTableName);
console.log('\nConnecting to database...'); // Split table names by both Chinese and English commas, and trim whitespace
tableNames = input
// Create connection .split(/[,,]/)
const connection = await mysql.createConnection({ .map((name) => name.trim())
host, .filter((name) => name)
port: parseInt(port), .map((name) => {
user, // Check if table name contains Chinese characters
password, if (/[\u4e00-\u9fa5]/.test(name)) {
database, console.error(`Table name "${name}" 不能有中文,已跳过.`);
}); return null;
}
// Convert camelCase to snake_case
let processed = name.replace(/([A-Z])/g, '_$1').toLowerCase();
if (processed[0] === '_') {
processed = processed.slice(1);
}
return processed;
})
.filter(Boolean);
}
console.log('Connected to database successfully!\n'); // Execute query
const sql = `SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? order by ordinal_position`;
// Execute query // Process each table
const sql = `SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? order by ordinal_position`; for (const processedTableName of tableNames) {
console.log('Processing table:', processedTableName);
console.log('Executing query...\n');
const [rows] = await connection.execute(sql, [ const [rows] = await connection.execute(sql, [
database, database,
processedTableName, processedTableName,
...@@ -101,11 +115,11 @@ async function main() { ...@@ -101,11 +115,11 @@ async function main() {
`===== XML file generated successfully for ${processedTableName}! =====` `===== XML file generated successfully for ${processedTableName}! =====`
); );
} }
// Close connection
await connection.end();
console.log('\nConnection closed.');
} }
// Close connection
await connection.end();
console.log('\nConnection closed.');
} catch (error) { } catch (error) {
console.error('Error:', error.message); console.error('Error:', error.message);
} finally { } finally {
......
...@@ -21,7 +21,7 @@ async function main() { ...@@ -21,7 +21,7 @@ async function main() {
const port = '5432'; const port = '5432';
const user = 'postgres'; const user = 'postgres';
const password = '123456'; const password = '123456';
const database = 'ai_data_governance'; const database = 'diting';
// Alternative configuration (commented out) // Alternative configuration (commented out)
// const host = '10.10.10.132'; // const host = '10.10.10.132';
...@@ -30,72 +30,84 @@ async function main() { ...@@ -30,72 +30,84 @@ async function main() {
// const password = 'your_password'; // const password = 'your_password';
// const database = 'your_database'; // const database = 'your_database';
let tableNames = await askQuestion( const mode = await askQuestion(
'Enter table names (separated by commas): ' '请选择模式 — 1: 所有表, 2: 指定表 (默认2): '
); );
if (!tableNames) {
console.log('表名不得为空!');
return;
}
// Split table names by both Chinese and English commas, and trim whitespace const client = new Client({
tableNames = tableNames host,
.split(/[,,]/) port: parseInt(port),
.map((name) => name.trim()) user,
.filter((name) => name); password,
database,
// Process each table name });
for (const tableName of tableNames) {
// Check if table name contains Chinese characters console.log('\nConnecting to database...');
if (/[\u4e00-\u9fa5]/.test(tableName)) { await client.connect();
console.error(`Table name "${tableName}" 不能有中文.`); console.log('Connected to database successfully!\n');
continue;
let tableNames;
if (mode.trim() === '1') {
// Fetch all table names from public schema
const allTablesResult = await client.query(
`SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
ORDER BY table_name`
);
tableNames = allTablesResult.rows.map((r) => r.table_name);
console.log(`共找到 ${tableNames.length} 张表\n`);
} else {
const input = await askQuestion(
'Enter table names (separated by commas): '
);
if (!input) {
console.log('表名不得为空!');
return;
} }
// Convert camelCase to snake_case // Split table names by both Chinese and English commas, and trim whitespace
let processedTableName = tableName tableNames = input
.replace(/([A-Z])/g, '_$1') .split(/[,,]/)
.toLowerCase(); .map((name) => name.trim())
// Remove leading underscore if present .filter((name) => name)
if (processedTableName[0] === '_') { .map((name) => {
processedTableName = processedTableName.slice(1); // Check if table name contains Chinese characters
} if (/[\u4e00-\u9fa5]/.test(name)) {
console.error(`Table name "${name}" 不能有中文,已跳过.`);
return null;
}
// Convert camelCase to snake_case
let processed = name.replace(/([A-Z])/g, '_$1').toLowerCase();
if (processed[0] === '_') {
processed = processed.slice(1);
}
return processed;
})
.filter(Boolean);
}
// Column query SQL
const sql = `
SELECT
c.column_name,
pgd.description as column_comment
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_statio_all_tables st
ON c.table_schema = st.schemaname
AND c.table_name = st.relname
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid
AND pgd.objsubid = c.ordinal_position
WHERE c.table_schema = $1
AND c.table_name = $2
ORDER BY c.ordinal_position
`;
// Process each table
for (const processedTableName of tableNames) {
console.log('Processing table:', processedTableName); console.log('Processing table:', processedTableName);
console.log('\nConnecting to database...');
// Create PostgreSQL client
const client = new Client({
host,
port: parseInt(port),
user,
password,
database,
});
await client.connect();
console.log('Connected to database successfully!\n');
// Execute query - PostgreSQL uses different column names in information_schema
// col_description function is used to get column comments in PostgreSQL
const sql = `
SELECT
c.column_name,
pgd.description as column_comment
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_statio_all_tables st
ON c.table_schema = st.schemaname
AND c.table_name = st.relname
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid
AND pgd.objsubid = c.ordinal_position
WHERE c.table_schema = $1
AND c.table_name = $2
ORDER BY c.ordinal_position
`;
console.log('Executing query...\n');
const result = await client.query(sql, ['public', processedTableName]); const result = await client.query(sql, ['public', processedTableName]);
const rows = result.rows; const rows = result.rows;
...@@ -109,11 +121,11 @@ async function main() { ...@@ -109,11 +121,11 @@ async function main() {
`===== XML file generated successfully for ${processedTableName}! =====` `===== XML file generated successfully for ${processedTableName}! =====`
); );
} }
// Close connection
await client.end();
console.log('\nConnection closed.');
} }
// Close connection
await client.end();
console.log('\nConnection closed.');
} catch (error) { } catch (error) {
console.error('Error:', error.message); console.error('Error:', error.message);
} finally { } finally {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment