googleapis/mcp-toolbox · warning

unable to get rows affected: %w

Error message

unable to get rows affected: %w

What it means

Returned by Source.RunSQL after a successful DML ExecContext when result.RowsAffected() fails to report how many rows the statement modified. The statement itself executed; only the row-count retrieval from the driver failed. This is rare with Oracle drivers but occurs when the driver cannot provide affected-row counts for the executed statement type.

Source

Thrown at internal/sources/oracle/oracle.go:154

func (s *Source) ToConfig() sources.SourceConfig {
	return s.Config
}

func (s *Source) OracleDB() *sql.DB {
	return s.DB
}

func (s *Source) RunSQL(ctx context.Context, statement string, params []any, readOnly bool) (any, error) {
	if !readOnly {
		result, err := s.OracleDB().ExecContext(ctx, statement, params...)
		if err != nil {
			return nil, fmt.Errorf("unable to execute DML statement: %w", err)
		}

		rowsAffected, err := result.RowsAffected()
		if err != nil {
			return nil, fmt.Errorf("unable to get rows affected: %w", err)
		}

		return map[string]any{
			"status":        "success",
			"rows_affected": rowsAffected,
		}, nil
	}
	rows, err := s.OracleDB().QueryContext(ctx, statement, params...)
	if err != nil {
		return nil, fmt.Errorf("unable to execute query: %w", err)
	}
	defer rows.Close()

	// If Columns() errors, it might be a DDL/DML without an OUTPUT clause.
	// We proceed, and results.Err() will catch actual query execution errors.
	// 'out' will remain an empty slice if cols is empty or err is not nil here.
	cols, _ := rows.Columns()

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Check the wrapped driver error to see which statement form caused it.
  2. Use plain DML (INSERT/UPDATE/DELETE/MERGE) through this path; run DDL/PL-SQL outside the tool or accept that row counts are undefined.
  3. Upgrade the go-ora or godror driver version, as row-count support for statement types has improved over releases.
  4. If the statement genuinely succeeded, treat the operation as applied and re-run only the count via an explicit query if needed.

Example fix

// before (DDL through DML path → rows affected undefined)
statement: "TRUNCATE TABLE audit_log"
// after (query the count instead, or execute DDL outside the tool)
statement: "DELETE FROM audit_log WHERE log_date < SYSDATE - 90"
Defensive patterns

Strategy: fallback

Validate before calling

// avoid DDL/PL-SQL on the DML path; detect statement kinds that have no row count
func hasRowsAffectedSemantics(stmt string) bool {
	s := strings.ToUpper(strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(stmt), ";")))
	for _, kw := range []string{"INSERT", "UPDATE", "DELETE", "MERGE"} {
		if strings.HasPrefix(s, kw+" ") {
			return true
		}
	}
	return false
}

Type guard

func isRowsAffectedError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "unable to get rows affected")
}

Try / catch

res, err := src.RunSQL(ctx, stmt, params, false)
if err != nil {
	if isRowsAffectedError(err) {
		log.Printf("statement likely applied but row count unavailable: %v", err)
		return map[string]any{"status": "success", "rows_affected": nil}, nil
	}
	return err
}

Prevention

When it happens

Trigger: readOnly=false path of RunSQL: ExecContext succeeds but result.RowsAffected() returns an error — typically for statements where the driver cannot compute affected rows (e.g. certain PL/SQL blocks, DDL statements like CREATE/DROP executed via ExecContext), or a driver-specific internal failure.

Common situations: Executing DDL (CREATE TABLE, TRUNCATE) or anonymous PL/SQL blocks through the execute-sql tool where affected-row semantics are undefined; unusual statement types that the go-ora/godror driver cannot map to a row count.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/ac27478113996627. Report an issue: GitHub.