import { AzureService, ServiceResult } from './base-service.js';
import { CacheKeys } from '../lib/cache.js';
import { executeAzCommand } from '../lib/cli-executor.js';
interface PostgresServer {
name: string;
resourceGroup: string;
location: string;
version: string;
state: string;
fullyQualifiedDomainName: string;
}
interface PostgresDatabase {
name: string;
charset: string;
collation: string;
}
interface PostgresTable {
schemaName: string;
tableName: string;
}
export class PostgresService extends AzureService {
readonly serviceName = 'PostgreSQL';
readonly cliPrefix = 'postgres flexible-server';
async list(resourceGroup?: string): Promise<ServiceResult<PostgresServer[]>> {
const cacheKey = CacheKeys.service('postgres', 'servers', resourceGroup ?? '');
return this.cachedExecute(cacheKey, async () => {
const opts: Record<string, string> = {};
if (resourceGroup) opts['resource-group'] = resourceGroup;
const result = await this.execute('list', opts);
return this.toResult<PostgresServer[]>(result);
});
}
async getServer(serverName: string, resourceGroup: string): Promise<ServiceResult> {
const result = await this.execute('show', {
'name': serverName,
'resource-group': resourceGroup
});
return this.toResult(result);
}
async getServerParameter(serverName: string, resourceGroup: string, paramName: string): Promise<ServiceResult> {
const result = await this.execute('parameter show', {
'server-name': serverName,
'resource-group': resourceGroup,
'name': paramName
});
return this.toResult(result);
}
async listParameters(serverName: string, resourceGroup: string): Promise<ServiceResult> {
const cacheKey = CacheKeys.service('postgres', 'params', serverName);
return this.cachedExecute(cacheKey, async () => {
const result = await this.execute('parameter list', {
'server-name': serverName,
'resource-group': resourceGroup
});
return this.toResult(result);
});
}
async listDatabases(serverName: string, resourceGroup: string): Promise<ServiceResult<PostgresDatabase[]>> {
const cacheKey = CacheKeys.service('postgres', 'databases', serverName);
return this.cachedExecute(cacheKey, async () => {
const result = await this.execute('db list', {
'server-name': serverName,
'resource-group': resourceGroup
});
return this.toResult<PostgresDatabase[]>(result);
});
}
async executeQuery(serverName: string, resourceGroup: string, databaseName: string, query: string): Promise<ServiceResult> {
const escapedQuery = query.replace(/"/g, '\\"');
const cmd = `az postgres flexible-server execute --name "${serverName}" --resource-group "${resourceGroup}" --database-name "${databaseName}" --querytext "${escapedQuery}"`;
const result = await executeAzCommand(cmd, { enableRetry: true });
return this.toResult(result, cmd);
}
async listTables(serverName: string, resourceGroup: string, databaseName: string): Promise<ServiceResult> {
const query = "SELECT schemaname as schema_name, tablename as table_name FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema')";
return this.executeQuery(serverName, resourceGroup, databaseName, query);
}
async getTableSchema(serverName: string, resourceGroup: string, databaseName: string, tableName: string): Promise<ServiceResult> {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tableName)) {
return { success: false, error: 'Invalid table name. Must start with letter/underscore and contain only alphanumeric/underscore.' };
}
const query = `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = '${tableName}'`;
return this.executeQuery(serverName, resourceGroup, databaseName, query);
}
}