pgsql-client 4.13.1 → 4.13.3

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/admin.d.ts CHANGED
@@ -5,6 +5,7 @@ export declare class DbAdmin {
5
5
  protected verbose: boolean;
6
6
  protected roleConfig?: PgTestConnectionOptions;
7
7
  constructor(config: PgConfig, verbose?: boolean, roleConfig?: PgTestConnectionOptions);
8
+ private connectionArgs;
8
9
  private getEnv;
9
10
  private run;
10
11
  private safeDropDb;
package/admin.js CHANGED
@@ -9,6 +9,8 @@ const pg_env_1 = require("pg-env");
9
9
  const roles_1 = require("./roles");
10
10
  const stream_1 = require("./stream");
11
11
  const log = new logger_1.Logger('db-admin');
12
+ const sqlLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
13
+ const quoteIdentifier = (value) => `"${value.replace(/"/g, '""')}"`;
12
14
  class DbAdmin {
13
15
  config;
14
16
  verbose;
@@ -18,6 +20,16 @@ class DbAdmin {
18
20
  this.verbose = verbose;
19
21
  this.roleConfig = roleConfig;
20
22
  }
23
+ connectionArgs() {
24
+ return [
25
+ '-U',
26
+ this.config.user,
27
+ '-h',
28
+ this.config.host,
29
+ '-p',
30
+ String(this.config.port)
31
+ ];
32
+ }
21
33
  getEnv() {
22
34
  return {
23
35
  PGHOST: this.config.host,
@@ -26,9 +38,10 @@ class DbAdmin {
26
38
  PGPASSWORD: this.config.password
27
39
  };
28
40
  }
29
- run(command) {
41
+ run(file, args) {
42
+ const command = [file, ...args].join(' ');
30
43
  try {
31
- (0, child_process_1.execSync)(command, {
44
+ (0, child_process_1.execFileSync)(file, args, {
32
45
  stdio: this.verbose ? 'inherit' : 'pipe',
33
46
  env: {
34
47
  ...process.env,
@@ -47,7 +60,7 @@ class DbAdmin {
47
60
  }
48
61
  safeDropDb(name) {
49
62
  try {
50
- this.run(`dropdb "${name}"`);
63
+ this.run('dropdb', [name]);
51
64
  }
52
65
  catch (err) {
53
66
  if (err.message.includes('does not exist')) {
@@ -56,8 +69,11 @@ class DbAdmin {
56
69
  if (err.message.includes('is being accessed by other users')) {
57
70
  log.warn(`Database ${name} has active sessions, terminating backends and retrying drop...`);
58
71
  try {
59
- this.run(`psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${name}' AND pid <> pg_backend_pid();"`);
60
- this.run(`dropdb "${name}"`);
72
+ this.run('psql', [
73
+ '-c',
74
+ `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ${sqlLiteral(name)} AND pid <> pg_backend_pid();`
75
+ ]);
76
+ this.run('dropdb', [name]);
61
77
  }
62
78
  catch (retryErr) {
63
79
  log.warn(`Could not drop database ${name} after terminating backends: ${retryErr.message}`);
@@ -71,22 +87,30 @@ class DbAdmin {
71
87
  this.safeDropDb(dbName ?? this.config.database);
72
88
  }
73
89
  dropTemplate(dbName) {
74
- this.run(`psql -c "UPDATE pg_database SET datistemplate='false' WHERE datname='${dbName}'"`);
90
+ this.run('psql', [
91
+ '-c',
92
+ `UPDATE pg_database SET datistemplate='false' WHERE datname=${sqlLiteral(dbName)}`
93
+ ]);
75
94
  this.drop(dbName);
76
95
  }
77
96
  create(dbName) {
78
97
  const db = dbName ?? this.config.database;
79
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} "${db}"`);
98
+ this.run('createdb', [...this.connectionArgs(), db]);
80
99
  }
81
100
  createFromTemplate(template, dbName) {
82
101
  const db = dbName ?? this.config.database;
83
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} -e "${db}" -T "${template}"`);
102
+ this.run('createdb', [...this.connectionArgs(), '-e', '-T', template, db]);
84
103
  }
85
104
  installExtensions(extensions, dbName) {
86
105
  const db = dbName ?? this.config.database;
87
106
  const extList = typeof extensions === 'string' ? extensions.split(',') : extensions;
88
107
  for (const extension of extList) {
89
- this.run(`psql --dbname "${db}" -c 'CREATE EXTENSION IF NOT EXISTS "${extension}" CASCADE;'`);
108
+ this.run('psql', [
109
+ '--dbname',
110
+ db,
111
+ '-c',
112
+ `CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(extension)} CASCADE;`
113
+ ]);
90
114
  }
91
115
  }
92
116
  connectionString(dbName) {
@@ -95,12 +119,18 @@ class DbAdmin {
95
119
  return `postgres://${user}:${password}@${host}:${port}/${db}`;
96
120
  }
97
121
  createTemplateFromBase(base, template) {
98
- this.run(`createdb -T "${base}" "${template}"`);
99
- this.run(`psql -c "UPDATE pg_database SET datistemplate = true WHERE datname = '${template}';"`);
122
+ this.run('createdb', ['-T', base, template]);
123
+ this.run('psql', [
124
+ '-c',
125
+ `UPDATE pg_database SET datistemplate = true WHERE datname = ${sqlLiteral(template)};`
126
+ ]);
100
127
  }
101
128
  cleanupTemplate(template) {
102
129
  try {
103
- this.run(`psql -c "UPDATE pg_database SET datistemplate = false WHERE datname = '${template}'"`);
130
+ this.run('psql', [
131
+ '-c',
132
+ `UPDATE pg_database SET datistemplate = false WHERE datname = ${sqlLiteral(template)}`
133
+ ]);
104
134
  }
105
135
  catch {
106
136
  log.warn(`Skipping failed UPDATE of datistemplate for ${template}`);
@@ -130,7 +160,7 @@ class DbAdmin {
130
160
  if (!(0, fs_1.existsSync)(file)) {
131
161
  throw new Error(`Missing SQL file: ${file}`);
132
162
  }
133
- this.run(`psql -f ${file} ${dbName}`);
163
+ this.run('psql', ['-f', file, dbName]);
134
164
  }
135
165
  async streamSql(sql, dbName) {
136
166
  await (0, stream_1.streamSql)({
package/esm/admin.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { generateCreateUserWithGrantsSQL, generateGrantRoleSQL } from '@pgpmjs/core';
2
2
  import { Logger } from '@pgpmjs/logger';
3
- import { execSync } from 'child_process';
3
+ import { execFileSync } from 'child_process';
4
4
  import { existsSync } from 'fs';
5
5
  import { getPgEnvOptions } from 'pg-env';
6
6
  import { getRoleName } from './roles';
7
7
  import { streamSql as stream } from './stream';
8
8
  const log = new Logger('db-admin');
9
+ const sqlLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
10
+ const quoteIdentifier = (value) => `"${value.replace(/"/g, '""')}"`;
9
11
  export class DbAdmin {
10
12
  config;
11
13
  verbose;
@@ -15,6 +17,16 @@ export class DbAdmin {
15
17
  this.verbose = verbose;
16
18
  this.roleConfig = roleConfig;
17
19
  }
20
+ connectionArgs() {
21
+ return [
22
+ '-U',
23
+ this.config.user,
24
+ '-h',
25
+ this.config.host,
26
+ '-p',
27
+ String(this.config.port)
28
+ ];
29
+ }
18
30
  getEnv() {
19
31
  return {
20
32
  PGHOST: this.config.host,
@@ -23,9 +35,10 @@ export class DbAdmin {
23
35
  PGPASSWORD: this.config.password
24
36
  };
25
37
  }
26
- run(command) {
38
+ run(file, args) {
39
+ const command = [file, ...args].join(' ');
27
40
  try {
28
- execSync(command, {
41
+ execFileSync(file, args, {
29
42
  stdio: this.verbose ? 'inherit' : 'pipe',
30
43
  env: {
31
44
  ...process.env,
@@ -44,7 +57,7 @@ export class DbAdmin {
44
57
  }
45
58
  safeDropDb(name) {
46
59
  try {
47
- this.run(`dropdb "${name}"`);
60
+ this.run('dropdb', [name]);
48
61
  }
49
62
  catch (err) {
50
63
  if (err.message.includes('does not exist')) {
@@ -53,8 +66,11 @@ export class DbAdmin {
53
66
  if (err.message.includes('is being accessed by other users')) {
54
67
  log.warn(`Database ${name} has active sessions, terminating backends and retrying drop...`);
55
68
  try {
56
- this.run(`psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${name}' AND pid <> pg_backend_pid();"`);
57
- this.run(`dropdb "${name}"`);
69
+ this.run('psql', [
70
+ '-c',
71
+ `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ${sqlLiteral(name)} AND pid <> pg_backend_pid();`
72
+ ]);
73
+ this.run('dropdb', [name]);
58
74
  }
59
75
  catch (retryErr) {
60
76
  log.warn(`Could not drop database ${name} after terminating backends: ${retryErr.message}`);
@@ -68,22 +84,30 @@ export class DbAdmin {
68
84
  this.safeDropDb(dbName ?? this.config.database);
69
85
  }
70
86
  dropTemplate(dbName) {
71
- this.run(`psql -c "UPDATE pg_database SET datistemplate='false' WHERE datname='${dbName}'"`);
87
+ this.run('psql', [
88
+ '-c',
89
+ `UPDATE pg_database SET datistemplate='false' WHERE datname=${sqlLiteral(dbName)}`
90
+ ]);
72
91
  this.drop(dbName);
73
92
  }
74
93
  create(dbName) {
75
94
  const db = dbName ?? this.config.database;
76
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} "${db}"`);
95
+ this.run('createdb', [...this.connectionArgs(), db]);
77
96
  }
78
97
  createFromTemplate(template, dbName) {
79
98
  const db = dbName ?? this.config.database;
80
- this.run(`createdb -U ${this.config.user} -h ${this.config.host} -p ${this.config.port} -e "${db}" -T "${template}"`);
99
+ this.run('createdb', [...this.connectionArgs(), '-e', '-T', template, db]);
81
100
  }
82
101
  installExtensions(extensions, dbName) {
83
102
  const db = dbName ?? this.config.database;
84
103
  const extList = typeof extensions === 'string' ? extensions.split(',') : extensions;
85
104
  for (const extension of extList) {
86
- this.run(`psql --dbname "${db}" -c 'CREATE EXTENSION IF NOT EXISTS "${extension}" CASCADE;'`);
105
+ this.run('psql', [
106
+ '--dbname',
107
+ db,
108
+ '-c',
109
+ `CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(extension)} CASCADE;`
110
+ ]);
87
111
  }
88
112
  }
89
113
  connectionString(dbName) {
@@ -92,12 +116,18 @@ export class DbAdmin {
92
116
  return `postgres://${user}:${password}@${host}:${port}/${db}`;
93
117
  }
94
118
  createTemplateFromBase(base, template) {
95
- this.run(`createdb -T "${base}" "${template}"`);
96
- this.run(`psql -c "UPDATE pg_database SET datistemplate = true WHERE datname = '${template}';"`);
119
+ this.run('createdb', ['-T', base, template]);
120
+ this.run('psql', [
121
+ '-c',
122
+ `UPDATE pg_database SET datistemplate = true WHERE datname = ${sqlLiteral(template)};`
123
+ ]);
97
124
  }
98
125
  cleanupTemplate(template) {
99
126
  try {
100
- this.run(`psql -c "UPDATE pg_database SET datistemplate = false WHERE datname = '${template}'"`);
127
+ this.run('psql', [
128
+ '-c',
129
+ `UPDATE pg_database SET datistemplate = false WHERE datname = ${sqlLiteral(template)}`
130
+ ]);
101
131
  }
102
132
  catch {
103
133
  log.warn(`Skipping failed UPDATE of datistemplate for ${template}`);
@@ -127,7 +157,7 @@ export class DbAdmin {
127
157
  if (!existsSync(file)) {
128
158
  throw new Error(`Missing SQL file: ${file}`);
129
159
  }
130
- this.run(`psql -f ${file} ${dbName}`);
160
+ this.run('psql', ['-f', file, dbName]);
131
161
  }
132
162
  async streamSql(sql, dbName) {
133
163
  await stream({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-client",
3
- "version": "4.13.1",
3
+ "version": "4.13.3",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PostgreSQL client utilities with query helpers, RLS context management, and database administration",
6
6
  "main": "index.js",
@@ -42,14 +42,14 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/pg": "^8.20.4",
45
- "makage": "^0.3.0"
45
+ "makage": "^0.8.0"
46
46
  },
47
47
  "dependencies": {
48
- "@pgpmjs/core": "^7.27.1",
49
- "@pgpmjs/logger": "^2.25.0",
50
- "@pgpmjs/types": "^2.52.1",
48
+ "@pgpmjs/core": "^7.27.3",
49
+ "@pgpmjs/logger": "^2.25.1",
50
+ "@pgpmjs/types": "^2.53.0",
51
51
  "pg": "^8.21.0",
52
- "pg-env": "^1.31.0"
52
+ "pg-env": "^1.31.1"
53
53
  },
54
- "gitHead": "77d58bb93796be91d4e00156bb6847392584d3bc"
54
+ "gitHead": "5e0bfead2856d1a062b495d343a43e543343f9b8"
55
55
  }