@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/.turbo/turbo-build.log +0 -0
- package/README.md +0 -0
- package/dist/bin/pg-describe.d.ts +2 -0
- package/dist/bin/pg-describe.js +95 -0
- package/dist/bin/pg-describe.js.map +1 -0
- package/dist/bin/pg-diff.d.ts +2 -0
- package/dist/bin/pg-diff.js +54 -0
- package/dist/bin/pg-diff.js.map +1 -0
- package/dist/describe.d.ts +55 -0
- package/dist/describe.js +236 -0
- package/dist/describe.js.map +1 -0
- package/dist/describe.sql +129 -0
- package/dist/diff.d.ts +8 -0
- package/dist/diff.js +167 -0
- package/dist/diff.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
- package/src/bin/pg-describe.ts +106 -0
- package/src/bin/pg-diff.ts +63 -0
- package/src/describe.sql +129 -0
- package/src/describe.ts +321 -0
- package/src/diff.ts +232 -0
- package/src/index.ts +7 -0
- package/tsconfig.json +8 -0
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
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
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
|
+
}
|
package/src/describe.sql
ADDED
|
@@ -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;
|