@prairielearn/postgres-tools 1.0.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/dist/diff.js ADDED
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.diffDirectories = exports.diffDirectoryAndDatabase = exports.diffDatabaseAndDirectory = exports.diffDatabases = void 0;
7
+ // @ts-check
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const lodash_1 = __importDefault(require("lodash"));
12
+ const diff_1 = require("diff");
13
+ const describe_1 = require("./describe");
14
+ async function diff(db1, db2, options) {
15
+ function formatText(text, formatter) {
16
+ if (options.coloredOutput && formatter) {
17
+ return formatter(text);
18
+ }
19
+ return text;
20
+ }
21
+ const db2Name = db2.type === 'database' ? db2.name : db2.path;
22
+ const db2NameBold = formatText(db2Name, chalk_1.default.bold);
23
+ let result = '';
24
+ const description1 = await loadDescription(db1);
25
+ const description2 = await loadDescription(db2);
26
+ // Determine if both databases have the same tables
27
+ const tablesMissingFrom1 = lodash_1.default.difference(lodash_1.default.keys(description2.tables), lodash_1.default.keys(description1.tables));
28
+ const tablesMissingFrom2 = lodash_1.default.difference(lodash_1.default.keys(description1.tables), lodash_1.default.keys(description2.tables));
29
+ if (tablesMissingFrom1.length > 0) {
30
+ result += formatText(`Tables added to ${db2NameBold} (${db2.type})\n`, chalk_1.default.underline);
31
+ result += formatText(tablesMissingFrom1.map((table) => `+ ${table}`).join('\n') + '\n\n', chalk_1.default.green);
32
+ }
33
+ if (tablesMissingFrom2.length > 0) {
34
+ result += formatText(`Tables missing from ${db2NameBold} (${db2.type})\n`, chalk_1.default.underline);
35
+ result += formatText(tablesMissingFrom2.map((table) => `- ${table}`).join('\n') + '\n\n', chalk_1.default.red);
36
+ }
37
+ // Determine if both databases have the same enums
38
+ const enumsMissingFrom1 = lodash_1.default.difference(lodash_1.default.keys(description2.enums), lodash_1.default.keys(description1.enums));
39
+ const enumsMissingFrom2 = lodash_1.default.difference(lodash_1.default.keys(description1.enums), lodash_1.default.keys(description2.enums));
40
+ if (enumsMissingFrom1.length > 0) {
41
+ result += formatText(`Enums added to ${db2NameBold} (${db1.type})\n`, chalk_1.default.underline);
42
+ result += formatText(enumsMissingFrom1.map((enumName) => `+ ${enumName}`).join('\n') + '\n\n', chalk_1.default.green);
43
+ }
44
+ if (enumsMissingFrom2.length > 0) {
45
+ result += formatText(`Enums missing from ${db2NameBold} (${db2.type})\n`, chalk_1.default.underline);
46
+ result += formatText(enumsMissingFrom2.map((enumName) => `- ${enumName}`).join('\n') + '\n\n', chalk_1.default.red);
47
+ }
48
+ // Determine if the columns of any table differ
49
+ const intersection = lodash_1.default.intersection(lodash_1.default.keys(description1.tables), lodash_1.default.keys(description2.tables));
50
+ lodash_1.default.forEach(intersection, (table) => {
51
+ // We normalize each blob to end with a newline to make diffs print cleaner
52
+ const diff = (0, diff_1.diffLines)(description1.tables[table].trim() + '\n', description2.tables[table].trim() + '\n');
53
+ if (diff.length === 1)
54
+ return;
55
+ const boldTable = formatText(table, chalk_1.default.bold);
56
+ result += formatText(`Differences in ${boldTable} table\n`, chalk_1.default.underline);
57
+ // Shift around the newlines so that we can cleanly show +/- symbols
58
+ for (let i = 1; i < diff.length; i++) {
59
+ const prev = diff[i - 1].value;
60
+ if (prev[prev.length - 1] === '\n') {
61
+ diff[i - 1].value = prev.slice(0, -1);
62
+ diff[i].value = '\n' + diff[i].value;
63
+ }
64
+ }
65
+ lodash_1.default.forEach(diff, (part, index) => {
66
+ if (index === 0) {
67
+ part.value = '\n' + part.value;
68
+ }
69
+ const mark = part.added ? '+ ' : part.removed ? '- ' : ' ';
70
+ let change = part.value.split('\n').join(`\n${mark}`);
71
+ if (index === 0) {
72
+ change = change.slice(1, change.length);
73
+ }
74
+ if (part.added || part.removed) {
75
+ result += formatText(change, part.added ? chalk_1.default.green : part.removed ? chalk_1.default.red : null);
76
+ }
77
+ });
78
+ result += '\n\n';
79
+ });
80
+ // Determine if the values of any enums differ
81
+ const enumsIntersection = lodash_1.default.intersection(lodash_1.default.keys(description1.enums), lodash_1.default.keys(description2.enums));
82
+ lodash_1.default.forEach(enumsIntersection, (enumName) => {
83
+ // We don't need to do a particularly fancy diff here, since
84
+ // enums are just represented here as strings
85
+ if (description1.enums[enumName].trim() !== description2.enums[enumName].trim()) {
86
+ const boldEnum = formatText(enumName, chalk_1.default.bold);
87
+ result += formatText(`Differences in ${boldEnum} enum\n`);
88
+ result += formatText(`- ${description1.enums[enumName].trim()}\n`, chalk_1.default.red);
89
+ result += formatText(`+ ${description2.enums[enumName].trim()}\n`, chalk_1.default.green);
90
+ result += '\n\n';
91
+ }
92
+ });
93
+ return result;
94
+ }
95
+ async function loadDescriptionFromDisk(dirPath) {
96
+ const description = {
97
+ tables: {},
98
+ enums: {},
99
+ };
100
+ const tables = await fs_extra_1.default.readdir(node_path_1.default.join(dirPath, 'tables'));
101
+ for (const table of tables) {
102
+ const data = await fs_extra_1.default.readFile(node_path_1.default.join(dirPath, 'tables', table), 'utf8');
103
+ description.tables[table.replace('.pg', '')] = data;
104
+ }
105
+ const enums = await fs_extra_1.default.readdir(node_path_1.default.join(dirPath, 'enums'));
106
+ for (const enumName of enums) {
107
+ const data = await fs_extra_1.default.readFile(node_path_1.default.join(dirPath, 'enums', enumName), 'utf8');
108
+ description.enums[enumName.replace('.pg', '')] = data;
109
+ }
110
+ return description;
111
+ }
112
+ async function loadDescriptionFromDatabase(name) {
113
+ const description = await (0, describe_1.describeDatabase)(name);
114
+ return (0, describe_1.formatDatabaseDescription)(description, { coloredOutput: false });
115
+ }
116
+ async function loadDescription(db) {
117
+ if (db.type === 'database') {
118
+ return loadDescriptionFromDatabase(db.name);
119
+ }
120
+ else if (db.type === 'directory') {
121
+ return loadDescriptionFromDisk(db.path);
122
+ }
123
+ else {
124
+ throw new Error('Invalid database type');
125
+ }
126
+ }
127
+ async function diffDatabases(database1, database2, options) {
128
+ return diff({
129
+ type: 'database',
130
+ name: database1,
131
+ }, {
132
+ type: 'database',
133
+ name: database2,
134
+ }, options);
135
+ }
136
+ exports.diffDatabases = diffDatabases;
137
+ async function diffDatabaseAndDirectory(database, directory, options) {
138
+ return diff({
139
+ type: 'database',
140
+ name: database,
141
+ }, {
142
+ type: 'directory',
143
+ path: directory,
144
+ }, options);
145
+ }
146
+ exports.diffDatabaseAndDirectory = diffDatabaseAndDirectory;
147
+ async function diffDirectoryAndDatabase(directory, database, options) {
148
+ return diff({
149
+ type: 'directory',
150
+ path: directory,
151
+ }, {
152
+ type: 'database',
153
+ name: database,
154
+ }, options);
155
+ }
156
+ exports.diffDirectoryAndDatabase = diffDirectoryAndDatabase;
157
+ async function diffDirectories(directory1, directory2, options) {
158
+ return diff({
159
+ type: 'directory',
160
+ path: directory1,
161
+ }, {
162
+ type: 'directory',
163
+ path: directory2,
164
+ }, options);
165
+ }
166
+ exports.diffDirectories = diffDirectories;
167
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { describeDatabase, formatDatabaseDescription } from './describe';
2
+ export { diffDatabases, diffDirectories, diffDatabaseAndDirectory, diffDirectoryAndDatabase, } from './diff';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diffDirectoryAndDatabase = exports.diffDatabaseAndDirectory = exports.diffDirectories = exports.diffDatabases = exports.formatDatabaseDescription = exports.describeDatabase = void 0;
4
+ var describe_1 = require("./describe");
5
+ Object.defineProperty(exports, "describeDatabase", { enumerable: true, get: function () { return describe_1.describeDatabase; } });
6
+ Object.defineProperty(exports, "formatDatabaseDescription", { enumerable: true, get: function () { return describe_1.formatDatabaseDescription; } });
7
+ var diff_1 = require("./diff");
8
+ Object.defineProperty(exports, "diffDatabases", { enumerable: true, get: function () { return diff_1.diffDatabases; } });
9
+ Object.defineProperty(exports, "diffDirectories", { enumerable: true, get: function () { return diff_1.diffDirectories; } });
10
+ Object.defineProperty(exports, "diffDatabaseAndDirectory", { enumerable: true, get: function () { return diff_1.diffDatabaseAndDirectory; } });
11
+ Object.defineProperty(exports, "diffDirectoryAndDatabase", { enumerable: true, get: function () { return diff_1.diffDirectoryAndDatabase; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAAyE;AAAhE,4GAAA,gBAAgB,OAAA;AAAE,qHAAA,yBAAyB,OAAA;AACpD,+BAKgB;AAJd,qGAAA,aAAa,OAAA;AACb,uGAAA,eAAe,OAAA;AACf,gHAAA,wBAAwB,OAAA;AACxB,gHAAA,wBAAwB,OAAA"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@prairielearn/postgres-tools",
3
+ "version": "1.0.0",
4
+ "main": "./dist/index.js",
5
+ "bin": {
6
+ "pg-describe": "./dist/bin/pg-describe.js",
7
+ "pg-diff": "./dist/bin/pg-diff.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/PrairieLearn/PrairieLearn.git",
12
+ "directory": "packages/postgres-tools"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc && copyfiles -u 1 \"./src/**/*.sql\" dist",
16
+ "dev": "tsc --watch --preserveWatchOutput"
17
+ },
18
+ "devDependencies": {
19
+ "@prairielearn/tsconfig": "^0.0.0",
20
+ "@types/diff": "^5.0.3",
21
+ "@types/fs-extra": "^11.0.1",
22
+ "@types/node": "^18.15.11",
23
+ "copyfiles": "^2.4.1",
24
+ "typescript": "^4.9.4"
25
+ },
26
+ "dependencies": {
27
+ "@prairielearn/postgres": "^1.6.0",
28
+ "@types/lodash": "^4.14.192",
29
+ "chalk": "^4.1.2",
30
+ "diff": "^5.1.0",
31
+ "fs-extra": "^11.1.1",
32
+ "lodash": "^4.17.21"
33
+ }
34
+ }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ import async from 'async';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs-extra';
6
+ import _ from 'lodash';
7
+ import path from 'path';
8
+ import yargs from 'yargs';
9
+
10
+ import { describeDatabase, formatDatabaseDescription, DatabaseDescription } from '../describe';
11
+
12
+ const args = yargs
13
+ .usage('Usage: $0 <database name> [options]')
14
+ .demandCommand(1)
15
+ .option('output', {
16
+ alias: 'o',
17
+ nargs: 1,
18
+ string: true,
19
+ description: 'Specify a directory to output files to',
20
+ })
21
+ .option('ignore-tables', {
22
+ array: true,
23
+ description: 'a list of tables to ignore',
24
+ })
25
+ .option('ignore-enums', {
26
+ array: true,
27
+ description: 'a list of enums to ignore',
28
+ })
29
+ .option('ignore-columns', {
30
+ array: true,
31
+ description: 'a list of columns to ignore, formatted like [table].[column]',
32
+ })
33
+ .help('h')
34
+ .alias('h', 'help')
35
+ .example('$0 postgres', 'Describe the "postgres" database')
36
+ .example(
37
+ '$0 userdb -o db_description --ignore-tables a b --ignore-columns a.col1 a.col2',
38
+ 'Describe the "userdb" database; ignore specific tables and columns'
39
+ )
40
+ .strict();
41
+
42
+ const argv = args.parseSync();
43
+
44
+ if (argv._.length !== 1) {
45
+ args.showHelp();
46
+ process.exit(1);
47
+ }
48
+
49
+ // Disable color if we're not attached to a tty
50
+ const coloredOutput = !argv.output && process.stdout.isTTY;
51
+
52
+ const options = {
53
+ ignoreTables: (argv['ignore-tables'] ?? []).map((table) => table.toString()),
54
+ ignoreEnums: (argv['ignore-enums'] ?? []).map((enumName) => enumName.toString()),
55
+ ignoreColumns: (argv['ignore-columns'] ?? []).map((column) => column.toString()),
56
+ };
57
+
58
+ function formatText(text: string, formatter: (text: string) => string) {
59
+ if (!argv.output && coloredOutput) {
60
+ return formatter(text);
61
+ }
62
+ return text;
63
+ }
64
+
65
+ describeDatabase(argv._[0].toString(), options).then(
66
+ async (description) => {
67
+ if (argv.output) {
68
+ await writeDescriptionToDisk(description, argv.output);
69
+ } else {
70
+ printDescription(description);
71
+ }
72
+ process.exit(0);
73
+ },
74
+ (err) => {
75
+ console.error(err);
76
+ process.exit(1);
77
+ }
78
+ );
79
+
80
+ function printDescription(description: DatabaseDescription) {
81
+ const formattedDescription = formatDatabaseDescription(description, { coloredOutput });
82
+ _.forEach(_.sortBy(_.keys(formattedDescription.tables)), (tableName) => {
83
+ process.stdout.write(formatText(`[table] ${tableName}\n`, chalk.bold));
84
+ process.stdout.write(formattedDescription.tables[tableName]);
85
+ process.stdout.write('\n\n');
86
+ });
87
+
88
+ _.forEach(_.sortBy(_.keys(formattedDescription.enums)), (enumName) => {
89
+ process.stdout.write(formatText(`[enum] ${enumName}\n`, chalk.bold));
90
+ process.stdout.write(formattedDescription.enums[enumName]);
91
+ process.stdout.write('\n\n');
92
+ });
93
+ }
94
+
95
+ async function writeDescriptionToDisk(description: DatabaseDescription, dir: string) {
96
+ const formattedDescription = formatDatabaseDescription(description, { coloredOutput: false });
97
+ await fs.emptyDir(dir);
98
+ await fs.mkdir(path.join(dir, 'tables'));
99
+ await fs.mkdir(path.join(dir, 'enums'));
100
+ await async.eachOf(formattedDescription.tables, async (value, key) => {
101
+ await fs.writeFile(path.join(dir, 'tables', `${key}.pg`), value);
102
+ });
103
+ await async.eachOf(formattedDescription.enums, async (value, key) => {
104
+ await fs.writeFile(path.join(dir, 'enums', `${key}.pg`), value);
105
+ });
106
+ }
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import yargs from 'yargs';
4
+
5
+ import {
6
+ diffDatabases,
7
+ diffDirectories,
8
+ diffDatabaseAndDirectory,
9
+ diffDirectoryAndDatabase,
10
+ } from '../diff';
11
+
12
+ const args = yargs
13
+ .usage('Usage: $0 [options]')
14
+ .option('db', {
15
+ description: 'reads a description from the named database',
16
+ })
17
+ .option('dir', {
18
+ description: "reads a description from a directory that's been generated by pg-describe",
19
+ })
20
+ .help('h')
21
+ .alias('h', 'help')
22
+ .example(
23
+ '$0 --db postgres --dir db_dump',
24
+ 'Diffs the database "postgres" with the description in the directory "db_dump"'
25
+ )
26
+ .example(
27
+ '$0 --db postgres --db old_restore',
28
+ 'Diffs the database "postgres" with the database "old_restore"'
29
+ )
30
+ .strict();
31
+
32
+ const argv = args.parseSync();
33
+
34
+ const options = {
35
+ outputFormat: 'string',
36
+ coloredOutput: process.stdout.isTTY,
37
+ };
38
+
39
+ if (argv.db && typeof argv.db === 'string' && argv.dir && typeof argv.dir === 'string') {
40
+ // Ensure correct ordering for the sake of diffs
41
+ if (process.argv.indexOf('--db') < process.argv.indexOf('--dir')) {
42
+ diffDatabaseAndDirectory(argv.db, argv.dir, options).then(handleResults, handleError);
43
+ } else {
44
+ diffDirectoryAndDatabase(argv.dir, argv.db, options).then(handleResults, handleError);
45
+ }
46
+ } else if (argv.db && Array.isArray(argv.db) && argv.db.length === 2 && !argv.dir) {
47
+ diffDatabases(argv.db[0], argv.db[1], options).then(handleResults, handleError);
48
+ } else if (argv.dir && Array.isArray(argv.dir) && argv.dir.length === 2 && !argv.db) {
49
+ diffDirectories(argv.dir[0], argv.dir[1], options).then(handleResults, handleError);
50
+ } else {
51
+ args.showHelp();
52
+ process.exit(1);
53
+ }
54
+
55
+ function handleResults(results: string) {
56
+ process.stdout.write(results);
57
+ process.exit(0);
58
+ }
59
+
60
+ function handleError(err: any) {
61
+ console.error(err);
62
+ process.exit(1);
63
+ }
@@ -0,0 +1,129 @@
1
+ -- BLOCK get_tables
2
+ SELECT
3
+ c.relname AS name,
4
+ c.oid AS oid
5
+ FROM
6
+ pg_catalog.pg_class c
7
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
8
+ WHERE
9
+ c.relkind = 'r'
10
+ AND n.nspname != 'pg_catalog'
11
+ AND n.nspname != 'information_schema'
12
+ AND n.nspname !~ '^pg_toast'
13
+ AND pg_catalog.pg_table_is_visible (c.oid)
14
+ ORDER BY
15
+ c.relname;
16
+
17
+ -- BLOCK get_columns_for_table
18
+ SELECT
19
+ a.attname AS name,
20
+ pg_catalog.format_type (a.atttypid, a.atttypmod) AS
21
+ type,
22
+ a.attnotnull AS notnull,
23
+ (
24
+ SELECT
25
+ substring(
26
+ pg_catalog.pg_get_expr (d.adbin, d.adrelid) for 128
27
+ )
28
+ FROM
29
+ pg_catalog.pg_attrdef d
30
+ WHERE
31
+ d.adrelid = a.attrelid
32
+ AND d.adnum = a.attnum
33
+ AND a.atthasdef
34
+ ) AS default
35
+ FROM
36
+ pg_catalog.pg_attribute a
37
+ JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
38
+ WHERE
39
+ a.attrelid = $oid
40
+ AND NOT a.attisdropped
41
+ AND a.attnum > 0
42
+ ORDER BY
43
+ a.attname;
44
+
45
+ -- BLOCK get_indexes_for_table
46
+ SELECT
47
+ c2.relname AS name,
48
+ i.indisprimary AS isprimary,
49
+ i.indisunique AS isunique,
50
+ pg_catalog.pg_get_indexdef (i.indexrelid, 0, true) AS indexdef,
51
+ pg_catalog.pg_get_constraintdef (con.oid, true) AS constraintdef,
52
+ contype
53
+ FROM
54
+ pg_catalog.pg_class c,
55
+ pg_catalog.pg_class c2,
56
+ pg_catalog.pg_index i
57
+ LEFT JOIN pg_catalog.pg_constraint con ON (
58
+ conrelid = i.indrelid
59
+ AND conindid = i.indexrelid
60
+ AND contype IN ('p', 'u')
61
+ )
62
+ WHERE
63
+ c.oid = $oid
64
+ AND c.oid = i.indrelid
65
+ AND i.indexrelid = c2.oid
66
+ ORDER BY
67
+ i.indisprimary DESC,
68
+ i.indisunique DESC,
69
+ c2.relname;
70
+
71
+ -- BLOCK get_references_for_table
72
+ SELECT
73
+ conname AS name,
74
+ conrelid::pg_catalog.regclass AS table,
75
+ pg_catalog.pg_get_constraintdef (c.oid, true) as condef
76
+ FROM
77
+ pg_catalog.pg_constraint c
78
+ WHERE
79
+ c.confrelid = $oid
80
+ AND c.contype = 'f'
81
+ ORDER BY
82
+ conname;
83
+
84
+ -- BLOCK get_foreign_key_constraints_for_table
85
+ SELECT
86
+ conname AS name,
87
+ pg_catalog.pg_get_constraintdef (r.oid, true) as def
88
+ FROM
89
+ pg_catalog.pg_constraint r
90
+ WHERE
91
+ r.conrelid = $oid
92
+ AND r.contype = 'f'
93
+ ORDER BY
94
+ 1;
95
+
96
+ -- BLOCK get_check_constraints_for_table
97
+ SELECT
98
+ conname AS name,
99
+ pg_catalog.pg_get_constraintdef (r.oid, true) as def
100
+ FROM
101
+ pg_catalog.pg_constraint r
102
+ WHERE
103
+ r.conrelid = $oid
104
+ AND r.contype = 'c'
105
+ ORDER BY
106
+ 1;
107
+
108
+ -- BLOCK get_enums
109
+ SELECT
110
+ t.typname AS name,
111
+ ARRAY (
112
+ SELECT
113
+ e.enumlabel
114
+ FROM
115
+ pg_catalog.pg_enum e
116
+ WHERE
117
+ e.enumtypid = t.oid
118
+ ORDER BY
119
+ e.enumsortorder
120
+ ) AS
121
+ values
122
+ FROM
123
+ pg_type t
124
+ JOIN pg_enum e ON t.oid = e.enumtypid
125
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
126
+ GROUP BY
127
+ n.nspname,
128
+ t.typname,
129
+ t.oid;