@bytebase/dbhub 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +34 -33
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1489 -13
  4. package/dist/resources/employee-sqlite/employee.sql +117 -0
  5. package/dist/resources/employee-sqlite/load_department.sql +10 -0
  6. package/dist/resources/employee-sqlite/load_dept_emp.sql +1103 -0
  7. package/dist/resources/employee-sqlite/load_dept_manager.sql +17 -0
  8. package/dist/resources/employee-sqlite/load_employee.sql +1000 -0
  9. package/dist/resources/employee-sqlite/load_salary1.sql +9488 -0
  10. package/dist/resources/employee-sqlite/load_title.sql +1470 -0
  11. package/dist/resources/employee-sqlite/object.sql +74 -0
  12. package/dist/resources/employee-sqlite/show_elapsed.sql +4 -0
  13. package/dist/resources/employee-sqlite/test_employee_md5.sql +119 -0
  14. package/package.json +5 -4
  15. package/dist/config/demo-loader.js +0 -45
  16. package/dist/config/env.js +0 -146
  17. package/dist/connectors/interface.js +0 -55
  18. package/dist/connectors/manager.js +0 -91
  19. package/dist/connectors/mysql/index.js +0 -169
  20. package/dist/connectors/postgres/index.js +0 -172
  21. package/dist/connectors/sqlite/index.js +0 -208
  22. package/dist/connectors/sqlserver/index.js +0 -186
  23. package/dist/prompts/db-explainer.js +0 -201
  24. package/dist/prompts/index.js +0 -11
  25. package/dist/prompts/sql-generator.js +0 -114
  26. package/dist/resources/index.js +0 -12
  27. package/dist/resources/schema.js +0 -36
  28. package/dist/resources/tables.js +0 -17
  29. package/dist/server.js +0 -140
  30. package/dist/tools/index.js +0 -11
  31. package/dist/tools/list-connectors.js +0 -43
  32. package/dist/tools/run-query.js +0 -32
  33. package/dist/utils/response-formatter.js +0 -109
