Skip to content

Commit 5414e36

Browse files
dasofieisbosio
andauthored
fix: call _heroku.pg_stat_statements_reset() on Essential and Advanced plans (#3751)
* fix: call _heroku.pg_stat_statements_reset on Essential and Advanced plans The real pg_stat_statements_reset is no longer granted to tenants on Aurora (Essential/Advanced) — they now have a SECURITY DEFINER wrapper in the _heroku schema scoped to the current database only. Standard and below continue to call the real function directly. * fix: use getAttachment to detect plan for pg_stat_statements_reset getDatabase returns ConnectionDetails without plan info. Use getAttachment to get the addon with plan and correctly detect Essential/Advanced plans. * Removing extra add-on resolver request --------- Co-authored-by: Santiago Bosio <sbosio@salesforce.com>
1 parent 5618ee2 commit 5414e36

2 files changed

Lines changed: 85 additions & 13 deletions

File tree

src/commands/pg/outliers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ export default class Outliers extends Command {
8989
await this.ensurePGStatStatement()
9090

9191
if (reset) {
92-
await this.psqlService.execQuery('SELECT pg_stat_statements_reset();')
92+
const resetFn = utils.pg.isEssentialDatabase(db.attachment!.addon) || utils.pg.isAdvancedDatabase(db.attachment!.addon)
93+
? '_heroku.pg_stat_statements_reset()'
94+
: 'pg_stat_statements_reset()'
95+
await this.psqlService.execQuery(`SELECT ${resetFn};`)
9396
return
9497
}
9598

test/unit/commands/pg/outliers.unit.test.ts

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,66 @@ import Cmd from '../../../../src/commands/pg/outliers.js'
77

88
describe('pg:outliers', function () {
99
let sandbox: sinon.SinonSandbox
10-
let getDatabaseStub: sinon.SinonStub
1110
let execQueryStub: sinon.SinonStub
1211
const expectedOutputText = 'slow things'
1312

14-
const mockDb: pg.ConnectionDetails = {
13+
const mockEssentialDb = {
14+
attachment: {
15+
addon: {
16+
id: 'addon-id',
17+
name: 'postgres-addon-name',
18+
plan: {
19+
id: 'essential-0-plan-id',
20+
name: 'heroku-postgresql:essential-0',
21+
},
22+
},
23+
app: {
24+
id: 'myapp-id',
25+
name: 'myapp',
26+
},
27+
config_vars: ['DATABASE_URL'],
28+
id: 'database-attachment-id',
29+
name: 'DATABASE',
30+
},
1531
database: 'testdb',
1632
host: 'localhost',
1733
password: 'testpass',
1834
pathname: '/testdb',
1935
port: '5432',
2036
url: 'postgres://localhost:5432/testdb',
2137
user: 'testuser',
22-
}
38+
} as unknown as pg.ConnectionDetails
39+
40+
const mockStandardDb = {
41+
...mockEssentialDb,
42+
attachment: {
43+
...mockEssentialDb.attachment,
44+
addon: {
45+
...mockEssentialDb.attachment!.addon,
46+
plan: {
47+
id: 'standard-0-plan-id',
48+
name: 'heroku-postgresql:standard-0',
49+
},
50+
},
51+
},
52+
} as unknown as pg.ConnectionDetails
53+
54+
const mockAdvancedDb = {
55+
...mockEssentialDb,
56+
attachment: {
57+
...mockEssentialDb.attachment,
58+
addon: {
59+
...mockEssentialDb.attachment!.addon,
60+
plan: {
61+
id: 'advanced-plan-id',
62+
name: 'heroku-postgresql:advanced',
63+
},
64+
},
65+
},
66+
} as unknown as pg.ConnectionDetails
2367

2468
beforeEach(function () {
2569
sandbox = sinon.createSandbox()
26-
getDatabaseStub = sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockDb)
2770
execQueryStub = sandbox.stub(utils.pg.PsqlService.prototype, 'execQuery')
2871
})
2972

@@ -39,25 +82,47 @@ describe('pg:outliers', function () {
3982
execQueryStub.onCall(2).resolves(expectedOutputText) // main query
4083
}
4184

42-
it('resets query stats', async function () {
43-
// For reset: 1) fetchVersion, 2) ensurePGStatStatement, 3) reset
85+
it('resets query stats on standard plan using real function', async function () {
86+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockStandardDb)
4487
execQueryStub.onCall(0).resolves('server_version\n---------\n13.7')
4588
execQueryStub.onCall(1).resolves('t')
4689
execQueryStub.onCall(2).resolves('')
4790

48-
await runCommand(Cmd, [
49-
'--app',
50-
'myapp',
51-
'--reset',
52-
])
91+
await runCommand(Cmd, ['--app', 'myapp', '--reset'])
5392

54-
expect(getDatabaseStub.calledOnce).to.be.true
5593
expect(execQueryStub.calledThrice).to.be.true
5694
const resetQuery = execQueryStub.getCall(2).args[0]
5795
expect(resetQuery.trim()).to.eq('SELECT pg_stat_statements_reset();')
5896
})
5997

98+
it('resets query stats on essential plan using _heroku wrapper', async function () {
99+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockEssentialDb)
100+
execQueryStub.onCall(0).resolves('server_version\n---------\n17.7')
101+
execQueryStub.onCall(1).resolves('t')
102+
execQueryStub.onCall(2).resolves('')
103+
104+
await runCommand(Cmd, ['--app', 'myapp', '--reset'])
105+
106+
expect(execQueryStub.calledThrice).to.be.true
107+
const resetQuery = execQueryStub.getCall(2).args[0]
108+
expect(resetQuery.trim()).to.eq('SELECT _heroku.pg_stat_statements_reset();')
109+
})
110+
111+
it('resets query stats on advanced plan using _heroku wrapper', async function () {
112+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockAdvancedDb)
113+
execQueryStub.onCall(0).resolves('server_version\n---------\n17.7')
114+
execQueryStub.onCall(1).resolves('t')
115+
execQueryStub.onCall(2).resolves('')
116+
117+
await runCommand(Cmd, ['--app', 'myapp', '--reset'])
118+
119+
expect(execQueryStub.calledThrice).to.be.true
120+
const resetQuery = execQueryStub.getCall(2).args[0]
121+
expect(resetQuery.trim()).to.eq('SELECT _heroku.pg_stat_statements_reset();')
122+
})
123+
60124
it('returns query outliers for version 11', async function () {
125+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockEssentialDb)
61126
setupVersionStub('11.16')
62127

63128
const {stdout} = await runCommand(Cmd, [
@@ -74,6 +139,7 @@ describe('pg:outliers', function () {
74139
})
75140

76141
it('uses an updated query for version 13+', async function () {
142+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockEssentialDb)
77143
setupVersionStub('13.7')
78144

79145
const {stdout} = await runCommand(Cmd, [
@@ -89,6 +155,7 @@ describe('pg:outliers', function () {
89155
})
90156

91157
it('uses updated block time fields for version 17+', async function () {
158+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockEssentialDb)
92159
setupVersionStub('17.0')
93160

94161
await runCommand(Cmd, [
@@ -103,6 +170,7 @@ describe('pg:outliers', function () {
103170
})
104171

105172
it('respects the --num flag', async function () {
173+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockEssentialDb)
106174
setupVersionStub('13.7')
107175

108176
await runCommand(Cmd, [
@@ -117,6 +185,7 @@ describe('pg:outliers', function () {
117185
})
118186

119187
it('truncates queries with --truncate flag', async function () {
188+
sandbox.stub(utils.pg.DatabaseResolver.prototype, 'getDatabase').resolves(mockEssentialDb)
120189
setupVersionStub('13.7')
121190

122191
await runCommand(Cmd, [

0 commit comments

Comments
 (0)