@prairielearn/postgres-tools 2.0.23 → 2.0.25
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 +16 -0
- package/dist/describe.d.ts +2 -2
- package/dist/describe.js +14 -14
- package/dist/describe.js.map +1 -1
- package/dist/describe.sql +12 -12
- package/package.json +7 -7
- package/src/describe.sql +12 -12
- package/src/describe.ts +16 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @prairielearn/postgres-tools
|
|
2
2
|
|
|
3
|
+
## 2.0.25
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c72a4b8: Upgrade dependencies
|
|
8
|
+
- Updated dependencies [c72a4b8]
|
|
9
|
+
- @prairielearn/postgres@4.4.1
|
|
10
|
+
|
|
11
|
+
## 2.0.24
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- f571b40: Upgrade all JavaScript dependencies
|
|
16
|
+
- Updated dependencies [f571b40]
|
|
17
|
+
- @prairielearn/postgres@4.1.1
|
|
18
|
+
|
|
3
19
|
## 2.0.23
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/describe.d.ts
CHANGED
package/dist/describe.js
CHANGED
|
@@ -12,7 +12,7 @@ async function describeWithPool(pool, options) {
|
|
|
12
12
|
};
|
|
13
13
|
// Get the names of the tables and filter out any ignored tables
|
|
14
14
|
const tablesRes = await pool.queryAsync(sql.get_tables, []);
|
|
15
|
-
const tables = tablesRes.rows.filter((table) => ignoreTables.
|
|
15
|
+
const tables = tablesRes.rows.filter((table) => !ignoreTables.includes(table.name));
|
|
16
16
|
// Transform ignored columns into a map from table names to arrays
|
|
17
17
|
// of column names
|
|
18
18
|
if (options.ignoreColumns && Array.isArray(options.ignoreColumns)) {
|
|
@@ -37,7 +37,7 @@ async function describeWithPool(pool, options) {
|
|
|
37
37
|
oid: table.oid,
|
|
38
38
|
});
|
|
39
39
|
const columns = columnResults.rows.filter((row) => {
|
|
40
|
-
return (ignoreColumns[table.name] || []).
|
|
40
|
+
return !(ignoreColumns[table.name] || []).includes(row.name);
|
|
41
41
|
});
|
|
42
42
|
const indexResults = await pool.queryAsync(sql.get_indexes_for_table, {
|
|
43
43
|
oid: table.oid,
|
|
@@ -48,7 +48,7 @@ async function describeWithPool(pool, options) {
|
|
|
48
48
|
});
|
|
49
49
|
// Filter out references from ignored tables
|
|
50
50
|
const references = referenceResults.rows.filter((row) => {
|
|
51
|
-
return ignoreTables.
|
|
51
|
+
return !ignoreTables.includes(row.table);
|
|
52
52
|
});
|
|
53
53
|
const checkConstraintResults = await pool.queryAsync(sql.get_check_constraints_for_table, {
|
|
54
54
|
oid: table.oid,
|
|
@@ -65,7 +65,7 @@ async function describeWithPool(pool, options) {
|
|
|
65
65
|
const enumsRes = await pool.queryAsync(sql.get_enums, []);
|
|
66
66
|
// Filter ignored enums
|
|
67
67
|
const rows = enumsRes.rows.filter((row) => {
|
|
68
|
-
return ignoreEnums.
|
|
68
|
+
return !ignoreEnums.includes(row.name);
|
|
69
69
|
});
|
|
70
70
|
rows.forEach((row) => {
|
|
71
71
|
output.enums[row.name] = parsePostgresArray(row.values);
|
|
@@ -120,29 +120,29 @@ export function formatDatabaseDescription(description, options = { coloredOutput
|
|
|
120
120
|
.map((row) => {
|
|
121
121
|
let rowText = formatText(` ${row.name}`, chalk.bold);
|
|
122
122
|
rowText += ':' + formatText(` ${row.type}`, chalk.green);
|
|
123
|
-
if (row.
|
|
123
|
+
if (row.isnotnull) {
|
|
124
124
|
rowText += formatText(' not null', chalk.gray);
|
|
125
125
|
}
|
|
126
|
-
if (row.
|
|
127
|
-
rowText += formatText(` default ${row.
|
|
126
|
+
if (row.defaultval) {
|
|
127
|
+
rowText += formatText(` default ${row.defaultval}`, chalk.gray);
|
|
128
128
|
}
|
|
129
129
|
return rowText;
|
|
130
130
|
})
|
|
131
131
|
.join('\n');
|
|
132
132
|
}
|
|
133
133
|
if (table.indexes.length > 0) {
|
|
134
|
-
if (output.tables[tableName].length
|
|
134
|
+
if (output.tables[tableName].length > 0) {
|
|
135
135
|
output.tables[tableName] += '\n\n';
|
|
136
136
|
}
|
|
137
137
|
output.tables[tableName] += formatText('indexes\n', chalk.underline);
|
|
138
138
|
output.tables[tableName] += table.indexes
|
|
139
139
|
.map((row) => {
|
|
140
|
-
const using = row.indexdef.
|
|
140
|
+
const using = row.indexdef.slice(Math.max(0, row.indexdef.indexOf('USING ')));
|
|
141
141
|
let rowText = formatText(` ${row.name}`, chalk.bold) + ':';
|
|
142
142
|
// Primary indexes are implicitly unique, so we don't need to
|
|
143
143
|
// capture that explicitly.
|
|
144
144
|
if (row.isunique && !row.isprimary) {
|
|
145
|
-
if (!row.constraintdef || row.constraintdef.
|
|
145
|
+
if (!row.constraintdef || !row.constraintdef.includes('UNIQUE')) {
|
|
146
146
|
// Some unique indexes don't include the UNIQUE constraint
|
|
147
147
|
// as part of the constraint definition, so we need to capture
|
|
148
148
|
// that manually.
|
|
@@ -156,7 +156,7 @@ export function formatDatabaseDescription(description, options = { coloredOutput
|
|
|
156
156
|
.join('\n');
|
|
157
157
|
}
|
|
158
158
|
if (table.checkConstraints.length > 0) {
|
|
159
|
-
if (output.tables[tableName].length
|
|
159
|
+
if (output.tables[tableName].length > 0) {
|
|
160
160
|
output.tables[tableName] += '\n\n';
|
|
161
161
|
}
|
|
162
162
|
output.tables[tableName] += formatText('check constraints\n', chalk.underline);
|
|
@@ -171,7 +171,7 @@ export function formatDatabaseDescription(description, options = { coloredOutput
|
|
|
171
171
|
//
|
|
172
172
|
// The second replace handles all other lines: we want to collapse
|
|
173
173
|
// all leading whitespace into a single space.
|
|
174
|
-
const def = row.def.
|
|
174
|
+
const def = row.def.replaceAll('(\n', '(').replaceAll(/\n\s*/g, ' ');
|
|
175
175
|
let rowText = formatText(` ${row.name}:`, chalk.bold);
|
|
176
176
|
rowText += formatText(` ${def}`, chalk.green);
|
|
177
177
|
return rowText;
|
|
@@ -179,7 +179,7 @@ export function formatDatabaseDescription(description, options = { coloredOutput
|
|
|
179
179
|
.join('\n');
|
|
180
180
|
}
|
|
181
181
|
if (table.foreignKeyConstraints.length > 0) {
|
|
182
|
-
if (output.tables[tableName].length
|
|
182
|
+
if (output.tables[tableName].length > 0) {
|
|
183
183
|
output.tables[tableName] += '\n\n';
|
|
184
184
|
}
|
|
185
185
|
output.tables[tableName] += formatText('foreign-key constraints\n', chalk.underline);
|
|
@@ -192,7 +192,7 @@ export function formatDatabaseDescription(description, options = { coloredOutput
|
|
|
192
192
|
.join('\n');
|
|
193
193
|
}
|
|
194
194
|
if (table.references.length > 0) {
|
|
195
|
-
if (output.tables[tableName].length
|
|
195
|
+
if (output.tables[tableName].length > 0) {
|
|
196
196
|
output.tables[tableName] += '\n\n';
|
|
197
197
|
}
|
|
198
198
|
output.tables[tableName] += formatText('referenced by\n', chalk.underline);
|
package/dist/describe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe.js","sourceRoot":"","sources":["../src/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from 'chalk';\nimport { parse as parsePostgresArray } from 'postgres-array';\n\nimport { PostgresPool, loadSqlEquiv } from '@prairielearn/postgres';\n\nconst sql = loadSqlEquiv(import.meta.url);\n\ninterface ColumnDescription {\n name: string;\n type: string;\n notnull: boolean;\n default: any;\n}\n\ninterface IndexDescription {\n name: string;\n isprimary: boolean;\n isunique: boolean;\n indexdef: string;\n constraintdef: string;\n contype: string;\n}\n\ninterface ForeignKeyConstraintDescription {\n name: string;\n def: string;\n}\n\ninterface ReferenceDescription {\n name: string;\n table: string;\n condef: string;\n}\n\ninterface CheckConstraintDescription {\n name: string;\n def: string;\n}\n\ninterface TableDescription {\n columns: ColumnDescription[];\n indexes: IndexDescription[];\n foreignKeyConstraints: ForeignKeyConstraintDescription[];\n references: ReferenceDescription[];\n checkConstraints: CheckConstraintDescription[];\n}\n\nexport interface DatabaseDescription {\n tables: Record<string, TableDescription>;\n enums: Record<string, string[]>;\n}\n\ninterface DescribeOptions {\n ignoreTables?: string[];\n ignoreColumns?: string[];\n ignoreEnums?: string[];\n}\n\nasync function describeWithPool(\n pool: PostgresPool,\n options: DescribeOptions,\n): Promise<DatabaseDescription> {\n const ignoreTables = options?.ignoreTables || [];\n const ignoreEnums = options?.ignoreEnums || [];\n let ignoreColumns: Record<string, string[]> = {};\n\n const output: DatabaseDescription = {\n tables: {},\n enums: {},\n };\n\n // Get the names of the tables and filter out any ignored tables\n const tablesRes = await pool.queryAsync(sql.get_tables, []);\n const tables = tablesRes.rows.filter((table) => ignoreTables.indexOf(table.name) === -1);\n\n // Transform ignored columns into a map from table names to arrays\n // of column names\n if (options.ignoreColumns && Array.isArray(options.ignoreColumns)) {\n ignoreColumns = options.ignoreColumns\n .filter((ignore) => {\n return /^[^\\s.]*\\.[^\\s.]*$/.test(ignore);\n })\n .reduce(\n (result, value) => {\n const res = /^(([^\\s.]*)\\.([^\\s.]*))$/.exec(value);\n if (!res) {\n throw new Error(`Invalid ignore column: ${value}`);\n }\n const table = res[2];\n const column = res[3];\n (result[table] || (result[table] = [])).push(column);\n return result;\n },\n {} as Record<string, string[]>,\n );\n }\n\n // Get column info for each table\n for (const table of tables) {\n const columnResults = await pool.queryAsync(sql.get_columns_for_table, {\n oid: table.oid,\n });\n\n const columns = columnResults.rows.filter((row) => {\n return (ignoreColumns[table.name] || []).indexOf(row.name) === -1;\n });\n\n const indexResults = await pool.queryAsync(sql.get_indexes_for_table, {\n oid: table.oid,\n });\n\n const foreignKeyConstraintResults = await pool.queryAsync(\n sql.get_foreign_key_constraints_for_table,\n { oid: table.oid },\n );\n\n const referenceResults = await pool.queryAsync(sql.get_references_for_table, {\n oid: table.oid,\n });\n\n // Filter out references from ignored tables\n const references = referenceResults.rows.filter((row) => {\n return ignoreTables.indexOf(row.table) === -1;\n });\n\n const checkConstraintResults = await pool.queryAsync(sql.get_check_constraints_for_table, {\n oid: table.oid,\n });\n\n output.tables[table.name] = {\n columns,\n indexes: indexResults.rows,\n foreignKeyConstraints: foreignKeyConstraintResults.rows,\n references,\n checkConstraints: checkConstraintResults.rows,\n };\n }\n\n // Get all enums\n const enumsRes = await pool.queryAsync(sql.get_enums, []);\n\n // Filter ignored enums\n const rows = enumsRes.rows.filter((row) => {\n return ignoreEnums.indexOf(row.name) === -1;\n });\n\n rows.forEach((row) => {\n output.enums[row.name] = parsePostgresArray(row.values);\n });\n\n return output;\n}\n\n/**\n * Will produce a description of a given database's schema. This will include\n * information about tables, enums, constraints, indices, etc.\n */\nexport async function describeDatabase(\n databaseName: string,\n options: DescribeOptions = {},\n): Promise<DatabaseDescription> {\n // Connect to the database.\n const pool = new PostgresPool();\n const pgConfig = {\n user: 'postgres',\n database: databaseName,\n host: 'localhost',\n max: 10,\n idleTimeoutMillis: 30000,\n };\n function idleErrorHandler(err: Error) {\n throw err;\n }\n await pool.initAsync(pgConfig, idleErrorHandler);\n\n try {\n return await describeWithPool(pool, options);\n } finally {\n await pool.closeAsync();\n }\n}\n\nexport function formatDatabaseDescription(\n description: DatabaseDescription,\n options = { coloredOutput: true },\n): { tables: Record<string, string>; enums: Record<string, string> } {\n const output = {\n tables: {} as Record<string, string>,\n enums: {} as Record<string, string>,\n };\n\n Object.keys(description.tables).forEach((tableName) => (output.tables[tableName] = ''));\n\n /**\n * Optionally applies the given formatter to the text if colored output is\n * enabled.\n */\n function formatText(text: string, formatter: (s: string) => string): string {\n if (options.coloredOutput) {\n return formatter(text);\n }\n return text;\n }\n\n Object.entries(description.tables).forEach(([tableName, table]) => {\n if (table.columns.length > 0) {\n output.tables[tableName] += formatText('columns\\n', chalk.underline);\n output.tables[tableName] += table.columns\n .map((row) => {\n let rowText = formatText(` ${row.name}`, chalk.bold);\n rowText += ':' + formatText(` ${row.type}`, chalk.green);\n if (row.notnull) {\n rowText += formatText(' not null', chalk.gray);\n }\n if (row.default) {\n rowText += formatText(` default ${row.default}`, chalk.gray);\n }\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.indexes.length > 0) {\n if (output.tables[tableName].length !== 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('indexes\\n', chalk.underline);\n output.tables[tableName] += table.indexes\n .map((row) => {\n const using = row.indexdef.substring(row.indexdef.indexOf('USING '));\n let rowText = formatText(` ${row.name}`, chalk.bold) + ':';\n // Primary indexes are implicitly unique, so we don't need to\n // capture that explicitly.\n if (row.isunique && !row.isprimary) {\n if (!row.constraintdef || row.constraintdef.indexOf('UNIQUE') === -1) {\n // Some unique indexes don't include the UNIQUE constraint\n // as part of the constraint definition, so we need to capture\n // that manually.\n rowText += formatText(' UNIQUE', chalk.green);\n }\n }\n rowText += row.constraintdef ? formatText(` ${row.constraintdef}`, chalk.green) : '';\n rowText += using ? formatText(` ${using}`, chalk.green) : '';\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.checkConstraints.length > 0) {\n if (output.tables[tableName].length !== 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('check constraints\\n', chalk.underline);\n output.tables[tableName] += table.checkConstraints\n .map((row) => {\n // Particularly long constraints are formatted as multiple lines.\n // We'll collapse them into a single line for better appearance in\n // the resulting file.\n //\n // The first replace handles lines that end with a parenthesis: we\n // want to avoid spaces between the parenthesis and the next token.\n //\n // The second replace handles all other lines: we want to collapse\n // all leading whitespace into a single space.\n const def = row.def.replace(/\\(\\n/g, '(').replace(/\\n\\s*/g, ' ');\n\n let rowText = formatText(` ${row.name}:`, chalk.bold);\n rowText += formatText(` ${def}`, chalk.green);\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.foreignKeyConstraints.length > 0) {\n if (output.tables[tableName].length !== 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('foreign-key constraints\\n', chalk.underline);\n output.tables[tableName] += table.foreignKeyConstraints\n .map((row) => {\n let rowText = formatText(` ${row.name}:`, chalk.bold);\n rowText += formatText(` ${row.def}`, chalk.green);\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.references.length > 0) {\n if (output.tables[tableName].length !== 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('referenced by\\n', chalk.underline);\n output.tables[tableName] += table.references\n ?.map((row) => {\n let rowText = formatText(` ${row.table}:`, chalk.bold);\n rowText += formatText(` ${row.condef}`, chalk.green);\n return rowText;\n })\n .join('\\n');\n }\n });\n\n Object.entries(description.enums).forEach(([enumName, enumValues]) => {\n output.enums[enumName] = formatText(enumValues.join(', '), chalk.gray);\n });\n\n // We need to tack on a newline to everything.\n Object.entries(output.tables).forEach(([tableName, table]) => {\n output.tables[tableName] = table + '\\n';\n });\n Object.entries(output.enums).forEach(([enumName, enumValues]) => {\n output.enums[enumName] = enumValues + '\\n';\n });\n\n return output;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"describe.js","sourceRoot":"","sources":["../src/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from 'chalk';\nimport { parse as parsePostgresArray } from 'postgres-array';\n\nimport { PostgresPool, loadSqlEquiv } from '@prairielearn/postgres';\n\nconst sql = loadSqlEquiv(import.meta.url);\n\ninterface ColumnDescription {\n name: string;\n type: string;\n isnotnull: boolean;\n defaultval: any;\n}\n\ninterface IndexDescription {\n name: string;\n isprimary: boolean;\n isunique: boolean;\n indexdef: string;\n constraintdef: string;\n contype: string;\n}\n\ninterface ForeignKeyConstraintDescription {\n name: string;\n def: string;\n}\n\ninterface ReferenceDescription {\n name: string;\n table: string;\n condef: string;\n}\n\ninterface CheckConstraintDescription {\n name: string;\n def: string;\n}\n\ninterface TableDescription {\n columns: ColumnDescription[];\n indexes: IndexDescription[];\n foreignKeyConstraints: ForeignKeyConstraintDescription[];\n references: ReferenceDescription[];\n checkConstraints: CheckConstraintDescription[];\n}\n\nexport interface DatabaseDescription {\n tables: Record<string, TableDescription>;\n enums: Record<string, string[]>;\n}\n\ninterface DescribeOptions {\n ignoreTables?: string[];\n ignoreColumns?: string[];\n ignoreEnums?: string[];\n}\n\nasync function describeWithPool(\n pool: PostgresPool,\n options: DescribeOptions,\n): Promise<DatabaseDescription> {\n const ignoreTables = options?.ignoreTables || [];\n const ignoreEnums = options?.ignoreEnums || [];\n let ignoreColumns: Record<string, string[]> = {};\n\n const output: DatabaseDescription = {\n tables: {},\n enums: {},\n };\n\n // Get the names of the tables and filter out any ignored tables\n const tablesRes = await pool.queryAsync(sql.get_tables, []);\n const tables = tablesRes.rows.filter((table) => !ignoreTables.includes(table.name));\n\n // Transform ignored columns into a map from table names to arrays\n // of column names\n if (options.ignoreColumns && Array.isArray(options.ignoreColumns)) {\n ignoreColumns = options.ignoreColumns\n .filter((ignore) => {\n return /^[^\\s.]*\\.[^\\s.]*$/.test(ignore);\n })\n .reduce(\n (result, value) => {\n const res = /^(([^\\s.]*)\\.([^\\s.]*))$/.exec(value);\n if (!res) {\n throw new Error(`Invalid ignore column: ${value}`);\n }\n const table = res[2];\n const column = res[3];\n (result[table] || (result[table] = [])).push(column);\n return result;\n },\n {} as Record<string, string[]>,\n );\n }\n\n // Get column info for each table\n for (const table of tables) {\n const columnResults = await pool.queryAsync(sql.get_columns_for_table, {\n oid: table.oid,\n });\n\n const columns = columnResults.rows.filter((row) => {\n return !(ignoreColumns[table.name] || []).includes(row.name);\n });\n\n const indexResults = await pool.queryAsync(sql.get_indexes_for_table, {\n oid: table.oid,\n });\n\n const foreignKeyConstraintResults = await pool.queryAsync(\n sql.get_foreign_key_constraints_for_table,\n { oid: table.oid },\n );\n\n const referenceResults = await pool.queryAsync(sql.get_references_for_table, {\n oid: table.oid,\n });\n\n // Filter out references from ignored tables\n const references = referenceResults.rows.filter((row) => {\n return !ignoreTables.includes(row.table);\n });\n\n const checkConstraintResults = await pool.queryAsync(sql.get_check_constraints_for_table, {\n oid: table.oid,\n });\n\n output.tables[table.name] = {\n columns,\n indexes: indexResults.rows,\n foreignKeyConstraints: foreignKeyConstraintResults.rows,\n references,\n checkConstraints: checkConstraintResults.rows,\n };\n }\n\n // Get all enums\n const enumsRes = await pool.queryAsync(sql.get_enums, []);\n\n // Filter ignored enums\n const rows = enumsRes.rows.filter((row) => {\n return !ignoreEnums.includes(row.name);\n });\n\n rows.forEach((row) => {\n output.enums[row.name] = parsePostgresArray(row.values);\n });\n\n return output;\n}\n\n/**\n * Will produce a description of a given database's schema. This will include\n * information about tables, enums, constraints, indices, etc.\n */\nexport async function describeDatabase(\n databaseName: string,\n options: DescribeOptions = {},\n): Promise<DatabaseDescription> {\n // Connect to the database.\n const pool = new PostgresPool();\n const pgConfig = {\n user: 'postgres',\n database: databaseName,\n host: 'localhost',\n max: 10,\n idleTimeoutMillis: 30000,\n };\n function idleErrorHandler(err: Error) {\n throw err;\n }\n await pool.initAsync(pgConfig, idleErrorHandler);\n\n try {\n return await describeWithPool(pool, options);\n } finally {\n await pool.closeAsync();\n }\n}\n\nexport function formatDatabaseDescription(\n description: DatabaseDescription,\n options = { coloredOutput: true },\n): { tables: Record<string, string>; enums: Record<string, string> } {\n const output = {\n tables: {} as Record<string, string>,\n enums: {} as Record<string, string>,\n };\n\n Object.keys(description.tables).forEach((tableName) => (output.tables[tableName] = ''));\n\n /**\n * Optionally applies the given formatter to the text if colored output is\n * enabled.\n */\n function formatText(text: string, formatter: (s: string) => string): string {\n if (options.coloredOutput) {\n return formatter(text);\n }\n return text;\n }\n\n Object.entries(description.tables).forEach(([tableName, table]) => {\n if (table.columns.length > 0) {\n output.tables[tableName] += formatText('columns\\n', chalk.underline);\n output.tables[tableName] += table.columns\n .map((row) => {\n let rowText = formatText(` ${row.name}`, chalk.bold);\n rowText += ':' + formatText(` ${row.type}`, chalk.green);\n if (row.isnotnull) {\n rowText += formatText(' not null', chalk.gray);\n }\n if (row.defaultval) {\n rowText += formatText(` default ${row.defaultval}`, chalk.gray);\n }\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.indexes.length > 0) {\n if (output.tables[tableName].length > 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('indexes\\n', chalk.underline);\n output.tables[tableName] += table.indexes\n .map((row) => {\n const using = row.indexdef.slice(Math.max(0, row.indexdef.indexOf('USING ')));\n let rowText = formatText(` ${row.name}`, chalk.bold) + ':';\n // Primary indexes are implicitly unique, so we don't need to\n // capture that explicitly.\n if (row.isunique && !row.isprimary) {\n if (!row.constraintdef || !row.constraintdef.includes('UNIQUE')) {\n // Some unique indexes don't include the UNIQUE constraint\n // as part of the constraint definition, so we need to capture\n // that manually.\n rowText += formatText(' UNIQUE', chalk.green);\n }\n }\n rowText += row.constraintdef ? formatText(` ${row.constraintdef}`, chalk.green) : '';\n rowText += using ? formatText(` ${using}`, chalk.green) : '';\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.checkConstraints.length > 0) {\n if (output.tables[tableName].length > 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('check constraints\\n', chalk.underline);\n output.tables[tableName] += table.checkConstraints\n .map((row) => {\n // Particularly long constraints are formatted as multiple lines.\n // We'll collapse them into a single line for better appearance in\n // the resulting file.\n //\n // The first replace handles lines that end with a parenthesis: we\n // want to avoid spaces between the parenthesis and the next token.\n //\n // The second replace handles all other lines: we want to collapse\n // all leading whitespace into a single space.\n const def = row.def.replaceAll('(\\n', '(').replaceAll(/\\n\\s*/g, ' ');\n\n let rowText = formatText(` ${row.name}:`, chalk.bold);\n rowText += formatText(` ${def}`, chalk.green);\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.foreignKeyConstraints.length > 0) {\n if (output.tables[tableName].length > 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('foreign-key constraints\\n', chalk.underline);\n output.tables[tableName] += table.foreignKeyConstraints\n .map((row) => {\n let rowText = formatText(` ${row.name}:`, chalk.bold);\n rowText += formatText(` ${row.def}`, chalk.green);\n return rowText;\n })\n .join('\\n');\n }\n\n if (table.references.length > 0) {\n if (output.tables[tableName].length > 0) {\n output.tables[tableName] += '\\n\\n';\n }\n output.tables[tableName] += formatText('referenced by\\n', chalk.underline);\n output.tables[tableName] += table.references\n ?.map((row) => {\n let rowText = formatText(` ${row.table}:`, chalk.bold);\n rowText += formatText(` ${row.condef}`, chalk.green);\n return rowText;\n })\n .join('\\n');\n }\n });\n\n Object.entries(description.enums).forEach(([enumName, enumValues]) => {\n output.enums[enumName] = formatText(enumValues.join(', '), chalk.gray);\n });\n\n // We need to tack on a newline to everything.\n Object.entries(output.tables).forEach(([tableName, table]) => {\n output.tables[tableName] = table + '\\n';\n });\n Object.entries(output.enums).forEach(([enumName, enumValues]) => {\n output.enums[enumName] = enumValues + '\\n';\n });\n\n return output;\n}\n"]}
|
package/dist/describe.sql
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-- BLOCK get_tables
|
|
2
2
|
SELECT
|
|
3
3
|
c.relname AS name,
|
|
4
|
-
c.oid
|
|
4
|
+
c.oid
|
|
5
5
|
FROM
|
|
6
6
|
pg_catalog.pg_class c
|
|
7
7
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
@@ -18,11 +18,11 @@ ORDER BY
|
|
|
18
18
|
SELECT
|
|
19
19
|
a.attname AS name,
|
|
20
20
|
pg_catalog.format_type (a.atttypid, a.atttypmod) AS type,
|
|
21
|
-
a.attnotnull AS
|
|
21
|
+
a.attnotnull AS isnotnull,
|
|
22
22
|
(
|
|
23
23
|
SELECT
|
|
24
24
|
substring(
|
|
25
|
-
pg_catalog.pg_get_expr (d.adbin, d.adrelid)
|
|
25
|
+
pg_catalog.pg_get_expr (d.adbin, d.adrelid) FOR 128
|
|
26
26
|
)
|
|
27
27
|
FROM
|
|
28
28
|
pg_catalog.pg_attrdef d
|
|
@@ -30,7 +30,7 @@ SELECT
|
|
|
30
30
|
d.adrelid = a.attrelid
|
|
31
31
|
AND d.adnum = a.attnum
|
|
32
32
|
AND a.atthasdef
|
|
33
|
-
) AS
|
|
33
|
+
) AS defaultval
|
|
34
34
|
FROM
|
|
35
35
|
pg_catalog.pg_attribute a
|
|
36
36
|
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
|
@@ -46,8 +46,8 @@ SELECT
|
|
|
46
46
|
c2.relname AS name,
|
|
47
47
|
i.indisprimary AS isprimary,
|
|
48
48
|
i.indisunique AS isunique,
|
|
49
|
-
pg_catalog.pg_get_indexdef (i.indexrelid, 0,
|
|
50
|
-
pg_catalog.pg_get_constraintdef (con.oid,
|
|
49
|
+
pg_catalog.pg_get_indexdef (i.indexrelid, 0, TRUE) AS indexdef,
|
|
50
|
+
pg_catalog.pg_get_constraintdef (con.oid, TRUE) AS constraintdef,
|
|
51
51
|
contype
|
|
52
52
|
FROM
|
|
53
53
|
pg_catalog.pg_class c,
|
|
@@ -65,13 +65,13 @@ WHERE
|
|
|
65
65
|
ORDER BY
|
|
66
66
|
i.indisprimary DESC,
|
|
67
67
|
i.indisunique DESC,
|
|
68
|
-
c2.relname;
|
|
68
|
+
c2.relname ASC;
|
|
69
69
|
|
|
70
70
|
-- BLOCK get_references_for_table
|
|
71
71
|
SELECT
|
|
72
72
|
conname AS name,
|
|
73
73
|
conrelid::pg_catalog.regclass AS table,
|
|
74
|
-
pg_catalog.pg_get_constraintdef (c.oid,
|
|
74
|
+
pg_catalog.pg_get_constraintdef (c.oid, TRUE) AS condef
|
|
75
75
|
FROM
|
|
76
76
|
pg_catalog.pg_constraint c
|
|
77
77
|
WHERE
|
|
@@ -83,26 +83,26 @@ ORDER BY
|
|
|
83
83
|
-- BLOCK get_foreign_key_constraints_for_table
|
|
84
84
|
SELECT
|
|
85
85
|
conname AS name,
|
|
86
|
-
pg_catalog.pg_get_constraintdef (r.oid,
|
|
86
|
+
pg_catalog.pg_get_constraintdef (r.oid, TRUE) AS def
|
|
87
87
|
FROM
|
|
88
88
|
pg_catalog.pg_constraint r
|
|
89
89
|
WHERE
|
|
90
90
|
r.conrelid = $oid
|
|
91
91
|
AND r.contype = 'f'
|
|
92
92
|
ORDER BY
|
|
93
|
-
|
|
93
|
+
name;
|
|
94
94
|
|
|
95
95
|
-- BLOCK get_check_constraints_for_table
|
|
96
96
|
SELECT
|
|
97
97
|
conname AS name,
|
|
98
|
-
pg_catalog.pg_get_constraintdef (r.oid,
|
|
98
|
+
pg_catalog.pg_get_constraintdef (r.oid, TRUE) AS def
|
|
99
99
|
FROM
|
|
100
100
|
pg_catalog.pg_constraint r
|
|
101
101
|
WHERE
|
|
102
102
|
r.conrelid = $oid
|
|
103
103
|
AND r.contype = 'c'
|
|
104
104
|
ORDER BY
|
|
105
|
-
|
|
105
|
+
name;
|
|
106
106
|
|
|
107
107
|
-- BLOCK get_enums
|
|
108
108
|
SELECT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/postgres-tools",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"dev": "tsc --watch --preserveWatchOutput & tscp --watch"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@prairielearn/postgres": "^4.
|
|
20
|
+
"@prairielearn/postgres": "^4.4.1",
|
|
21
21
|
"async": "^3.2.6",
|
|
22
|
-
"chalk": "^5.
|
|
23
|
-
"commander": "^14.0.
|
|
22
|
+
"chalk": "^5.6.2",
|
|
23
|
+
"commander": "^14.0.1",
|
|
24
24
|
"diff": "^8.0.2",
|
|
25
|
-
"fs-extra": "^11.3.
|
|
25
|
+
"fs-extra": "^11.3.2",
|
|
26
26
|
"lodash": "^4.17.21",
|
|
27
27
|
"postgres-array": "^3.0.4"
|
|
28
28
|
},
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@prairielearn/tsconfig": "^0.0.0",
|
|
31
31
|
"@types/fs-extra": "^11.0.4",
|
|
32
32
|
"@types/lodash": "^4.17.20",
|
|
33
|
-
"@types/node": "^22.
|
|
34
|
-
"typescript": "^5.9.
|
|
33
|
+
"@types/node": "^22.18.8",
|
|
34
|
+
"typescript": "^5.9.3",
|
|
35
35
|
"typescript-cp": "^0.1.9"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/describe.sql
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-- BLOCK get_tables
|
|
2
2
|
SELECT
|
|
3
3
|
c.relname AS name,
|
|
4
|
-
c.oid
|
|
4
|
+
c.oid
|
|
5
5
|
FROM
|
|
6
6
|
pg_catalog.pg_class c
|
|
7
7
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
@@ -18,11 +18,11 @@ ORDER BY
|
|
|
18
18
|
SELECT
|
|
19
19
|
a.attname AS name,
|
|
20
20
|
pg_catalog.format_type (a.atttypid, a.atttypmod) AS type,
|
|
21
|
-
a.attnotnull AS
|
|
21
|
+
a.attnotnull AS isnotnull,
|
|
22
22
|
(
|
|
23
23
|
SELECT
|
|
24
24
|
substring(
|
|
25
|
-
pg_catalog.pg_get_expr (d.adbin, d.adrelid)
|
|
25
|
+
pg_catalog.pg_get_expr (d.adbin, d.adrelid) FOR 128
|
|
26
26
|
)
|
|
27
27
|
FROM
|
|
28
28
|
pg_catalog.pg_attrdef d
|
|
@@ -30,7 +30,7 @@ SELECT
|
|
|
30
30
|
d.adrelid = a.attrelid
|
|
31
31
|
AND d.adnum = a.attnum
|
|
32
32
|
AND a.atthasdef
|
|
33
|
-
) AS
|
|
33
|
+
) AS defaultval
|
|
34
34
|
FROM
|
|
35
35
|
pg_catalog.pg_attribute a
|
|
36
36
|
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
|
@@ -46,8 +46,8 @@ SELECT
|
|
|
46
46
|
c2.relname AS name,
|
|
47
47
|
i.indisprimary AS isprimary,
|
|
48
48
|
i.indisunique AS isunique,
|
|
49
|
-
pg_catalog.pg_get_indexdef (i.indexrelid, 0,
|
|
50
|
-
pg_catalog.pg_get_constraintdef (con.oid,
|
|
49
|
+
pg_catalog.pg_get_indexdef (i.indexrelid, 0, TRUE) AS indexdef,
|
|
50
|
+
pg_catalog.pg_get_constraintdef (con.oid, TRUE) AS constraintdef,
|
|
51
51
|
contype
|
|
52
52
|
FROM
|
|
53
53
|
pg_catalog.pg_class c,
|
|
@@ -65,13 +65,13 @@ WHERE
|
|
|
65
65
|
ORDER BY
|
|
66
66
|
i.indisprimary DESC,
|
|
67
67
|
i.indisunique DESC,
|
|
68
|
-
c2.relname;
|
|
68
|
+
c2.relname ASC;
|
|
69
69
|
|
|
70
70
|
-- BLOCK get_references_for_table
|
|
71
71
|
SELECT
|
|
72
72
|
conname AS name,
|
|
73
73
|
conrelid::pg_catalog.regclass AS table,
|
|
74
|
-
pg_catalog.pg_get_constraintdef (c.oid,
|
|
74
|
+
pg_catalog.pg_get_constraintdef (c.oid, TRUE) AS condef
|
|
75
75
|
FROM
|
|
76
76
|
pg_catalog.pg_constraint c
|
|
77
77
|
WHERE
|
|
@@ -83,26 +83,26 @@ ORDER BY
|
|
|
83
83
|
-- BLOCK get_foreign_key_constraints_for_table
|
|
84
84
|
SELECT
|
|
85
85
|
conname AS name,
|
|
86
|
-
pg_catalog.pg_get_constraintdef (r.oid,
|
|
86
|
+
pg_catalog.pg_get_constraintdef (r.oid, TRUE) AS def
|
|
87
87
|
FROM
|
|
88
88
|
pg_catalog.pg_constraint r
|
|
89
89
|
WHERE
|
|
90
90
|
r.conrelid = $oid
|
|
91
91
|
AND r.contype = 'f'
|
|
92
92
|
ORDER BY
|
|
93
|
-
|
|
93
|
+
name;
|
|
94
94
|
|
|
95
95
|
-- BLOCK get_check_constraints_for_table
|
|
96
96
|
SELECT
|
|
97
97
|
conname AS name,
|
|
98
|
-
pg_catalog.pg_get_constraintdef (r.oid,
|
|
98
|
+
pg_catalog.pg_get_constraintdef (r.oid, TRUE) AS def
|
|
99
99
|
FROM
|
|
100
100
|
pg_catalog.pg_constraint r
|
|
101
101
|
WHERE
|
|
102
102
|
r.conrelid = $oid
|
|
103
103
|
AND r.contype = 'c'
|
|
104
104
|
ORDER BY
|
|
105
|
-
|
|
105
|
+
name;
|
|
106
106
|
|
|
107
107
|
-- BLOCK get_enums
|
|
108
108
|
SELECT
|
package/src/describe.ts
CHANGED
|
@@ -8,8 +8,8 @@ const sql = loadSqlEquiv(import.meta.url);
|
|
|
8
8
|
interface ColumnDescription {
|
|
9
9
|
name: string;
|
|
10
10
|
type: string;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
isnotnull: boolean;
|
|
12
|
+
defaultval: any;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface IndexDescription {
|
|
@@ -71,7 +71,7 @@ async function describeWithPool(
|
|
|
71
71
|
|
|
72
72
|
// Get the names of the tables and filter out any ignored tables
|
|
73
73
|
const tablesRes = await pool.queryAsync(sql.get_tables, []);
|
|
74
|
-
const tables = tablesRes.rows.filter((table) => ignoreTables.
|
|
74
|
+
const tables = tablesRes.rows.filter((table) => !ignoreTables.includes(table.name));
|
|
75
75
|
|
|
76
76
|
// Transform ignored columns into a map from table names to arrays
|
|
77
77
|
// of column names
|
|
@@ -102,7 +102,7 @@ async function describeWithPool(
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
const columns = columnResults.rows.filter((row) => {
|
|
105
|
-
return (ignoreColumns[table.name] || []).
|
|
105
|
+
return !(ignoreColumns[table.name] || []).includes(row.name);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
const indexResults = await pool.queryAsync(sql.get_indexes_for_table, {
|
|
@@ -120,7 +120,7 @@ async function describeWithPool(
|
|
|
120
120
|
|
|
121
121
|
// Filter out references from ignored tables
|
|
122
122
|
const references = referenceResults.rows.filter((row) => {
|
|
123
|
-
return ignoreTables.
|
|
123
|
+
return !ignoreTables.includes(row.table);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
const checkConstraintResults = await pool.queryAsync(sql.get_check_constraints_for_table, {
|
|
@@ -141,7 +141,7 @@ async function describeWithPool(
|
|
|
141
141
|
|
|
142
142
|
// Filter ignored enums
|
|
143
143
|
const rows = enumsRes.rows.filter((row) => {
|
|
144
|
-
return ignoreEnums.
|
|
144
|
+
return !ignoreEnums.includes(row.name);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
rows.forEach((row) => {
|
|
@@ -209,11 +209,11 @@ export function formatDatabaseDescription(
|
|
|
209
209
|
.map((row) => {
|
|
210
210
|
let rowText = formatText(` ${row.name}`, chalk.bold);
|
|
211
211
|
rowText += ':' + formatText(` ${row.type}`, chalk.green);
|
|
212
|
-
if (row.
|
|
212
|
+
if (row.isnotnull) {
|
|
213
213
|
rowText += formatText(' not null', chalk.gray);
|
|
214
214
|
}
|
|
215
|
-
if (row.
|
|
216
|
-
rowText += formatText(` default ${row.
|
|
215
|
+
if (row.defaultval) {
|
|
216
|
+
rowText += formatText(` default ${row.defaultval}`, chalk.gray);
|
|
217
217
|
}
|
|
218
218
|
return rowText;
|
|
219
219
|
})
|
|
@@ -221,18 +221,18 @@ export function formatDatabaseDescription(
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
if (table.indexes.length > 0) {
|
|
224
|
-
if (output.tables[tableName].length
|
|
224
|
+
if (output.tables[tableName].length > 0) {
|
|
225
225
|
output.tables[tableName] += '\n\n';
|
|
226
226
|
}
|
|
227
227
|
output.tables[tableName] += formatText('indexes\n', chalk.underline);
|
|
228
228
|
output.tables[tableName] += table.indexes
|
|
229
229
|
.map((row) => {
|
|
230
|
-
const using = row.indexdef.
|
|
230
|
+
const using = row.indexdef.slice(Math.max(0, row.indexdef.indexOf('USING ')));
|
|
231
231
|
let rowText = formatText(` ${row.name}`, chalk.bold) + ':';
|
|
232
232
|
// Primary indexes are implicitly unique, so we don't need to
|
|
233
233
|
// capture that explicitly.
|
|
234
234
|
if (row.isunique && !row.isprimary) {
|
|
235
|
-
if (!row.constraintdef || row.constraintdef.
|
|
235
|
+
if (!row.constraintdef || !row.constraintdef.includes('UNIQUE')) {
|
|
236
236
|
// Some unique indexes don't include the UNIQUE constraint
|
|
237
237
|
// as part of the constraint definition, so we need to capture
|
|
238
238
|
// that manually.
|
|
@@ -247,7 +247,7 @@ export function formatDatabaseDescription(
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
if (table.checkConstraints.length > 0) {
|
|
250
|
-
if (output.tables[tableName].length
|
|
250
|
+
if (output.tables[tableName].length > 0) {
|
|
251
251
|
output.tables[tableName] += '\n\n';
|
|
252
252
|
}
|
|
253
253
|
output.tables[tableName] += formatText('check constraints\n', chalk.underline);
|
|
@@ -262,7 +262,7 @@ export function formatDatabaseDescription(
|
|
|
262
262
|
//
|
|
263
263
|
// The second replace handles all other lines: we want to collapse
|
|
264
264
|
// all leading whitespace into a single space.
|
|
265
|
-
const def = row.def.
|
|
265
|
+
const def = row.def.replaceAll('(\n', '(').replaceAll(/\n\s*/g, ' ');
|
|
266
266
|
|
|
267
267
|
let rowText = formatText(` ${row.name}:`, chalk.bold);
|
|
268
268
|
rowText += formatText(` ${def}`, chalk.green);
|
|
@@ -272,7 +272,7 @@ export function formatDatabaseDescription(
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
if (table.foreignKeyConstraints.length > 0) {
|
|
275
|
-
if (output.tables[tableName].length
|
|
275
|
+
if (output.tables[tableName].length > 0) {
|
|
276
276
|
output.tables[tableName] += '\n\n';
|
|
277
277
|
}
|
|
278
278
|
output.tables[tableName] += formatText('foreign-key constraints\n', chalk.underline);
|
|
@@ -286,7 +286,7 @@ export function formatDatabaseDescription(
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
if (table.references.length > 0) {
|
|
289
|
-
if (output.tables[tableName].length
|
|
289
|
+
if (output.tables[tableName].length > 0) {
|
|
290
290
|
output.tables[tableName] += '\n\n';
|
|
291
291
|
}
|
|
292
292
|
output.tables[tableName] += formatText('referenced by\n', chalk.underline);
|