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(
'请选择模式 — 1: 所有表, 2: 指定表 (默认2): '
);
// Create connection
const connection = await mysql.createConnection({
host,
port: parseInt(port),
user,
password,
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): ' 'Enter table names (separated by commas): '
); );
if (!tableNames) { if (!input) {
console.log('表名不得为空!'); console.log('表名不得为空!');
return; return;
} }
// Split table names by both Chinese and English commas, and trim whitespace // Split table names by both Chinese and English commas, and trim whitespace
tableNames = tableNames tableNames = input
.split(/[,,]/) .split(/[,,]/)
.map((name) => name.trim()) .map((name) => name.trim())
.filter((name) => name); .filter((name) => name)
.map((name) => {
// Process each table name
for (const tableName of tableNames) {
// Check if table name contains Chinese characters // Check if table name contains Chinese characters
if (/[\u4e00-\u9fa5]/.test(tableName)) { if (/[\u4e00-\u9fa5]/.test(name)) {
console.error(`Table name "${tableName}" 不能有中文.`); console.error(`Table name "${name}" 不能有中文,已跳过.`);
continue; return null;
} }
// Convert camelCase to snake_case // Convert camelCase to snake_case
let processedTableName = tableName let processed = name.replace(/([A-Z])/g, '_$1').toLowerCase();
.replace(/([A-Z])/g, '_$1') if (processed[0] === '_') {
.toLowerCase(); processed = processed.slice(1);
// Remove leading underscore if present }
if (processedTableName[0] === '_') { return processed;
processedTableName = processedTableName.slice(1); })
.filter(Boolean);
} }
console.log('Processing table:', processedTableName);
console.log('\nConnecting to database...');
// Create connection
const connection = await mysql.createConnection({
host,
port: parseInt(port),
user,
password,
database,
});
console.log('Connected to database successfully!\n');
// Execute query // Execute query
const sql = `SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? order by ordinal_position`; const sql = `SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? order by ordinal_position`;
console.log('Executing query...\n'); // Process each table
for (const processedTableName of tableNames) {
console.log('Processing table:', processedTableName);
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 // Close connection
await connection.end(); await connection.end();
console.log('\nConnection closed.'); 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,55 +30,64 @@ async function main() { ...@@ -30,55 +30,64 @@ 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(
'请选择模式 — 1: 所有表, 2: 指定表 (默认2): '
);
const client = new Client({
host,
port: parseInt(port),
user,
password,
database,
});
console.log('\nConnecting to database...');
await client.connect();
console.log('Connected to database successfully!\n');
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): ' 'Enter table names (separated by commas): '
); );
if (!tableNames) { if (!input) {
console.log('表名不得为空!'); console.log('表名不得为空!');
return; return;
} }
// Split table names by both Chinese and English commas, and trim whitespace // Split table names by both Chinese and English commas, and trim whitespace
tableNames = tableNames tableNames = input
.split(/[,,]/) .split(/[,,]/)
.map((name) => name.trim()) .map((name) => name.trim())
.filter((name) => name); .filter((name) => name)
.map((name) => {
// Process each table name
for (const tableName of tableNames) {
// Check if table name contains Chinese characters // Check if table name contains Chinese characters
if (/[\u4e00-\u9fa5]/.test(tableName)) { if (/[\u4e00-\u9fa5]/.test(name)) {
console.error(`Table name "${tableName}" 不能有中文.`); console.error(`Table name "${name}" 不能有中文,已跳过.`);
continue; return null;
} }
// Convert camelCase to snake_case // Convert camelCase to snake_case
let processedTableName = tableName let processed = name.replace(/([A-Z])/g, '_$1').toLowerCase();
.replace(/([A-Z])/g, '_$1') if (processed[0] === '_') {
.toLowerCase(); processed = processed.slice(1);
// Remove leading underscore if present }
if (processedTableName[0] === '_') { return processed;
processedTableName = processedTableName.slice(1); })
.filter(Boolean);
} }
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 // Column query SQL
// col_description function is used to get column comments in PostgreSQL
const sql = ` const sql = `
SELECT SELECT
c.column_name, c.column_name,
...@@ -95,7 +104,10 @@ async function main() { ...@@ -95,7 +104,10 @@ async function main() {
ORDER BY c.ordinal_position ORDER BY c.ordinal_position
`; `;
console.log('Executing query...\n'); // Process each table
for (const processedTableName of tableNames) {
console.log('Processing table:', processedTableName);
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 // Close connection
await client.end(); await client.end();
console.log('\nConnection closed.'); 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