OtterMind/Chat2DB · error · BusinessException

database.delete.systemSchemaForbidden

database.delete.systemSchemaForbidden

Error message

database.delete.systemSchemaForbidden

What it means

Thrown by rejectSystemSchema when the active dbType is PostgreSQL and the schema name matches a protected system schema (pg_catalog, information_schema, public) or starts with pg_toast or pg_temp (case-insensitive). These schemas are internal to PostgreSQL and dropping them corrupts the database. Fires in both prepare and execute paths.

Source

Thrown at chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/db/DbDatabaseObjectDeleteServiceImpl.java:244

    private String buildDropDatabase(String dbType, String databaseName) {
        return Chat2DBContext.getDbMetaData(dbType).getSqlBuilder().ddl().database().buildDropDatabase(databaseName);
    }

    private void assertSchemaDeletionSupported(String dbType) {
        DBConfig dbConfig = Chat2DBContext.getDBConfig(dbType);
        if (dbConfig == null || !dbConfig.isSupportSchema()
                || !DatabaseTypeEnum.POSTGRESQL.name().equalsIgnoreCase(dbType)) {
            throw new BusinessException("database.delete.notSupportSchema");
        }
    }

    private void rejectSystemSchema(String dbType, String schemaName) {
        if (DatabaseTypeEnum.POSTGRESQL.name().equalsIgnoreCase(dbType)) {
            String lowerSchemaName = StringUtils.lowerCase(schemaName);
            if (POSTGRESQL_SYSTEM_SCHEMAS.contains(lowerSchemaName)
                    || StringUtils.startsWith(lowerSchemaName, "pg_toast")
                    || StringUtils.startsWith(lowerSchemaName, "pg_temp")) {
                throw new BusinessException("database.delete.systemSchemaForbidden");
            }
        }
    }

    private void assertSchemaExists(Connection connection, String databaseName, String schemaName) {
        List<Schema> schemas = Chat2DBContext.getDbMetaData().schemas(connection, databaseName);
        boolean exists = schemas != null && schemas.stream()
                .anyMatch(schema -> StringUtils.equals(schema.getName(), schemaName));
        if (!exists) {
            throw new BusinessException("database.delete.schemaNotExists");
        }
    }

    private String schemaConfirmName(String schemaName) {
        return schemaName;
    }

    private String buildDropSchema(String dbType, String schemaName) {

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Never target pg_catalog, information_schema, public, pg_toast*, or pg_temp* for deletion.
  2. Filter these names from the schema list before presenting delete as an option.
  3. Create and delete only user-defined schemas.

Example fix

// before
prepareSchemaDelete(req); // schemaName='public'

// after
Set<String> blocked = Set.of("pg_catalog","information_schema","public");
String lower = StringUtils.lowerCase(req.getSchemaName());
if (blocked.contains(lower) || lower.startsWith("pg_toast") || lower.startsWith("pg_temp")) {
    return; // skip system schemas
}
prepareSchemaDelete(req);
Defensive patterns

Strategy: validation

Validate before calling

Set<String> pgSchemas = Set.of("pg_catalog","information_schema","public");
String lower = StringUtils.lowerCase(schemaName);
if ("POSTGRESQL".equalsIgnoreCase(dbType)
        && (pgSchemas.contains(lower) || lower.startsWith("pg_toast") || lower.startsWith("pg_temp"))) {
    // skip / disable delete
}

Prevention

When it happens

Trigger: Calling prepareSchemaDelete or executeSchemaDelete with schemaName equal to (case-insensitive) 'pg_catalog', 'information_schema', or 'public', or starting with 'pg_toast'/'pg_temp' on a PostgreSQL connection.

Common situations: User selects 'public' in the schema list and attempts delete; temp schema (pg_temp_N) appears in listings; pg_toast schemas leaked into UI.

Understand the failure class

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/1c5d74141837f4d4. Report an issue: GitHub.