Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/backend-shared/rpc/backend-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { statementRowCount } from '@dotaz/shared/query-result'
import type { ConnectionConfig, ConnectionInfo, ConnectionSecrets } from '@dotaz/shared/types/connection'
import { mergeSecrets } from '@dotaz/shared/types/connection'
import type { DatabaseInfo } from '@dotaz/shared/types/database'
Expand Down Expand Up @@ -282,7 +283,7 @@ export class BackendAdapter implements RpcAdapter {
stmt.sql,
result.error ? 'error' : 'success',
durationMs,
result.affectedRows ?? result.rowCount,
statementRowCount(result),
result.error,
database,
effectiveSessionId,
Expand Down
3 changes: 2 additions & 1 deletion src/backend-shared/services/query-executor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { statementRowCount } from '@dotaz/shared/query-result'
import { parseErrorPosition, splitStatements, stripLiteralsAndComments } from '@dotaz/shared/sql/statements'
import { DatabaseError } from '@dotaz/shared/types/errors'
import type { ExplainNode, ExplainResult, QueryResult } from '@dotaz/shared/types/query'
Expand Down Expand Up @@ -516,7 +517,7 @@ export class QueryExecutor {
private logHistory(connectionId: string, sql: string, results: QueryResult[], database?: string, sessionId?: string): void {
const hasError = results.some((r) => r.error)
const totalDuration = results.reduce((sum, r) => sum + (r.durationMs ?? 0), 0)
const totalRows = results.reduce((sum, r) => sum + (r.affectedRows ?? r.rowCount), 0)
const totalRows = results.reduce((sum, r) => sum + statementRowCount(r), 0)
const errorMessage = results.find((r) => r.error)?.error

// Always add to session log (in-memory)
Expand Down
3 changes: 2 additions & 1 deletion src/frontend-demo/demo-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { ExportWriter } from '@dotaz/backend-shared/services/export-service
import { importFromStream, importPreviewFromStream } from '@dotaz/backend-shared/services/import-service'
import { searchDatabase } from '@dotaz/backend-shared/services/search-service'
import { formatSql } from '@dotaz/backend-shared/services/sql-formatter'
import { statementRowCount } from '@dotaz/shared/query-result'
import { splitStatements } from '@dotaz/shared/sql/statements'
import { type ConnectionConfig, type ConnectionInfo, getDefaultDatabase } from '@dotaz/shared/types/connection'
import type { DatabaseInfo } from '@dotaz/shared/types/database'
Expand Down Expand Up @@ -633,7 +634,7 @@ export class DemoAdapter implements RpcAdapter {
private logHistory(connectionId: string, sql: string, results: QueryResult[]): void {
const hasError = results.some((r) => r.error)
const totalDuration = results.reduce((sum, r) => sum + (r.durationMs ?? 0), 0)
const totalRows = results.reduce((sum, r) => sum + (r.affectedRows ?? r.rowCount), 0)
const totalRows = results.reduce((sum, r) => sum + statementRowCount(r), 0)
const errorMessage = results.find((r) => r.error)?.error

// Add to session log (in-memory)
Expand Down
3 changes: 2 additions & 1 deletion src/frontend-shared/stores/editor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { statementRowCount } from '@dotaz/shared/query-result'
import { generateChangesPreview, generateChangeSql } from '@dotaz/shared/sql/builders'
import { detectDestructiveWithoutWhere, isUnlimitedSelect, splitStatements } from '@dotaz/shared/sql/statements'
import type { ExplainResult, QueryEditability, QueryResult } from '@dotaz/shared/types/query'
Expand Down Expand Up @@ -270,7 +271,7 @@ function setCursorPosition(tabId: string, position: number) {
function recordHistory(connectionId: string, database: string | undefined, sql: string, results: QueryResult[]) {
const hasError = results.some((r) => r.error)
const totalDuration = results.reduce((sum, r) => sum + (r.durationMs ?? 0), 0)
const totalRows = results.reduce((sum, r) => sum + (r.affectedRows ?? r.rowCount ?? 0), 0)
const totalRows = results.reduce((sum, r) => sum + statementRowCount(r), 0)
storage.addHistoryEntry({
connectionId,
database,
Expand Down
1 change: 1 addition & 0 deletions src/shared/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ src/shared/
clipboard-paste.ts ← Clipboard paste parsing
column-types.ts ← Column type utilities
comparison.ts ← Data comparison logic
query-result.ts ← Row count a statement accounts for (history, session log)
```

## Types (`src/shared/types/`)
Expand Down
10 changes: 10 additions & 0 deletions src/shared/query-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { QueryResult } from './types/query'

/**
* Rows one statement accounts for: what it returned when it returned rows, what it changed
* otherwise. `affectedRows` alone is not enough — drivers report 0 for a SELECT. Drivers derive
* `columns` from the first row, so a zero-row read takes the `affectedRows` branch, which is 0.
*/
export function statementRowCount(result: QueryResult): number {
return result.columns.length > 0 ? result.rowCount : (result.affectedRows ?? result.rowCount)
}
14 changes: 14 additions & 0 deletions tests/rpc-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,20 @@ describe('RPC Handlers', () => {
expect(history[0].rowCount).toBe(1)
})

// Drivers report `affectedRows: 0` for a SELECT (MySQL always, PostgreSQL/SQLite since Bun 1.4),
// so history must count what a result set returned, and what a write changed.
test('history counts returned rows for a read and changed rows for a write', async () => {
await handlers['query.execute']({ connectionId, sql: 'CREATE TABLE hist_t (a INTEGER)', queryId: 'h-c' })
await handlers['query.execute']({ connectionId, sql: 'INSERT INTO hist_t VALUES (1), (2), (3)', queryId: 'h-i' })
await handlers['query.execute']({ connectionId, sql: 'SELECT a FROM hist_t WHERE a > 1', queryId: 'h-s' })
await handlers['query.execute']({ connectionId, sql: 'SELECT a FROM hist_t WHERE a > 9', queryId: 'h-e' })

const bySql = new Map(handlers['history.list']({}).map((entry) => [entry.sql, entry.rowCount]))
expect(bySql.get('INSERT INTO hist_t VALUES (1), (2), (3)')).toBe(3)
expect(bySql.get('SELECT a FROM hist_t WHERE a > 1')).toBe(2)
expect(bySql.get('SELECT a FROM hist_t WHERE a > 9')).toBe(0)
})

test('failed queries are logged with error status', async () => {
await handlers['query.execute']({
connectionId,
Expand Down
Loading