devel/psql/psql.js (97 lines of code) (raw):

// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import process from "process"; const fs = require("fs"); import path from "path"; // *** Main *** main(); /** * Print usage and exit */ function usage() { console.log("psql.js [describe|sql] [table|file] [--format=json|table]") process.exit(1); } /** * Read a file and return content as string. * If file doesn't exists, process will exit in error * @param filename * @returns {Promise<string>} */ async function getFile(filename) { let fullFileName = ''; if (!path.isAbsolute(filename)) { fullFileName = [ process.env.OPS_PWD, filename ].join(path.sep); } else { fullFileName = filename; } const file = Bun.file(fullFileName); if (!await file.exists()) { throw Error(`File ${fullFileName} doesn't exists`); } return await file.text(); } async function main() { const auth = process.env.AUTHB64 const psqlAddr = `${process.env.APIHOST}/api/v1/web/whisk-system/nuv/psql`; // parse command line let command, param, format; command = String(Bun.argv[2]); if (Bun.argv[3] && Bun.argv[3].length > 0) { param = String(Bun.argv[3]); param = param.replace(/(\r\n|\n|\r)/gm, ""); } format = (Bun.argv.length === 5) ? Bun.argv[4] : Bun.argv[3]; if (!format) { format = 'json'; } if (['json', 'table'].indexOf(format) === -1) { console.log(`Unsupported output format ${format}`); usage(); } // build the command for system action const cmds = [] if ('describe' === command) { let tableInfo = param.split('.'); const schema = tableInfo.length > 1 ? tableInfo[0] : process.env.OPSDEV_USERNAME; const table = tableInfo.length > 1 ? tableInfo[1] : tableInfo[0]; let cmd = {} cmd['command'] = `SELECT table_catalog, table_schema, column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = '${schema}' and table_name='${table}'`; cmds.push(cmd); } if ('sql' === command) { // process data from stdin (if any) const isPipe = fs.fstatSync(0).isFIFO(); let sql = ''; if (isPipe) { for await (const chunk of Bun.stdin.stream()) { // chunk is Uint8Array - this converts it to text (assumes ASCII encoding) sql += Buffer.from(chunk).toString(); } } else { sql = await getFile(param); } const queries = sql .split(/;\s*(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/) .map(query => query.trim()) .filter(query => query.length > 0); for (const query of queries) { let cmd = {} cmd['command'] = query; cmds.push(cmd); } } // call the system action try { for (const cmd of cmds) { const response = await fetch(`${psqlAddr}`, { method: "POST", body: JSON.stringify(cmd), headers: {'x-impersonate-auth': `${auth}`}, }); // format the output let outputData = ''; const contentType = response.headers.get('Content-Type'); const isJson = contentType && contentType.includes('application/json') if (isJson) { outputData = await response.json(); } else { outputData = await response.text(); } if (format === 'table' && isJson && outputData.length > 0) { console.log(Bun.inspect.table(outputData)); } else { console.log(outputData); } } } catch (err) { console.error(`[ERROR]: ${err.message}`); } }