@@ -1,186 +0,0 @@
1
- import sql from 'mssql';
2
- import { ConnectorRegistry } from '../interface.js';
3
- /**
4
- * SQL Server DSN parser
5
- * Expected format: mssql://username:password@host:port/database
6
- */
7
- export class SQLServerDSNParser {
8
- parse(dsn) {
9
- // Remove the protocol prefix
10
- if (!this.isValidDSN(dsn)) {
11
- throw new Error('Invalid SQL Server DSN format. Expected: mssql://username:password@host:port/database');
12
- }
13
- // Parse the DSN
14
- const url = new URL(dsn);
15
- const host = url.hostname;
16
- const port = url.port ? parseInt(url.port, 10) : 1433; // Default SQL Server port
17
- const database = url.pathname.substring(1); // Remove leading slash
18
- const user = url.username;
19
- const password = url.password;
20
- // Parse additional options from query parameters
21
- const options = {};
22
- for (const [key, value] of url.searchParams.entries()) {
23
- if (key === 'encrypt') {
24
- options.encrypt = value;
25
- }
26
- else if (key === 'trustServerCertificate') {
27
- options.trustServerCertificate = value === 'true';
28
- }
29
- else if (key === 'connectTimeout') {
30
- options.connectTimeout = parseInt(value, 10);
31
- }
32
- else if (key === 'requestTimeout') {
33
- options.requestTimeout = parseInt(value, 10);
34
- }
35
- }
36
- // Construct and return the config
37
- return {
38
- user,
39
- password,
40
- server: host,
41
- port,
42
- database,
43
- options: {
44
- encrypt: options.encrypt ?? true, // Default to encrypted connection
45
- trustServerCertificate: options.trustServerCertificate === true, // Need explicit conversion to boolean
46
- connectTimeout: options.connectTimeout ?? 15000,
47
- requestTimeout: options.requestTimeout ?? 15000,
48
- },
49
- };
50
- }
51
- getSampleDSN() {
52
- return 'mssql://username:password@localhost:1433/database?encrypt=true';
53
- }
54
- isValidDSN(dsn) {
55
- try {
56
- const url = new URL(dsn);
57
- return url.protocol === 'mssql:';
58
- }
59
- catch (e) {
60
- return false;
61
- }
62
- }
63
- }
64
- /**
65
- * SQL Server connector
66
- */
67
- export class SQLServerConnector {
68
- constructor() {
69
- this.id = 'sqlserver';
70
- this.name = 'SQL Server';
71
- this.dsnParser = new SQLServerDSNParser();
72
- }
73
- async connect(dsn) {
74
- try {
75
- this.config = this.dsnParser.parse(dsn);
76
- if (!this.config.options) {
77
- this.config.options = {};
78
- }
79
- this.connection = await new sql.ConnectionPool(this.config).connect();
80
- }
81
- catch (error) {
82
- throw error;
83
- }
84
- }
85
- async disconnect() {
86
- if (this.connection) {
87
- await this.connection.close();
88
- this.connection = undefined;
89
- }
90
- }
91
- async getTables() {
92
- if (!this.connection) {
93
- throw new Error('Not connected to SQL Server database');
94
- }
95
- try {
96
- const result = await this.connection.request().query(`
97
- SELECT TABLE_NAME
98
- FROM INFORMATION_SCHEMA.TABLES
99
- ORDER BY TABLE_NAME
100
- `);
101
- return result.recordset.map((row) => row.TABLE_NAME);
102
- }
103
- catch (error) {
104
- throw new Error(`Failed to get tables: ${error.message}`);
105
- }
106
- }
107
- async tableExists(tableName) {
108
- if (!this.connection) {
109
- throw new Error('Not connected to SQL Server database');
110
- }
111
- try {
112
- const result = await this.connection.request()
113
- .input('tableName', sql.VarChar, tableName)
114
- .query(`
115
- SELECT COUNT(*) as count
116
- FROM INFORMATION_SCHEMA.TABLES
117
- WHERE TABLE_NAME = @tableName
118
- `);
119
- return result.recordset[0].count > 0;
120
- }
121
- catch (error) {
122
- throw new Error(`Failed to check if table exists: ${error.message}`);
123
- }
124
- }
125
- async getTableSchema(tableName) {
126
- if (!this.connection) {
127
- throw new Error('Not connected to SQL Server database');
128
- }
129
- try {
130
- const result = await this.connection.request()
131
- .input('tableName', sql.VarChar, tableName)
132
- .query(`
133
- SELECT
134
- COLUMN_NAME as column_name,
135
- DATA_TYPE as data_type,
136
- IS_NULLABLE as is_nullable,
137
- COLUMN_DEFAULT as column_default
138
- FROM INFORMATION_SCHEMA.COLUMNS
139
- WHERE TABLE_NAME = @tableName
140
- ORDER BY ORDINAL_POSITION
141
- `);
142
- return result.recordset;
143
- }
144
- catch (error) {
145
- throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`);
146
- }
147
- }
148
- async executeQuery(query) {
149
- if (!this.connection) {
150
- throw new Error('Not connected to SQL Server database');
151
- }
152
- const safetyCheck = this.validateQuery(query);
153
- if (!safetyCheck.isValid) {
154
- throw new Error(safetyCheck.message || "Query validation failed");
155
- }
156
- try {
157
- const result = await this.connection.request().query(query);
158
- return {
159
- rows: result.recordset || [],
160
- fields: result.recordset && result.recordset.length > 0
161
- ? Object.keys(result.recordset[0]).map(key => ({
162
- name: key,
163
- }))
164
- : [],
165
- rowCount: result.rowsAffected[0] || 0,
166
- };
167
- }
168
- catch (error) {
169
- throw new Error(`Failed to execute query: ${error.message}`);
170
- }
171
- }
172
- validateQuery(query) {
173
- // Basic check to prevent non-SELECT queries
174
- const normalizedQuery = query.trim().toLowerCase();
175
- if (!normalizedQuery.startsWith('select')) {
176
- return {
177
- isValid: false,
178
- message: "Only SELECT queries are allowed for security reasons."
179
- };
180
- }
181
- return { isValid: true };
182
- }
183
- }
184
- // Create and register the connector
185
- const sqlServerConnector = new SQLServerConnector();
186
- ConnectorRegistry.register(sqlServerConnector);
@@ -1,201 +0,0 @@
1
- import { z } from "zod";
2
- import { ConnectorManager } from '../connectors/manager.js';
3
- import { formatPromptSuccessResponse, formatPromptErrorResponse } from '../utils/response-formatter.js';
4
- // Schema for database explainer prompt
5
- export const dbExplainerSchema = {
6
- target: z.string().describe("Name of the table, column, or database to explain")
7
- };
8
- /**
9
- * Database Explainer Prompt Handler
10
- * Provides explanations about database elements
11
- */
12
- export async function dbExplainerPromptHandler({ target }, _extra) {
13
- try {
14
- const connector = ConnectorManager.getCurrentConnector();
15
- // First check if this is a table name
16
- const tables = await connector.getTables();
17
- const normalizedTarget = target.toLowerCase();
18
- // Check if target matches a table
19
- const matchingTable = tables.find(t => t.toLowerCase() === normalizedTarget);
20
- if (matchingTable) {
21
- // Explain the table
22
- const columns = await connector.getTableSchema(matchingTable);
23
- // Create a table structure description
24
- const tableDescription = `Table: ${matchingTable}
25
-
26
- Structure:
27
- ${columns.map(col => `- ${col.column_name} (${col.data_type})${col.is_nullable === 'YES' ? ', nullable' : ''}${col.column_default ? `, default: ${col.column_default}` : ''}`).join('\n')}
28
-
29
- Purpose:
30
- This table appears to store ${determineTablePurpose(matchingTable, columns)}
31
-
32
- Relationships:
33
- ${determineRelationships(matchingTable, columns)}`;
34
- return formatPromptSuccessResponse(tableDescription);
35
- }
36
- // Check if target is a table.column format
37
- if (target.includes('.')) {
38
- const [tableName, columnName] = target.split('.');
39
- if (tables.find(t => t.toLowerCase() === tableName.toLowerCase())) {
40
- // Get column info
41
- const columns = await connector.getTableSchema(tableName);
42
- const column = columns.find(c => c.column_name.toLowerCase() === columnName.toLowerCase());
43
- if (column) {
44
- const columnDescription = `Column: ${tableName}.${column.column_name}
45
-
46
- Type: ${column.data_type}
47
- Nullable: ${column.is_nullable === 'YES' ? 'Yes' : 'No'}
48
- Default: ${column.column_default || 'None'}
49
-
50
- Purpose:
51
- ${determineColumnPurpose(column.column_name, column.data_type)}`;
52
- return formatPromptSuccessResponse(columnDescription);
53
- }
54
- }
55
- }
56
- // If target is not a specific table or column, provide database overview
57
- // Determine if 'database' or similar term is in the target
58
- if (['database', 'db', 'schema', 'overview', 'all'].includes(normalizedTarget)) {
59
- let dbOverview = `Database Overview
60
-
61
- Tables: ${tables.length}
62
- ${tables.map(t => `- ${t}`).join('\n')}
63
-
64
- This database ${describeDatabasePurpose(tables)}`;
65
- return formatPromptSuccessResponse(dbOverview);
66
- }
67
- // If no match is found but the target could be a partial match
68
- const possibleTableMatches = tables.filter(t => t.toLowerCase().includes(normalizedTarget) ||
69
- normalizedTarget.includes(t.toLowerCase()));
70
- if (possibleTableMatches.length > 0) {
71
- return formatPromptSuccessResponse(`Could not find exact match for "${target}". Did you mean one of these tables?\n\n${possibleTableMatches.join('\n')}`);
72
- }
73
- // No match found
74
- return formatPromptErrorResponse(`Could not find a table, column, or database feature matching "${target}"`, "NOT_FOUND");
75
- }
76
- catch (error) {
77
- return formatPromptErrorResponse(`Error explaining database: ${error.message}`, "EXPLANATION_ERROR");
78
- }
79
- }
80
- /**
81
- * Helper function to make an educated guess about the purpose of a table
82
- * based on its name and columns
83
- */
84
- function determineTablePurpose(tableName, columns) {
85
- const lowerTableName = tableName.toLowerCase();
86
- const columnNames = columns.map(c => c.column_name.toLowerCase());
87
- // Check for common patterns
88
- if (lowerTableName.includes('user') || columnNames.includes('username') || columnNames.includes('email')) {
89
- return 'user information and profiles';
90
- }
91
- if (lowerTableName.includes('order') || lowerTableName.includes('purchase')) {
92
- return 'order or purchase transactions';
93
- }
94
- if (lowerTableName.includes('product') || lowerTableName.includes('item')) {
95
- return 'product or item information';
96
- }
97
- if (lowerTableName.includes('log') || columnNames.includes('timestamp')) {
98
- return 'event or activity logs';
99
- }
100
- if (columnNames.includes('created_at') && columnNames.includes('updated_at')) {
101
- return 'tracking timestamped data records';
102
- }
103
- // Default
104
- return 'data related to ' + tableName;
105
- }
106
- /**
107
- * Helper function to determine potential relationships based on column names
108
- */
109
- function determineRelationships(tableName, columns) {
110
- const potentialRelationships = [];
111
- // Look for _id columns which often indicate foreign keys
112
- const idColumns = columns.filter(c => c.column_name.toLowerCase().endsWith('_id') &&
113
- !c.column_name.toLowerCase().startsWith(tableName.toLowerCase()));
114
- if (idColumns.length > 0) {
115
- idColumns.forEach(col => {
116
- const referencedTable = col.column_name.toLowerCase().replace('_id', '');
117
- potentialRelationships.push(`May have a relationship with the "${referencedTable}" table (via ${col.column_name})`);
118
- });
119
- }
120
- // Check if the table itself might be referenced by others
121
- if (columns.some(c => c.column_name.toLowerCase() === 'id')) {
122
- potentialRelationships.push(`May be referenced by other tables as "${tableName.toLowerCase()}_id"`);
123
- }
124
- return potentialRelationships.length > 0
125
- ? potentialRelationships.join('\n')
126
- : 'No obvious relationships identified based on column names';
127
- }
128
- /**
129
- * Helper function to determine the purpose of a column based on naming patterns
130
- */
131
- function determineColumnPurpose(columnName, dataType) {
132
- const lowerColumnName = columnName.toLowerCase();
133
- if (lowerColumnName === 'id') {
134
- return 'Primary identifier for records in this table';
135
- }
136
- if (lowerColumnName.endsWith('_id')) {
137
- const referencedTable = lowerColumnName.replace('_id', '');
138
- return `Foreign key reference to the "${referencedTable}" table`;
139
- }
140
- if (lowerColumnName.includes('name')) {
141
- return 'Stores name information';
142
- }
143
- if (lowerColumnName.includes('email')) {
144
- return 'Stores email address information';
145
- }
146
- if (lowerColumnName.includes('password') || lowerColumnName.includes('hash')) {
147
- return 'Stores security credential information (likely hashed)';
148
- }
149
- if (lowerColumnName === 'created_at' || lowerColumnName === 'created_on') {
150
- return 'Timestamp for when the record was created';
151
- }
152
- if (lowerColumnName === 'updated_at' || lowerColumnName === 'modified_at') {
153
- return 'Timestamp for when the record was last updated';
154
- }
155
- if (lowerColumnName.includes('date') || lowerColumnName.includes('time')) {
156
- return 'Stores date or time information';
157
- }
158
- if (lowerColumnName.includes('price') || lowerColumnName.includes('cost') || lowerColumnName.includes('amount')) {
159
- return 'Stores monetary value information';
160
- }
161
- // Data type specific purposes
162
- if (dataType.includes('boolean')) {
163
- return 'Stores a true/false flag';
164
- }
165
- if (dataType.includes('json')) {
166
- return 'Stores structured JSON data';
167
- }
168
- if (dataType.includes('text') || dataType.includes('varchar') || dataType.includes('char')) {
169
- return 'Stores text information';
170
- }
171
- // Default
172
- return `Stores ${dataType} data`;
173
- }
174
- /**
175
- * Helper function to describe the overall database purpose based on tables
176
- */
177
- function describeDatabasePurpose(tables) {
178
- const tableNames = tables.map(t => t.toLowerCase());
179
- if (tableNames.some(t => t.includes('user')) &&
180
- tableNames.some(t => t.includes('order'))) {
181
- return 'appears to be an e-commerce or customer order management system';
182
- }
183
- if (tableNames.some(t => t.includes('patient')) ||
184
- tableNames.some(t => t.includes('medical'))) {
185
- return 'appears to be related to healthcare or medical record management';
186
- }
187
- if (tableNames.some(t => t.includes('student')) ||
188
- tableNames.some(t => t.includes('course'))) {
189
- return 'appears to be related to education or student management';
190
- }
191
- if (tableNames.some(t => t.includes('employee')) ||
192
- tableNames.some(t => t.includes('payroll'))) {
193
- return 'appears to be related to HR or employee management';
194
- }
195
- if (tableNames.some(t => t.includes('inventory')) ||
196
- tableNames.some(t => t.includes('stock'))) {
197
- return 'appears to be related to inventory or stock management';
198
- }
199
- // Default
200
- return 'contains multiple tables that store related information';
201
- }
@@ -1,11 +0,0 @@
1
- import { sqlGeneratorPromptHandler, sqlGeneratorSchema } from './sql-generator.js';
2
- import { dbExplainerPromptHandler, dbExplainerSchema } from './db-explainer.js';
3
- /**
4
- * Register all prompt handlers with the MCP server
5
- */
6
- export function registerPrompts(server) {
7
- // Register SQL Generator prompt
8
- server.prompt("generate_sql", "Generate SQL queries from natural language descriptions", sqlGeneratorSchema, sqlGeneratorPromptHandler);
9
- // Register Database Explainer prompt
10
- server.prompt("explain_db", "Get explanations about database tables, columns, and structures", dbExplainerSchema, dbExplainerPromptHandler);
11
- }
@@ -1,114 +0,0 @@
1
- import { z } from "zod";
2
- import { ConnectorManager } from '../connectors/manager.js';
3
- import { formatPromptSuccessResponse, formatPromptErrorResponse } from '../utils/response-formatter.js';
4
- // Schema for SQL generator prompt
5
- export const sqlGeneratorSchema = {
6
- description: z.string().describe("Natural language description of the SQL query to generate"),
7
- dialect: z.enum(["postgres", "sqlite"]).optional().describe("SQL dialect to use (optional)")
8
- };
9
- /**
10
- * SQL Generator Prompt Handler
11
- * Generates SQL queries from natural language descriptions
12
- */
13
- export async function sqlGeneratorPromptHandler({ description, dialect }, _extra) {
14
- try {
15
- // Get current connector to determine dialect if not specified
16
- const connector = ConnectorManager.getCurrentConnector();
17
- // Determine SQL dialect from connector if not explicitly provided
18
- const sqlDialect = dialect ||
19
- (connector.id === 'postgres' ? 'postgres' :
20
- (connector.id === 'sqlite' ? 'sqlite' : 'postgres'));
21
- // Get schema information to help with table/column references
22
- const tables = await connector.getTables();
23
- const tableSchemas = await Promise.all(tables.map(async (table) => {
24
- try {
25
- const columns = await connector.getTableSchema(table);
26
- return {
27
- table,
28
- columns: columns.map(col => ({
29
- name: col.column_name,
30
- type: col.data_type
31
- }))
32
- };
33
- }
34
- catch (error) {
35
- // Skip tables we can't access
36
- return null;
37
- }
38
- }));
39
- // Filter out null entries (tables we couldn't access)
40
- const accessibleSchemas = tableSchemas.filter(schema => schema !== null);
41
- // Generate a schema description for context
42
- const schemaContext = accessibleSchemas.length > 0
43
- ? `Available tables and their columns:\n${accessibleSchemas.map(schema => `- ${schema.table}: ${schema.columns.map(col => `${col.name} (${col.type})`).join(', ')}`).join('\n')}`
44
- : 'No schema information available.';
45
- // Example queries for the given dialect to use as reference
46
- const dialectExamples = {
47
- postgres: [
48
- "SELECT * FROM users WHERE created_at > NOW() - INTERVAL '1 day'",
49
- "SELECT u.name, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.name HAVING COUNT(o.id) > 5",
50
- "SELECT product_name, price FROM products WHERE price > (SELECT AVG(price) FROM products)"
51
- ],
52
- sqlite: [
53
- "SELECT * FROM users WHERE created_at > datetime('now', '-1 day')",
54
- "SELECT u.name, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.name HAVING COUNT(o.id) > 5",
55
- "SELECT product_name, price FROM products WHERE price > (SELECT AVG(price) FROM products)"
56
- ]
57
- };
58
- // Build a prompt that would help generate the SQL
59
- // In a real implementation, this would call an AI model
60
- const prompt = `
61
- Generate a ${sqlDialect} SQL query based on this description: "${description}"
62
-
63
- ${schemaContext}
64
-
65
- The query should:
66
- 1. Be written for ${sqlDialect} dialect
67
- 2. Use only the available tables and columns
68
- 3. Prioritize readability
69
- 4. Include appropriate comments
70
- 5. Be compatible with ${sqlDialect} syntax
71
- `;
72
- // In a real implementation, this would be the result from an AI model call
73
- // For this demo, we'll generate a simple SQL query based on the description
74
- let generatedSQL;
75
- // Very simple pattern matching for demo purposes
76
- // In a real implementation, this would use a language model
77
- if (description.toLowerCase().includes('count')) {
78
- generatedSQL = `-- Count query generated from: "${description}"
79
- SELECT COUNT(*) AS count
80
- FROM ${accessibleSchemas.length > 0 ? accessibleSchemas[0].table : 'table_name'};`;
81
- }
82
- else if (description.toLowerCase().includes('average') || description.toLowerCase().includes('avg')) {
83
- const table = accessibleSchemas.length > 0 ? accessibleSchemas[0].table : 'table_name';
84
- const numericColumn = accessibleSchemas.length > 0
85
- ? accessibleSchemas[0].columns.find(col => ['int', 'numeric', 'decimal', 'float', 'real', 'double'].some(t => col.type.includes(t)))?.name || 'numeric_column'
86
- : 'numeric_column';
87
- generatedSQL = `-- Average query generated from: "${description}"
88
- SELECT AVG(${numericColumn}) AS average
89
- FROM ${table};`;
90
- }
91
- else if (description.toLowerCase().includes('join')) {
92
- generatedSQL = `-- Join query generated from: "${description}"
93
- SELECT t1.*, t2.*
94
- FROM ${accessibleSchemas.length > 0 ? accessibleSchemas[0]?.table : 'table1'} t1
95
- JOIN ${accessibleSchemas.length > 1 ? accessibleSchemas[1]?.table : 'table2'} t2
96
- ON t1.id = t2.${accessibleSchemas.length > 0 ? accessibleSchemas[0]?.table : 'table1'}_id;`;
97
- }
98
- else {
99
- // Default to a simple SELECT
100
- const table = accessibleSchemas.length > 0 ? accessibleSchemas[0].table : 'table_name';
101
- generatedSQL = `-- Query generated from: "${description}"
102
- SELECT *
103
- FROM ${table}
104
- LIMIT 10;`;
105
- }
106
- // Return the generated SQL with explanations
107
- return formatPromptSuccessResponse(generatedSQL,
108
- // Add references to example queries that could help
109
- dialectExamples[sqlDialect]);
110
- }
111
- catch (error) {
112
- return formatPromptErrorResponse(`Failed to generate SQL: ${error.message}`, "SQL_GENERATION_ERROR");
113
- }
114
- }
@@ -1,12 +0,0 @@
1
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { tablesResourceHandler } from './tables.js';
3
- import { schemaResourceHandler } from './schema.js';
4
- /**
5
- * Register all resource handlers with the MCP server
6
- */
7
- export function registerResources(server) {
8
- // Resource for listing all tables
9
- server.resource("tables", "db://tables", tablesResourceHandler);
10
- // Resource for getting table schema
11
- server.resource("schema", new ResourceTemplate("db://schema/{tableName}", { list: undefined }), schemaResourceHandler);
12
- }
@@ -1,36 +0,0 @@
1
- import { ConnectorManager } from '../connectors/manager.js';
2
- import { createResourceSuccessResponse, createResourceErrorResponse } from '../utils/response-formatter.js';
3
- /**
4
- * Schema resource handler
5
- * Returns schema information for a specific table
6
- */
7
- export async function schemaResourceHandler(uri, variables, _extra) {
8
- const connector = ConnectorManager.getCurrentConnector();
9
- // Handle tableName which could be a string or string array from URL template
10
- const tableName = Array.isArray(variables.tableName)
11
- ? variables.tableName[0]
12
- : variables.tableName;
13
- try {
14
- // If table doesn't exist, getTableSchema will throw an error
15
- const columns = await connector.getTableSchema(tableName);
16
- // Create a more structured response
17
- const formattedColumns = columns.map(col => ({
18
- name: col.column_name,
19
- type: col.data_type,
20
- nullable: col.is_nullable === 'YES',
21
- default: col.column_default
22
- }));
23
- // Prepare response data
24
- const responseData = {
25
- table: tableName,
26
- columns: formattedColumns,
27
- count: formattedColumns.length
28
- };
29
- // Use the utility to create a standardized response
30
- return createResourceSuccessResponse(uri.href, responseData);
31
- }
32
- catch (error) {
33
- // Use the utility to create a standardized error response
34
- return createResourceErrorResponse(uri.href, `Table '${tableName}' does not exist or cannot be accessed`, "TABLE_NOT_FOUND");
35
- }
36
- }
@@ -1,17 +0,0 @@
1
- import { ConnectorManager } from '../connectors/manager.js';
2
- import { createResourceSuccessResponse } from '../utils/response-formatter.js';
3
- /**
4
- * Tables resource handler
5
- * Returns a list of all tables in the database
6
- */
7
- export async function tablesResourceHandler(uri, _extra) {
8
- const connector = ConnectorManager.getCurrentConnector();
9
- const tableNames = await connector.getTables();
10
- // Prepare response data
11
- const responseData = {
12
- tables: tableNames,
13
- count: tableNames.length
14
- };
15
- // Use the utility to create a standardized response
16
- return createResourceSuccessResponse(uri.href, responseData);
17
- }