t8y2/dbx · error

table read requires a SELECT query

Error message

table read requires a SELECT query

What it means

The Oracle driver's table-read path only executes SELECT statements, since it pages through a live server-side cursor (runPagedOracleSelect). startTableRead validates the trimmed SQL with isQuerySQL and refuses anything that is not a query. This protects the paged-read machinery, which cannot handle DML/DDL that returns no row set.

Source

Thrown at agents/drivers/oracle-go/main.go:3874

}

func (s *server) storeQuerySession(session *querySession) string {
	s.nextSessionID++
	sessionID := fmt.Sprintf("oracle-go-%d", s.nextSessionID)
	s.sessions[sessionID] = session
	return sessionID
}

func (s *server) startTableRead(opts queryOptions, pageSize int) (queryPageResult, error) {
	start := time.Now()
	if strings.TrimSpace(opts.Schema) != "" && !s.hasManualTransaction() {
		if err := s.setSchema(opts.Schema); err != nil {
			return queryPageResult{}, err
		}
	}
	sqlText := trimStatementSQL(opts.SQL)
	if !isQuerySQL(sqlText) {
		return queryPageResult{}, errors.New("table read requires a SELECT query")
	}
	result, session, err := s.runPagedOracleSelect(sqlText, opts, pageSize, start)
	if err != nil {
		return queryPageResult{}, err
	}
	if session != nil {
		sessionID := s.storeTableReadSession(session)
		result.SessionID = &sessionID
	}
	return result, nil
}

func (s *server) fetchTableReadPage(sessionID string, pageSize int) (queryPageResult, error) {
	session := s.tableReadSessions[sessionID]
	if session == nil {
		return queryPageResult{Columns: []string{}, ColumnTypes: []string{}, Rows: [][]any{}, SessionID: nil, HasMore: false}, nil
	}
	result, err := readQuerySessionPage(session, pageSize)

View on GitHub (pinned to c0390bff16)

Solutions

  1. Rewrite the statement as a SELECT (the table-read API is read-only paging over a cursor).
  2. Run DML/DDL through the driver's generic execute/non-query API instead of the table-read path.
  3. Ensure the SQL literally starts with a SELECT clause recognized by isQuerySQL and contains no leading comments/whitespace tricks that hide it.

Example fix

// before
res, err := client.TableRead(ctx, QueryOptions{SQL: "DELETE FROM orders WHERE id = 7"})
// after
res, err := client.Execute(ctx, "DELETE FROM orders WHERE id = 7") // non-query API
// or for reads:
res, err := client.TableRead(ctx, QueryOptions{SQL: "SELECT * FROM orders WHERE id = 7"})
Defensive patterns

Strategy: validation

Validate before calling

sqlText := strings.TrimSpace(opts.SQL)
if !strings.HasPrefix(strings.ToUpper(sqlText), "SELECT") {
    return fmt.Errorf("table read needs SELECT, got: %.40s", sqlText)
}

Type guard

func isSelectQuery(sql string) bool {
    s := strings.TrimSpace(strings.TrimLeft(strings.TrimSpace(sql), "(--/* \t\n"))
    return strings.HasPrefix(strings.ToUpper(s), "SELECT")
}

Prevention

When it happens

Trigger: Calling the table-read/paged-query API with opts.SQL set to an INSERT, UPDATE, DELETE, MERGE, DDL statement (CREATE/ALTER/DROP), or an empty/whitespace string, or a SELECT-less statement that the isQuerySQL heuristic does not recognize as a query.

Common situations: Developers reusing a generic 'run SQL' wrapper and pointing it at the table-read endpoint; passing a script with trailing comments that defeats the SELECT-prefix heuristic; trying to mutate data through a read-oriented paging API.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/72ebc1053a90f2f4. Report an issue: GitHub.