heroku 11.6.0 → 11.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/commands/auth/whoami.js +1 -1
- package/dist/commands/pg/cache-hit.d.ts +16 -0
- package/dist/commands/pg/cache-hit.js +40 -0
- package/dist/commands/pg/calls.d.ts +19 -0
- package/dist/commands/pg/calls.js +55 -0
- package/dist/commands/pg/extensions.d.ts +16 -0
- package/dist/commands/pg/extensions.js +39 -0
- package/dist/commands/pg/fdwsql.d.ts +16 -0
- package/dist/commands/pg/fdwsql.js +70 -0
- package/dist/commands/pg/index-size.d.ts +16 -0
- package/dist/commands/pg/index-size.js +40 -0
- package/dist/commands/pg/index-usage.d.ts +16 -0
- package/dist/commands/pg/index-usage.js +41 -0
- package/dist/commands/pg/long-running-queries.d.ts +16 -0
- package/dist/commands/pg/long-running-queries.js +43 -0
- package/dist/commands/pg/mandelbrot.d.ts +15 -0
- package/dist/commands/pg/mandelbrot.js +49 -0
- package/dist/commands/pg/records-rank.d.ts +16 -0
- package/dist/commands/pg/records-rank.js +38 -0
- package/dist/commands/pg/seq-scans.d.ts +16 -0
- package/dist/commands/pg/seq-scans.js +36 -0
- package/dist/commands/pg/stats-reset.d.ts +15 -0
- package/dist/commands/pg/stats-reset.js +34 -0
- package/dist/commands/pg/table-indexes-size.d.ts +16 -0
- package/dist/commands/pg/table-indexes-size.js +39 -0
- package/dist/commands/pg/table-size.d.ts +16 -0
- package/dist/commands/pg/table-size.js +39 -0
- package/dist/commands/pg/total-index-size.d.ts +16 -0
- package/dist/commands/pg/total-index-size.js +37 -0
- package/dist/commands/pg/total-table-size.d.ts +16 -0
- package/dist/commands/pg/total-table-size.js +39 -0
- package/dist/commands/pg/unused-indexes.d.ts +16 -0
- package/dist/commands/pg/unused-indexes.js +41 -0
- package/dist/commands/pg/user-connections.d.ts +16 -0
- package/dist/commands/pg/user-connections.js +38 -0
- package/dist/lib/pg/extras.d.ts +11 -0
- package/dist/lib/pg/extras.js +57 -0
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +1173 -165
- package/package.json +2 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateSeqScansQuery = () => `
|
|
8
|
+
SELECT relname AS name,
|
|
9
|
+
seq_scan as count
|
|
10
|
+
FROM
|
|
11
|
+
pg_stat_user_tables
|
|
12
|
+
ORDER BY seq_scan DESC;
|
|
13
|
+
`.trim();
|
|
14
|
+
export default class PgSeqScans extends Command {
|
|
15
|
+
static args = {
|
|
16
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
17
|
+
};
|
|
18
|
+
static description = 'show the count of sequential scans by table descending by order';
|
|
19
|
+
static examples = [heredoc `
|
|
20
|
+
${color.command('heroku pg:seq-scans --app example-app')}
|
|
21
|
+
`];
|
|
22
|
+
static flags = {
|
|
23
|
+
app: flags.app({ required: true }),
|
|
24
|
+
remote: flags.remote(),
|
|
25
|
+
};
|
|
26
|
+
static hiddenAliases = ['pg:seq_scans'];
|
|
27
|
+
static topic = 'pg';
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(PgSeqScans);
|
|
30
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
31
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
32
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
33
|
+
const output = await psqlService.execQuery(generateSeqScansQuery());
|
|
34
|
+
ux.stdout(output);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class PgStatsReset extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
static hiddenAliases: string[];
|
|
13
|
+
static topic: string;
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { ensureEssentialTierPlan } from '../../lib/pg/extras.js';
|
|
6
|
+
import { nls } from '../../nls.js';
|
|
7
|
+
const heredoc = tsheredoc.default;
|
|
8
|
+
export default class PgStatsReset extends Command {
|
|
9
|
+
static args = {
|
|
10
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
11
|
+
};
|
|
12
|
+
static description = 'calls the Postgres functions pg_stat_reset()';
|
|
13
|
+
static examples = [heredoc `
|
|
14
|
+
${color.command('heroku pg:stats-reset --app example-app')}
|
|
15
|
+
`];
|
|
16
|
+
static flags = {
|
|
17
|
+
app: flags.app({ required: true }),
|
|
18
|
+
remote: flags.remote(),
|
|
19
|
+
};
|
|
20
|
+
static hiddenAliases = ['pg:stats_reset'];
|
|
21
|
+
static topic = 'pg';
|
|
22
|
+
async run() {
|
|
23
|
+
const { args, flags } = await this.parse(PgStatsReset);
|
|
24
|
+
const { app } = flags;
|
|
25
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
26
|
+
const db = await dbResolver.getDatabase(app, args.database);
|
|
27
|
+
await ensureEssentialTierPlan(db);
|
|
28
|
+
const { addon } = await dbResolver.getAttachment(app, args.database);
|
|
29
|
+
const { body } = await this.heroku.put(`/client/v11/databases/${addon.id}/stats_reset`, {
|
|
30
|
+
hostname: utils.pg.host(),
|
|
31
|
+
});
|
|
32
|
+
ux.stdout(body.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export declare const generateTableIndexesSizeQuery: () => string;
|
|
3
|
+
export default class PgTableIndexesSize extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static hiddenAliases: string[];
|
|
14
|
+
static topic: string;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateTableIndexesSizeQuery = () => `
|
|
8
|
+
SELECT c.relname AS table,
|
|
9
|
+
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size
|
|
10
|
+
FROM pg_class c
|
|
11
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
|
12
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
13
|
+
AND n.nspname !~ '^pg_toast'
|
|
14
|
+
AND c.relkind='r'
|
|
15
|
+
ORDER BY pg_indexes_size(c.oid) DESC;
|
|
16
|
+
`.trim();
|
|
17
|
+
export default class PgTableIndexesSize extends Command {
|
|
18
|
+
static args = {
|
|
19
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
20
|
+
};
|
|
21
|
+
static description = 'show the total size of all the indexes on each table, descending by size';
|
|
22
|
+
static examples = [heredoc `
|
|
23
|
+
${color.command('heroku pg:table-indexes-size --app example-app')}
|
|
24
|
+
`];
|
|
25
|
+
static flags = {
|
|
26
|
+
app: flags.app({ required: true }),
|
|
27
|
+
remote: flags.remote(),
|
|
28
|
+
};
|
|
29
|
+
static hiddenAliases = ['pg:table_indexes_size'];
|
|
30
|
+
static topic = 'pg';
|
|
31
|
+
async run() {
|
|
32
|
+
const { args, flags } = await this.parse(PgTableIndexesSize);
|
|
33
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
34
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
35
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
36
|
+
const output = await psqlService.execQuery(generateTableIndexesSizeQuery());
|
|
37
|
+
ux.stdout(output);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export declare const generateTableSizeQuery: () => string;
|
|
3
|
+
export default class PgTableSize extends Command {
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static topic: string;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateTableSizeQuery = () => `
|
|
8
|
+
SELECT c.relname AS name,
|
|
9
|
+
pg_size_pretty(pg_table_size(c.oid)) AS size
|
|
10
|
+
FROM pg_class c
|
|
11
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
|
12
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
13
|
+
AND n.nspname !~ '^pg_toast'
|
|
14
|
+
AND c.relkind='r'
|
|
15
|
+
ORDER BY pg_table_size(c.oid) DESC;
|
|
16
|
+
`.trim();
|
|
17
|
+
export default class PgTableSize extends Command {
|
|
18
|
+
static aliases = ['pg:table_size'];
|
|
19
|
+
static args = {
|
|
20
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
21
|
+
};
|
|
22
|
+
static description = 'show the size of the tables (excluding indexes), descending by size';
|
|
23
|
+
static examples = [heredoc `
|
|
24
|
+
${color.command('heroku pg:table-size --app example-app')}
|
|
25
|
+
`];
|
|
26
|
+
static flags = {
|
|
27
|
+
app: flags.app({ required: true }),
|
|
28
|
+
remote: flags.remote(),
|
|
29
|
+
};
|
|
30
|
+
static topic = 'pg';
|
|
31
|
+
async run() {
|
|
32
|
+
const { args, flags } = await this.parse(PgTableSize);
|
|
33
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
34
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
35
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
36
|
+
const output = await psqlService.execQuery(generateTableSizeQuery());
|
|
37
|
+
ux.stdout(output);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export declare const generateTotalIndexSizeQuery: () => string;
|
|
3
|
+
export default class PgTotalIndexSize extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static hiddenAliases: string[];
|
|
14
|
+
static topic: string;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateTotalIndexSizeQuery = () => `
|
|
8
|
+
SELECT pg_size_pretty(sum(c.relpages::bigint*8192)::bigint) AS size
|
|
9
|
+
FROM pg_class c
|
|
10
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
|
11
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
12
|
+
AND n.nspname !~ '^pg_toast'
|
|
13
|
+
AND c.relkind='i';
|
|
14
|
+
`.trim();
|
|
15
|
+
export default class PgTotalIndexSize extends Command {
|
|
16
|
+
static args = {
|
|
17
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
18
|
+
};
|
|
19
|
+
static description = 'show the total size of all indexes in MB';
|
|
20
|
+
static examples = [heredoc `
|
|
21
|
+
${color.command('heroku pg:total-index-size --app example-app')}
|
|
22
|
+
`];
|
|
23
|
+
static flags = {
|
|
24
|
+
app: flags.app({ required: true }),
|
|
25
|
+
remote: flags.remote(),
|
|
26
|
+
};
|
|
27
|
+
static hiddenAliases = ['pg:total_index_size'];
|
|
28
|
+
static topic = 'pg';
|
|
29
|
+
async run() {
|
|
30
|
+
const { args, flags } = await this.parse(PgTotalIndexSize);
|
|
31
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
32
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
33
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
34
|
+
const output = await psqlService.execQuery(generateTotalIndexSizeQuery());
|
|
35
|
+
ux.stdout(output);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export declare const generateTotalTableSizeQuery: () => string;
|
|
3
|
+
export default class PgTotalTableSize extends Command {
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static topic: string;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateTotalTableSizeQuery = () => `
|
|
8
|
+
SELECT c.relname AS name,
|
|
9
|
+
pg_size_pretty(pg_total_relation_size(c.oid)) AS size
|
|
10
|
+
FROM pg_class c
|
|
11
|
+
LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
|
|
12
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
13
|
+
AND n.nspname !~ '^pg_toast'
|
|
14
|
+
AND c.relkind='r'
|
|
15
|
+
ORDER BY pg_total_relation_size(c.oid) DESC;
|
|
16
|
+
`.trim();
|
|
17
|
+
export default class PgTotalTableSize extends Command {
|
|
18
|
+
static aliases = ['pg:total_table_size'];
|
|
19
|
+
static args = {
|
|
20
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
21
|
+
};
|
|
22
|
+
static description = 'show the size of the tables (including indexes), descending by size';
|
|
23
|
+
static examples = [heredoc `
|
|
24
|
+
${color.command('heroku pg:total-table-size --app example-app')}
|
|
25
|
+
`];
|
|
26
|
+
static flags = {
|
|
27
|
+
app: flags.app({ required: true }),
|
|
28
|
+
remote: flags.remote(),
|
|
29
|
+
};
|
|
30
|
+
static topic = 'pg';
|
|
31
|
+
async run() {
|
|
32
|
+
const { args, flags } = await this.parse(PgTotalTableSize);
|
|
33
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
34
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
35
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
36
|
+
const output = await psqlService.execQuery(generateTotalTableSizeQuery());
|
|
37
|
+
ux.stdout(output);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export declare const generateUnusedIndexesQuery: () => string;
|
|
3
|
+
export default class PgUnusedIndexes extends Command {
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static topic: string;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateUnusedIndexesQuery = () => `
|
|
8
|
+
SELECT
|
|
9
|
+
schemaname || '.' || relname AS table,
|
|
10
|
+
indexrelname AS index,
|
|
11
|
+
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
|
|
12
|
+
idx_scan as index_scans
|
|
13
|
+
FROM pg_stat_user_indexes ui
|
|
14
|
+
JOIN pg_index i ON ui.indexrelid = i.indexrelid
|
|
15
|
+
WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192
|
|
16
|
+
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
|
|
17
|
+
pg_relation_size(i.indexrelid) DESC;
|
|
18
|
+
`.trim();
|
|
19
|
+
export default class PgUnusedIndexes extends Command {
|
|
20
|
+
static aliases = ['pg:unused_indexes'];
|
|
21
|
+
static args = {
|
|
22
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
23
|
+
};
|
|
24
|
+
static description = 'show unused and almost unused indexes';
|
|
25
|
+
static examples = [heredoc `
|
|
26
|
+
${color.command('heroku pg:unused-indexes --app example-app')}
|
|
27
|
+
`];
|
|
28
|
+
static flags = {
|
|
29
|
+
app: flags.app({ required: true }),
|
|
30
|
+
remote: flags.remote(),
|
|
31
|
+
};
|
|
32
|
+
static topic = 'pg';
|
|
33
|
+
async run() {
|
|
34
|
+
const { args, flags } = await this.parse(PgUnusedIndexes);
|
|
35
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
36
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
37
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
38
|
+
const output = await psqlService.execQuery(generateUnusedIndexesQuery());
|
|
39
|
+
ux.stdout(output);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export declare const generateUserConnectionsQuery: () => string;
|
|
3
|
+
export default class PgUserConnections extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
database: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static hiddenAliases: string[];
|
|
14
|
+
static topic: string;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import { nls } from '../../nls.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export const generateUserConnectionsQuery = () => `
|
|
8
|
+
SELECT
|
|
9
|
+
usename AS credential,
|
|
10
|
+
count(*) AS connections
|
|
11
|
+
FROM pg_stat_activity
|
|
12
|
+
WHERE state = 'active'
|
|
13
|
+
GROUP BY usename
|
|
14
|
+
ORDER BY connections DESC;
|
|
15
|
+
`.trim();
|
|
16
|
+
export default class PgUserConnections extends Command {
|
|
17
|
+
static args = {
|
|
18
|
+
database: Args.string({ description: `${nls('pg:database:arg:description')} ${nls('pg:database:arg:description:default:suffix')}`, required: false }),
|
|
19
|
+
};
|
|
20
|
+
static description = 'returns the number of connections per credential';
|
|
21
|
+
static examples = [heredoc `
|
|
22
|
+
${color.command('heroku pg:user-connections --app example-app')}
|
|
23
|
+
`];
|
|
24
|
+
static flags = {
|
|
25
|
+
app: flags.app({ required: true }),
|
|
26
|
+
remote: flags.remote(),
|
|
27
|
+
};
|
|
28
|
+
static hiddenAliases = ['pg:user_connections'];
|
|
29
|
+
static topic = 'pg';
|
|
30
|
+
async run() {
|
|
31
|
+
const { args, flags } = await this.parse(PgUserConnections);
|
|
32
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
33
|
+
const db = await dbResolver.getDatabase(flags.app, args.database);
|
|
34
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
35
|
+
const output = await psqlService.execQuery(generateUserConnectionsQuery());
|
|
36
|
+
ux.stdout(output);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Heroku from '@heroku-cli/schema';
|
|
2
|
+
import { pg } from '@heroku/heroku-cli-util';
|
|
3
|
+
interface Plan {
|
|
4
|
+
plan: Heroku.AddOn['plan'];
|
|
5
|
+
}
|
|
6
|
+
export declare function ensurePGStatStatement(db: pg.ConnectionDetails): Promise<void>;
|
|
7
|
+
export declare function ensureEssentialTierPlan(db: pg.ConnectionDetails): Promise<void>;
|
|
8
|
+
export declare function essentialNumPlan(a: Plan): boolean;
|
|
9
|
+
export declare function newTotalExecTimeField(db: pg.ConnectionDetails): Promise<boolean>;
|
|
10
|
+
export declare function newBlkTimeFields(db: pg.ConnectionDetails): Promise<boolean>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { utils } from '@heroku/heroku-cli-util';
|
|
2
|
+
import { ux } from '@oclif/core/ux';
|
|
3
|
+
export async function ensurePGStatStatement(db) {
|
|
4
|
+
const query = `
|
|
5
|
+
SELECT exists(
|
|
6
|
+
SELECT 1 FROM pg_extension e LEFT JOIN pg_namespace n ON n.oid = e.extnamespace
|
|
7
|
+
WHERE e.extname='pg_stat_statements' AND n.nspname IN ('public', 'heroku_ext')
|
|
8
|
+
) AS available`;
|
|
9
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
10
|
+
const output = await psqlService.execQuery(query);
|
|
11
|
+
if (!output.includes('t')) {
|
|
12
|
+
ux.error(`pg_stat_statements extension need to be installed in the public schema first.
|
|
13
|
+
You can install it by running:
|
|
14
|
+
|
|
15
|
+
CREATE EXTENSION pg_stat_statements;`, { exit: 1 });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function ensureEssentialTierPlan(db) {
|
|
19
|
+
const planName = db.attachment?.addon?.plan?.name;
|
|
20
|
+
if (!planName) {
|
|
21
|
+
ux.error('Unable to determine database plan type', { exit: 1 });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (/(dev|basic|essential-\d+)$/.test(planName)) {
|
|
25
|
+
ux.error('This operation is not supported by Essential-tier databases.', { exit: 1 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function essentialNumPlan(a) {
|
|
29
|
+
if (!a.plan?.name)
|
|
30
|
+
return false;
|
|
31
|
+
const parts = a.plan.name.split(':');
|
|
32
|
+
if (parts.length < 2)
|
|
33
|
+
return false;
|
|
34
|
+
return Boolean(parts[1].startsWith('essential'));
|
|
35
|
+
}
|
|
36
|
+
export async function newTotalExecTimeField(db) {
|
|
37
|
+
const newTotalExecTimeFieldQuery = 'SELECT current_setting(\'server_version_num\')::numeric >= 130000';
|
|
38
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
39
|
+
const newTotalExecTimeFieldRaw = await psqlService.execQuery(newTotalExecTimeFieldQuery, ['-t', '-q']);
|
|
40
|
+
// error checks
|
|
41
|
+
const newTotalExecTimeField = newTotalExecTimeFieldRaw.split('\n')[0].trim();
|
|
42
|
+
if (newTotalExecTimeField !== 't' && newTotalExecTimeField !== 'f') {
|
|
43
|
+
ux.error(`Unable to determine database version, expected "t" or "f", got: "${newTotalExecTimeField}"`, { exit: 1 });
|
|
44
|
+
}
|
|
45
|
+
return newTotalExecTimeField === 't';
|
|
46
|
+
}
|
|
47
|
+
export async function newBlkTimeFields(db) {
|
|
48
|
+
const newBlkTimeFieldsQuery = 'SELECT current_setting(\'server_version_num\')::numeric >= 170000';
|
|
49
|
+
const psqlService = new utils.pg.PsqlService(db);
|
|
50
|
+
const newBlkTimeFieldsRaw = await psqlService.execQuery(newBlkTimeFieldsQuery, ['-t', '-q']);
|
|
51
|
+
// error checks
|
|
52
|
+
const newBlkTimeField = newBlkTimeFieldsRaw.split('\n')[0].trim();
|
|
53
|
+
if (newBlkTimeField !== 't' && newBlkTimeField !== 'f') {
|
|
54
|
+
ux.error(`Unable to determine database version, expected "t" or "f", got: "${newBlkTimeField}"`, { exit: 1 });
|
|
55
|
+
}
|
|
56
|
+
return newBlkTimeField === 't';
|
|
57
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "heroku",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.7.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "heroku",
|
|
9
|
-
"version": "11.
|
|
9
|
+
"version": "11.7.0",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@heroku-cli/command": "12.4.2",
|