Last active
November 20, 2020 15:15
-
-
Save sytranvn/0c7e0fff32a0e6446738be1401424831 to your computer and use it in GitHub Desktop.
List Postgres views, mviews depend on some other objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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