nodejs-mw-select-builder/select-builder-oracle.js

236 lines
6.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Простенький "селект билдер" по мотивам MediaWiki-овского, успешно юзаю подобный в PHP уже лет 8
// (c) Виталий Филиппов, 2018
// В PHP, правда, прикольнее - там в массиве можно смешивать строковые и численные ключи,
// благодаря чему можно писать $where = [ 't1.a=t2.a', 't2.b' => [ 1, 2, 3 ] ]
const MS_HASH = 0;
const MS_LIST = 1;
const MS_ROW = 2;
const MS_COL = 4;
const MS_VALUE = 6;
function limitOffset(sql, limit, offset)
{
if (!limit && !offset)
return sql;
const w = [];
if (limit)
w.push('rownum <= '+((limit|0)+(offset|0)));
if (offset)
w.push('rownum >= '+(1+(offset|0)));
return 'select * from ('+sql+') t where '+w.join(' and ');
}
function hashResult(res)
{
const keys = res.metaData.map(m => m.name.toLowerCase());
const hashes = [];
for (const row of res.rows)
{
const h = {};
keys.map((k, i) => h[k] = row[i]);
hashes.push(h);
}
return hashes;
}
function selectBuilder(tables, fields, where, options)
{
let sql = 'SELECT ', bind = [];
if (fields instanceof Array)
{
sql += fields.join(', ');
}
else if (typeof fields == 'string')
{
sql += fields;
}
else if (typeof fields == 'object')
{
sql += Object.keys(fields).map(k => fields[k]+' AS '+k).join(', ');
}
else
{
throw new Error('fields = '+fields+' is invalid');
}
sql += ' FROM ';
let first = true;
let moreWhere = null;
tables = typeof tables == 'string' ? { t: tables } : tables;
for (const k in tables)
{
if (first)
{
if (typeof tables[k] != 'string')
{
// Бывает удобно указывать WHERE как условие "JOIN" первой таблицы
sql += tables[k][1] + ' ' + k;
moreWhere = tables[k][2];
}
else
{
sql += tables[k] + ' ' + k;
}
first = false;
}
else if (typeof tables[k] == 'string')
{
sql += ' INNER JOIN '+tables[k]+' '+k+' ON 1=1';
}
else
{
const on = whereBuilder(tables[k][2]);
sql += ' ' + tables[k][0].toUpperCase() + ' JOIN ' + tables[k][1] + ' ' + k + ' ON ' + (on[0] || '1=1');
bind.push.apply(bind, on[1]);
}
}
const w = whereBuilder(where);
sql += ' WHERE '+(w[0] || '1=1');
bind.push.apply(bind, w[1]);
if (moreWhere)
{
moreWhere = whereBuilder(moreWhere);
if (moreWhere[0])
{
sql += ' AND '+moreWhere[0];
bind.push.apply(bind, moreWhere[1]);
}
}
options = options||{};
if (options['GROUP BY'] || options.group_by)
{
let group = options['GROUP BY'] || options.group_by;
group = group instanceof Array ? group : [ group ];
sql += ' GROUP BY '+group.join(', ');
}
if (options['ORDER BY'] || options.order_by)
{
let order = options['ORDER BY'] || options.order_by;
order = order instanceof Array ? order : [ order ];
sql += ' ORDER BY '+order.join(', ');
}
if (options.LIMIT || options.OFFSET || options.limit || options.offset)
{
sql = limitOffset(sql, options.LIMIT || options.limit, options.OFFSET || options.offset);
}
return [ sql, bind ];
}
function whereOrSetBuilder(fields, where)
{
if (typeof fields == 'string')
return [ fields, [] ];
const w = [], bind = [];
for (const k in fields)
{
let v = fields[k];
if (k.indexOf('?') >= 0)
{
if (!(v instanceof Array))
v = [ v ];
w.push(k);
bind.push.apply(bind, v);
}
else if (/^\d+$/.exec(k))
{
if (v instanceof Array)
{
w.push(v[0]);
bind.push.apply(bind, v.slice(1));
}
else
{
w.push(v);
}
}
else if (v != null || v instanceof Array && v.length)
{
v = v instanceof Array ? v : [ v ];
w.push(v.length == 1 ? k + ' = ?' : k + ' in (' + v.map(() => '?').join(', ') + ')');
bind.push.apply(bind, v);
}
}
if (!where)
return [ w.join(', '), bind ];
return [ w.length ? '('+w.join(') and (')+')' : '', bind ];
}
function whereBuilder(where)
{
return whereOrSetBuilder(where, true);
}
function _positional(sql)
{
let i = 0;
sql = sql.replace(/\?/g, () => ':'+(++i));
return sql;
}
async function select(dbh, tables, fields, where, options, format)
{
let [ sql, bind ] = selectBuilder(tables, fields, where, options);
let data = await dbh.execute(_positional(sql), bind);
if ((format & MS_LIST) || (format & MS_COL))
data = data.rows;
else
data = hashResult(data);
if (format & MS_ROW)
data = data[0];
if (data && (format & MS_COL))
data = data[0];
return data;
}
async function insert(dbh, table, rows)
{
if (!(rows instanceof Array))
{
rows = [ rows ];
}
if (!rows.length)
{
return null;
}
const keys = Object.keys(rows[0]);
let sql = 'insert into '+table+' ('+keys.join(', ')+') ';
const bind = [];
let i = 0;
for (const row of rows)
{
sql += (i > 0 ? ' union all ' : '')+' select'+keys.map(() => ':'+(++i)).join(', ')+' from dual';
bind.push.apply(bind, keys.map(k => row[k]));
}
return await dbh.execute(sql, bind);
}
async function _delete(dbh, table, where)
{
const w = whereBuilder(where);
const sql = 'DELETE FROM '+table+' WHERE '+(w[0] || '1=1');
return await dbh.execute(_positional(sql), w[1]);
}
async function update(dbh, table, set, where)
{
set = whereOrSetBuilder(set, false);
where = whereOrSetBuilder(where, true)
const sql = 'UPDATE '+table+' SET '+set[0]+' WHERE '+(where[0] || '1=1');
const bind = [ ...set[1], ...where[1] ];
return await dbh.execute(_positional(sql), bind);
}
module.exports = {
select,
insert,
delete: _delete,
update,
MS_HASH,
MS_LIST,
MS_ROW,
MS_COL,
MS_VALUE,
hashResult,
};