pj
Version:
Create SQL strings for PostgreSQL by interfacing with a javascript API
326 lines (244 loc) • 5.95 kB
JavaScript
// third-party modules
import classer from 'classer';
import pg from 'pg';
// local classes
/**
* static:
**/
// connection string regex parser
// const R_CONNECT = /^\s*(?:(?:(?:(\w+):\/\/)?(\w+)(?::([^@]+))?@)?(\w+)?\/)?(\w+)(\?.+)\s*$/;
const R_CONNECT = /^\s*([\w\-]+)?(:[^@]+)?@([^:\/?\s]+)?(:\d+)?\/([^?\s]+)\s*/;
// connection defaults
const H_CONNECT_DEFAULTS = {
protocol: 'postgres',
user: 'root',
host: 'localhost',
};
// connect to database using string
const connect_string = (s_connect) => {
// parse connection string
var m_connect = R_CONNECT.exec(s_connect);
// invalid connection string
if(!m_connect) return local.fail(`invalid connection string: "${s_connect}"`);
// construct full postgres connection string
return ''
// protocol
+(false || H_CONNECT_DEFAULTS.protocol)+'://'
// user
+(m_connect[1] || H_CONNECT_DEFAULTS.user)
// password
+(m_connect[2]? m_connect[2]: '')+'@'
// host
+(m_connect[3] || H_CONNECT_DEFAULTS.host)
// port
+(m_connect[4]? m_connect[4]: '')+'/'
// database
+m_connect[5];
};
// escape string literal
const escape_literal = (s_value) => {
return s_value
.replace(/'/g, '\'\'')
.replace(/\t/g, '\\t')
.replace(/\n/g, '\\n');
};
//
const valuify = (z_value) => {
switch(typeof z_value) {
case 'string':
return `'${escape_literal(z_value)}'`;
case 'number':
return z_value;
case 'boolean':
return z_value? 'TRUE': 'FALSE';
case 'object':
// null
if(null === z_value) {
return null;
}
// raw sql
else if('string' === typeof z_value.raw) {
return z_value.raw;
}
// default
return escape_literal(
JSON.stringify(z_value)
);
case 'function':
return z_value()+'';
default:
throw 'unable to convert into safe value: "${z_value}"';
}
};
//
const H_WRITERS = {
// convert hash query to string query
insert(h_query) {
// ref insert list
let a_inserts = h_query.insert;
// prep list of rows that have been observed from first element
let a_keys = Object.keys(a_inserts[0]);
// build columns part of sql string
let s_keys = a_keys.map(s_key => `"${s_key}"`).join(',');
// build values part of sql string
let a_rows = [];
// each insert row
a_inserts.forEach((h_row) => {
// list of values to insert for this row
let a_values = [];
// each key-value pair in row
for(let s_key in h_row) {
// key is missing from accepted values section
if(-1 === a_keys.indexOf(s_key)) {
return local.fail('new key "${s_key}" introduced after first element in insert chain');
}
// append to values
a_values.push(valuify(h_row[s_key]));
}
// push row to values list
a_rows.push(`(${a_values.join(',')})`);
});
//
let s_tail = '';
//
if(h_query.conflict_target && h_query.conflict_action) {
s_tail += `on conflict ${h_query.conflict_target} ${h_query.conflict_action}`;
}
// prep sql query string
return `insert into "${h_query.into}" (${s_keys}) values ${a_rows.join(',')} ${s_tail}`;
},
};
/**
* class:
**/
const local = classer('pj', function(z_config) {
//
let a_queue = [];
// connection string
let s_connection = (() => {
// setup postgres connection
switch(typeof z_config) {
// config given as string
case 'string':
// connection string
return connect_string(z_config);
}
return false;
})();
//
if(!s_connection) return local.fail('failed to understand connection config argument');
//
local.info(`connecting to postgres w/ ${s_connection}`);
// postgres client
let y_client = new pg.Client(s_connection);
// initiate connection
y_client.connect((e_connect) => {
// connection error
if(e_connect) {
local.fail('failed to connect');
}
//
next_query();
});
//
const next_query = () => {
// queue is not empty
if(a_queue.length) {
// shift first query from beginning
let h_query = a_queue.shift();
// execute query
y_client.query(h_query.sql, h_query.callback);
}
};
// submit a query to be executed
const submit_query = (s_query, f_okay) => {
// push to queue
a_queue.push({
sql: s_query,
callback: f_okay,
});
// queue was empty
if(1 === a_queue.length) {
// initiate
next_query();
}
};
// query-building for insertion
const qb_insert = (h_query) => {
// default insert hash
h_query.insert = h_query.insert || [];
//
const self = {
// insert rows
insert(z_values) {
// list of rows to insert simultaneously
if(Array.isArray(z_values)) {
// append to existing insertion list
h_query.insert.push(...z_values);
}
// values hash
else if('object' === typeof z_values) {
// single row to append to insertion list
h_query.insert.push(z_values);
}
// other type
else {
local.fail('invalid type for insertion argument');
}
// normal insert actions
return self;
},
// on conflict
on_conflict(s_target) {
// set conflict target
h_query.conflict_target = `(${s_target})`;
// next action hash
return {
// do nothing
do_nothing() {
// set conflict action
h_query.conflict_action = 'do nothing';
// normal insert actions
return self;
},
};
},
//
debug() {
// generate sql
let s_sql = H_WRITERS.insert(h_query);
debugger;
return self;
},
//
exec(f_okay) {
// generate sql
let s_sql = H_WRITERS.insert(h_query);
// submit
submit_query(s_sql, (e_insert, w_result) => {
// insert error
if(e_insert) {
local.fail(e_insert);
}
//
if('function' === typeof f_okay) {
f_okay(w_result);
}
});
},
};
//
return self;
};
//
return classer.operator(function() {
}, {
// start of an insert query
into(s_table) {
return qb_insert({
into: s_table,
});
},
});
});
export default local;