Skip to content

Instantly share code, notes, and snippets.

@sytranvn
Last active November 20, 2020 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sytranvn/0c7e0fff32a0e6446738be1401424831 to your computer and use it in GitHub Desktop.
Save sytranvn/0c7e0fff32a0e6446738be1401424831 to your computer and use it in GitHub Desktop.
List Postgres views, mviews depend on some other objects
#!/usr/bin/env node
/**
*******************************
* Copyright 2020 sytranvn *
* License: MIT License *
*******************************
*/
const { exit } = require('process')
const {promises } = require('fs')
const { readdir, readFile } = promises
const help = `Usage: node this-script.js <path> [<change object>,...]
Read object dependencies from path/tables, path/views, path/mviews and output changed ones.
*******************************
* Copyright 2020 sytranvn *
* License: MIT License *
*******************************
`
let { path: schemaPath, changes, bailout = '' } = parseArgs()
if (!schemaPath || !changes) {
console.error(help)
console.log(parseArgs())
exit(1)
}
changes = changes.split(',').map(d => d.trim()).filter(Boolean)
const schema = schemaPath.slice(schemaPath.lastIndexOf('/') + 1)
let tables
let views
let mviews
let objects
let graph = {}
let q = [...changes]
let done = false
let bailedouts = []
async function init() {
tables = await getObjects(`${schemaPath}/tables`)
views = await getObjects(`${schemaPath}/views`)
mviews = await getObjects(`${schemaPath}/mviews`)
objects = await [...tables, ...views, mviews]
}
async function getObjects (path) {
let objects
try {
objects = await readdir(path)
} catch {
return []
}
const data = await objects.map(o => o.slice(`${schema}.`.length, o.lastIndexOf('.'))) // remove schema. and.sql from file name
return data
}
function buildDeps (object, sql) {
if (bailout && (new RegExp(bailout, 'im')).test(sql)) {
bailedouts.push(object)
return
}
const deps = sql.match(/(FROM|JOIN)[^\w]+(\w+)/img) // table after FROM, JOIN token
.filter(m => m && !m.match(/(FROM|JOIN)[^\w]+SELECT/)) // but not subquery
.map(m => m.match(/(FROM|JOIN)[^\w]+(\w+)/im)[2]) // extract object name
.map(m => m.slice())
for (dep of deps) {
if (graph[dep])
graph[dep].push(object) // make edge from dep to object
else
graph[dep] = [object]
}
}
async function makeGraph () {
for (object of [...views]) {
const sql = await readFile(`${schemaPath}/views/${schema}.${object}.sql`, 'utf8')
await buildDeps(object, sql)
}
for (object of [...mviews]) {
const sql = await readFile(`${schemaPath}/mviews/${schema}.${object}.sql`, 'utf8')
await buildDeps(object, sql)
}
}
function dfs() {
while (q.length) {
cur = q.pop()
for (dep of graph[cur] || []) {
if (!changes.includes(dep)) {
changes.push(dep)
q.push(dep)
dfs(dep)
}
}
}
}
async function main () {
await init()
await makeGraph()
await dfs()
await spinner.stop()
// TODO: Toposort changed objects
await console.log(`Changed objects:
- ${changes.join('\n - ')}`)
if (bailedouts.length) {
console.log(`Bailed out dblink objects:
- ${bailedouts.join('\n -')}`)
}
}
try {
main()
} catch {
spinner.stop()
exit(1)
}
/**
* Spinner by https://github.com/ianmcodes/node-simple-spinner
*/
var cursor //= require('ansi')(process.stdout);
var spinner = (function() {
var sequence = ["|","/","-","\\"]; //[".", "o", "0", "@", "*"];
var index = 0;
var timer;
var opts = {};
function start(inv, options) {
options = options || {};
opts = options;
if(options.hideCursor) {
cursor.hide();
}
inv = inv || 250;
index = 0;
process.stdout.write(sequence[index]);
timer = setInterval(function() {
process.stdout.write(sequence[index].replace(/./g,"\b"));
index = (index < sequence.length - 1) ? index + 1 : 0;
process.stdout.write(sequence[index]);
},inv);
if(options.doNotBlock) {
timer.unref();
}
}
function stop() {
clearInterval(timer);
if(opts.hideCursor) {
cursor.show();
}
process.stdout.write(sequence[index].replace(/./g,"\b"));
}
function change_sequence(seq) {
if(Array.isArray(seq)) {
sequence = seq;
}
}
return {
start: start,
stop: stop,
change_sequence: change_sequence
};
})();
spinner.start({ doNotBlock: true })
function parseArgs() {
const args = {}
for (arg of process.argv) {
const key = arg.slice(2, arg.indexOf('='))
if (key) {
args[key] = arg.slice(arg.indexOf('=') + 1)
}
}
return args
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment