Implement compound pk support

This commit is contained in:
simov
2014-06-02 11:16:12 +03:00
parent 5bceb07bbd
commit e558967ba7
11 changed files with 288 additions and 167 deletions
+3 -2
View File
@@ -106,12 +106,13 @@ function createColumn (name, info) {
*/
function primaryKey (columns) {
var pk = [];
for (var name in columns) {
for (var property in columns[name]) {
if (columns[name][property] === 'pri') {
return name;
pk.push(name);
}
}
}
return '';
return !pk.length ? '' : (pk.length > 1 ? pk : pk[0]);
}
+53 -17
View File
@@ -47,19 +47,22 @@ exports.update = function (args, cb) {
function updateRecord (args, settings, record, cb) {
if (args.type !== 'view') {
var fk = args.settings[args.name].editview[args.type][settings.table.name];
record.columns[fk] = args.id;
fk = fk instanceof Array ? fk : [fk];
for (var i=0; i < fk.length; i++) {
record.columns[fk[i]] = args.id[i];
}
}
if (record.insert) {
var data = kvp(args.name, record.columns, settings.columns),
str = query.replace('insert',
settings.table.name, data.keys.join(), data.values.join(), settings.table.pk);
settings.table.name, data.keys.join(), data.values.join(), sql.list('',settings.table.pk));
var action = 'insert';
} else if (record.remove || args.action == 'remove') {
var pk = settings.table.pk;
str = query.replace('delete', settings.table.name, pk, record.pk);
str = query.replace('delete', settings.table.name, sql.andValues(settings.table.name, pk, record.pk));
var action = 'remove';
@@ -67,7 +70,7 @@ function updateRecord (args, settings, record, cb) {
var pk = settings.table.pk,
data = kvp(args.name, record.columns, settings.columns),
str = query.replace('update',
settings.table.name, data.pairs.join(), pk, record.pk);
settings.table.name, data.pairs.join(), sql.andValues(settings.table.name, pk, record.pk));
var action = 'update';
}
@@ -97,15 +100,29 @@ function execute (action, args, str, record, settings, cb) {
return cb();
}
args.log && console.log('tbl'.green, str);
// insert or update
if (action != 'remove') {
args.db.client.query(str, function (err, result) {
if (err) return cb(err);
if (action == 'insert') {
record.pk = result.insertId;
var pks = settings.table.pk instanceof Array ? settings.table.pk : [settings.table.pk],
columns = record.columns,
values = [];
for (var i=0; i < pks.length; i++) {
if (columns[pks[i]] === undefined) {
values.push(result.insertId);
} else {
values.push(columns[pks[i]]);
}
}
record.pk = values;
if (args.type == 'view' && args.action == 'insert')
args.id = result.insertId;
args.id = values;
}
var queries = manyToMany(action, args, record, settings.columns);
@@ -131,6 +148,7 @@ function execute (action, args, str, record, settings, cb) {
function spin (args, queries, cb) {
(function loop (i) {
if (i == queries.length) return cb();
args.log && console.log('mtm'.green, queries[i]);
args.db.client.query(queries[i], function (err) {
if (err) return cb(err);
loop(++i);
@@ -149,6 +167,25 @@ function kvp (table, data, columns) {
var name = columns[i].name;
if (!{}.hasOwnProperty.call(data, name)) continue;
if (columns[i].oneToMany && columns[i].fk) {
var _values = data[name].split(',');
for (var j=0; j < columns[i].fk.length; j++) {
var _name = columns[i].fk[j];
var just = sql.justName(_name);
keys.push(just);
for (var k=0; k < columns.length; k++) {
if (columns[k].name == _name) {
var value = escape(_values[j], columns[k].type);
break;
}
}
values.push(value);
pairs.push(just+'='+value);
}
continue;
}
var just = sql.justName(name);
keys.push(just);
@@ -215,14 +252,12 @@ function manyToMany (action, args, record, columns) {
function manyInsert (ids, link, record) {
if (!ids.length) return null;
var values = [];
for (var i=0; i < ids.length; i++) {
values.push('('+record.pk+','+ids[i]+')');
}
var parentPk = link.parentPk instanceof Array ? link.parentPk : [link.parentPk],
childPk = link.childPk instanceof Array ? link.childPk : [link.childPk];
var str = query.replace('insert-many',
link.table, [sql.justName(link.parentPk), sql.justName(link.childPk)].join(),
values.join());
var str = query.replace('insert-many', link.table,
sql.list('', parentPk.concat(childPk)),
sql.values(record.pk, ids));
return str;
}
@@ -230,18 +265,19 @@ function manyInsert (ids, link, record) {
function manyDelete (ids, link, record) {
if (!ids.length) return null;
var str = query.replace('delete-many',
link.table, link.parentPk, record.pk, link.childPk, ids.join());
var str = query.replace('delete-many', link.table,
sql.andValues('', link.parentPk, record.pk),
sql.in('', link.childPk, ids));
return str;
}
function difference (source, target) {
for (var i=0; i < source.length; i++) {
source[i] = parseInt(source[i],10);
source[i] = source[i].toString();
}
for (var i=0; i < target.length; i++) {
target[i] = parseInt(target[i],10);
target[i] = target[i].toString();
}
return source.filter(function (i) {
return !(target.indexOf(i) > -1);
+15 -11
View File
@@ -6,11 +6,10 @@ var query = require('../sql/query'),
// modifies `args.config.columns` with `value`
var otm = {
_getRef: function (args, ref, cb) {
var columns = sql.fullNames(ref.table, ref.columns);
var pk = sql.pk(ref.table, ref.pk),
text = sql.concat(sql.cast(columns), '__text');
var pk = sql.as(ref.table, ref.pk, '__pk'),
text = sql.castConcat(ref.table, ref.columns, '__text');
var str = query.replace('get-records', [pk,text].join(), ref.table);
args.log && console.log('otm'.cyan, str);
args.db.client.query(str, function (err, rows) {
if (err) return cb(err);
cb(null, rows);
@@ -74,14 +73,16 @@ var tbl = {
var names = [];
for (var i=0; i < columns.length; i++) {
if (!columns[i].manyToMany) names.push(columns[i].name);
if (columns[i].oneToMany && columns[i].fk) {
names.push(sql.as(table.name, columns[i].fk, columns[i].name));
}
else if (!(columns[i].oneToMany && columns[i].fk) && !columns[i].manyToMany)
names.push(sql.fullName(table.name, columns[i].name));
}
names = sql.fullNames(table.name, names);
names.unshift(sql.pk(table.name, table.pk));
names.unshift(sql.as(table.name, table.pk, '__pk'));
return query.replace('get-record',
names.join(), table.name, (args.fk? args.fk:table.pk), args.id);
names.join(), table.name, sql.andValues(table.name, (args.fk? args.fk:table.pk), args.id));
},
// public - add docs
get: function (args, cb) {
@@ -92,6 +93,7 @@ var tbl = {
}
var str = tbl._getSql(args);
args.log && console.log('tbl'.cyan, str);
args.db.client.query(str, function (err, rows) {
if (err) return cb(err);
var result = [];
@@ -110,12 +112,14 @@ var mtm = {
_getIds: function (column, pk, args, cb) {
var link = column.manyToMany.link;
var str = query.replace('get-record',
sql.fullName(link.table, link.childPk), link.table, link.parentPk, pk);
sql.concat(link.table, link.childPk), link.table, sql.andValues(link.table, link.parentPk, pk));
args.log && console.log('mtm'.cyan, str);
args.db.client.query(str, function (err, rows) {
if (err) return cb(err);
var result = [];
for (var i=0; i < rows.length; i++) {
result.push(rows[i][link.childPk]);
// result.push(rows[i][link.childPk]);
result.push(rows[i][Object.keys(rows[i])[0]]);
}
cb(err, result);
});
+3 -3
View File
@@ -49,7 +49,7 @@ function setActiveSingle (column, value) {
if (!column.value) return value;
var rows = column.value;
for (var i=0; i < rows.length; i++) {
if (parseInt(rows[i]['__pk']) === parseInt(value)) {
if (rows[i]['__pk'] == value) {
rows[i].selected = true;
return rows;
}
@@ -61,10 +61,10 @@ function setActiveMultiple (column, value) {
if (!column.value) return value;
var rows = column.value;
for (var i=0; i < rows.length; i++) {
var pk = parseInt(rows[i]['__pk']);
var pk = rows[i]['__pk'];
value = (value instanceof Array) ? value : [value];
for (var j=0; j < value.length; j++) {
if (pk === parseInt(value[j])) {
if (pk == value[j]) {
rows[i].selected = true;
break;
}
+1
View File
@@ -14,6 +14,7 @@ exports = module.exports = function (args, cb) {
}
columns = visible;
args.log && console.log('list'.cyan, args.query);
args.db.client.query(args.query, function (err, rows) {
if (err) return cb(err);
var records = [];
+4 -3
View File
@@ -5,10 +5,11 @@ var query = require('../sql/query'),
exports.get = function (args, cb) {
var s = args.statements,
pk = sql.fullName(args.config.table.name, args.config.table.pk);
var table = args.config.table,
s = args.statements;
var str = query.replace('total', pk, s.table, s.join, s.where);
var str = query.replace('total', sql.concat(table.name, table.pk), s.table, s.join, s.where);
args.log && console.log('list'.cyan, str);
args.db.client.query(str, function (err, rows) {
if (err) return cb(err);
var total = parseInt(rows[0].count),
+37 -21
View File
@@ -13,16 +13,19 @@ exports = module.exports = function (args) {
for (var i=0; i < columns.length; i++) {
if (!columns[i].listview.show) continue;
if (columns[i].oneToMany) {
var ref = columns[i].oneToMany,
cols = sql.fullNames(ref.table, ref.columns);
names.push(sql.group(sql.cast(cols), columns[i].name));
joins.push(sql.joins(table.name, columns[i].name, ref.table, ref.pk));
var ref = columns[i].oneToMany;
names.push(sql.group(ref.table, ref.columns, columns[i].name));
if (columns[i].fk) {
joins.push(sql.joins(table.name, columns[i].fk, ref.table, ref.pk));
} else {
joins.push(sql.joins(table.name, columns[i].name, ref.table, ref.pk));
}
}
else if (columns[i].manyToMany) {
var ref = columns[i].manyToMany.ref,
cols = sql.fullNames(ref.table, ref.columns);
names.push(sql.group(sql.cast(cols), columns[i].name));
var ref = columns[i].manyToMany.ref;
names.push(sql.group(ref.table, ref.columns, columns[i].name));
var link = columns[i].manyToMany.link;
joins.push(sql.joins(table.name, table.pk, link.table, link.parentPk));
@@ -32,18 +35,18 @@ exports = module.exports = function (args) {
names.push(sql.fullName(table.name, columns[i].name));
}
}
names.unshift(sql.pk(table.name, table.pk));
names.unshift(sql.as(table.name, table.pk, '__pk'));
var where = statement(table, columns, args.filter);
// always group by pk inside the listview!
var group = sql.groupby(sql.fullName(table.name, table.pk));
var group = sql.groupby(table.name, table.pk);
var order = '';
if (args.filter.order && args.filter.order != 'NULL') {
for (var i=0; i < columns.length; i++) {
if (columns[i].name == args.filter.order) {
var direction = args.filter.direction || 'asc';
var direction = args.filter.direction || 'ASC';
var column = (columns[i].oneToMany || columns[i].manyToMany)
? args.filter.order
: sql.fullName(table.name, args.filter.order);
@@ -53,7 +56,8 @@ exports = module.exports = function (args) {
}
}
else if (!Object.keys(view.listview.order).length) {
order = sql.fullName(table.name, table.pk) + ' asc';
var pk = table.pk instanceof Array ? table.pk[0] : table.pk;
order = sql.fullName(table.name, pk) + ' ASC';
}
else {
order = sql.order(table.name, view.listview.order).join();
@@ -77,16 +81,28 @@ function statement (table, columns, filter) {
var column = columns[i],
value = filter.columns[column.name];
if (!value) continue;
var clause = sql.where(column.control, value);
if (!clause) continue;
var name = '';
if (column.manyToMany) {
var ref = column.manyToMany.ref;
name = sql.fullName(ref.table, ref.pk);
} else {
name = sql.fullName(table.name, column.name);
if (column.oneToMany && column.fk) {
if (value == 'NULL') continue;
statements.push(sql.andValues(table.name, column.fk, value));
}
else if (column.manyToMany) {
var ref = column.manyToMany.ref;
statements.push(sql.in(ref.table, ref.pk, value));
}
else {
var clause = sql.where(column.control, value);
if (!clause) continue;
var name = '';
if (column.manyToMany) {
var ref = column.manyToMany.ref;
name = sql.fullName(ref.table, ref.pk);
} else {
name = sql.fullName(table.name, column.name);
}
statements.push([name, clause].join(' '));
}
statements.push([name, clause].join(' '));
}
return statements.length ? ' WHERE ' + statements.join(' AND ') : '';
}
+5 -5
View File
@@ -15,7 +15,7 @@ exports.sql = function (name) {
: "SELECT {0} FROM `{1}` {2} {3} {4} ORDER BY {5} LIMIT {7} OFFSET {6} ;";
break;
case 'get-record': s =
"SELECT {0} FROM `{1}` WHERE `{2}`='{3}' ;";
"SELECT {0} FROM `{1}` WHERE {2} ;";
break;
case 'get-records': s =
"SELECT {0} FROM `{1}` ;";
@@ -26,20 +26,20 @@ exports.sql = function (name) {
// edit
case 'insert': s = (this.client.mysql || this.client.sqlite)
? "INSERT INTO `{0}` ({1}) VALUES ({2}) ;"
: "INSERT INTO `{0}` ({1}) VALUES ({2}) RETURNING `{3}` ;";
: "INSERT INTO `{0}` ({1}) VALUES ({2}) RETURNING {3} ;";
break;
case 'update': s =
"UPDATE `{0}` SET {1} WHERE `{2}`='{3}' ;";
"UPDATE `{0}` SET {1} WHERE {2} ;";
break;
case 'delete': s =
"DELETE FROM `{0}` WHERE `{1}`='{2}' ;";
"DELETE FROM `{0}` WHERE {1} ;";
break;
// many to many
case 'insert-many': s =
"INSERT INTO `{0}` ({1}) VALUES {2} ;";
break;
case 'delete-many': s =
"DELETE FROM `{0}` WHERE `{1}`={2} AND `{3}` IN ({4}) ;";
"DELETE FROM `{0}` WHERE {1} AND {2} ;";
break;
+162 -101
View File
@@ -1,123 +1,161 @@
exports.client = {};
function quotes (result) {
return (exports.client.mysql || exports.client.sqlite)
? result : result.replace(/`/g,'"');
}
function escape (value) {
return "'"+value.replace(/('|")/g, '\\$1')+"'";
return (typeof value === 'string')
? "'"+value.replace(/('|")/g, '\\$1')+"'"
: value;
}
/**
* Quotes a `name`
*
* @param {String} name
* @return {String}
* @api public
*/
// `name`
exports.justName = function (name) {
var result = ['`',name,'`'].join('');
return quotes(result);
}
/**
* Convert table and column names into
* `tableName`.`columnName`
*
* @param {Array} columns
* @param {String} table
* @return {String}
* @api public
*/
// `table`.`column`
// if table == '' returns justName instead of fullName
exports.fullName = function (table, column) {
var result = ['`',table,'`.`',column,'`'].join('');
var result = !table
? this.justName(column)
: ['`',table,'`.`',column,'`'].join('');
return quotes(result);
}
/**
* Convert array of column names into
* `tableName`.`columnName`
*
* @param {String} table
* @param {Array} columns
* @return {Array} result
* @api public
*/
// ['`table.column1`', '`table.column2`']
exports.fullNames = function (table, columns) {
columns = columns instanceof Array ? columns : [columns];
var result = [];
for (var i=0; i < columns.length; i++) {
result.push(quotes(['`',table,'`.`',columns[i],'`'].join('')));
result.push(this.fullName(table, columns[i]));
}
return result;
}
/**
* Convert array of column names into
* CAST(columnName AS CHAR)
* @tip: pass in fullNames(columns, table) as a parameter
*
* @param {Array} columns
* @return {Array} result
* @api public
*/
exports.cast = function (columns) {
var result = [];
for (var i=0; i < columns.length; i++) {
this.client.mysql
? result.push(['CAST(',columns[i],' AS CHAR)'].join(''))
: result.push(columns[i]);
}
// select, order by, group by
// `table`.`column`
// `table`.`column1`,`table`.`column2`
exports.list = function (table, column) {
var result = (column instanceof Array)
? this.fullNames(table, column).join()
: this.fullName(table, column);
return result;
}
/**
* Concat array of columns and
* select them as name
* @tip: pass in cast(columns) and fk as parameters
*
* @param {Array} columns
* @param {String} name
* @return {String}
* @api public
*/
// `table1`.`column` = `table2`.`column`
// `table1`.`column` = `table2`.`column` AND
exports.andColumns = function (table, fk, refTable, pk) {
fk = fk instanceof Array ? fk : [fk];
pk = pk instanceof Array ? pk : [pk];
exports.concat = function (columns, name) {
var result = this.client.sqlite
? [columns.join('||\' \'||'),' AS `',name,'`'].join('')
: ['CONCAT_WS(\' \',',columns.join(),') AS `',name,'`'].join('');
return quotes(result);
var result = [];
for (var i=0; i < fk.length; i++) {
result.push([
this.fullName(table, fk[i]),
this.fullName(refTable, pk[i])].join(' = '));
}
result = result.join(' AND ');
return result;
}
// same as concat expect that it wrapts everything in group
exports.group = function (columns, name) {
// `table1`.`column` = 'value'
// `table1`.`column` = 'value' AND
exports.andValues = function (table, column, value) {
var columns = column instanceof Array ? column : [column],
values = (value instanceof Array)
? value
: (typeof value === 'string' ? value.split(',') : [value]);
var result = [];
for (var i=0; i < columns.length; i++) {
result.push(
this.fullName(table, columns[i])+' = '+escape(values[i]===undefined?null:values[i]));
}
result = result.join(' AND ');
return result;
}
// concat_ws(' ',`table`.`column1`,`table`.`column2`)
// `table`.`column1`||' '||`table`.`column2`
exports.concat = function (table, columns, sep) {
sep = sep||',';
return this.client.sqlite
? this.fullNames(table, columns).join("||'"+sep+"'||")
: ["CONCAT_WS('",sep,"',",this.list(table, columns),')'].join('');
}
// concat_ws(',',`table`.`column1`,`table`.`column2`) AS name
// `table`.`column` AS name
exports.as = function (table, columns, name) {
var result = (columns instanceof Array)
? this.concat(table, columns)
: this.fullName(table, columns);
result = result.concat(' AS ', name);
return result;
}
// group by `table.column`
// group by `table.column1`,`table.column2`
exports.groupby = function (table, column) {
return ['','GROUP BY',this.list(table, column),''].join(' ');
}
// left join `table1.column` ON `table2`.`column`
// left join `table1.column1` ON `table2`.`column1` AND `table1.column2` = `table2`.`column2`
exports.joins = function (table, fk, refTable, pk) {
var result = [
' LEFT JOIN',this.justName(refTable),'ON',
this.andColumns(table, fk, refTable, pk)
].join(' ');
return result;
}
// only for mysql
// [ 'CAST(`table`.`column` AS CHAR)' ]
exports.cast = function (table, columns) {
var result = [];
for (var i=0; i < columns.length; i++) {
result.push(['CAST(',this.fullName(table, columns[i]),' AS CHAR)'].join(''));
}
return result.join();
}
// concat (additionally cast for mysql)
exports.castConcat = function (table, columns, name) {
var result = this.client.mysql
? ['GROUP_CONCAT(DISTINCT CONCAT_WS(\' \',',columns.join(),')) AS `',name,'`'].join('')
: (this.client.pg
? ['string_agg(DISTINCT CONCAT_WS(\' \',',columns.join(),'),\',\') AS `',name,'`'].join('')
: ['GROUP_CONCAT(DISTINCT ',columns.join('||\' \'||'),') AS `',name,'`'].join('')
);
return quotes(result);
? ["CONCAT_WS(' ',",this.cast(table, columns),')'].join('')
: this.concat(table, columns, ' ');
result = result.concat(' AS ', this.justName(name))
return result;
}
/**
* Returns a primary key statement.
*
* @param {String} table
* @return {String} pk
* @api public
*/
exports.pk = function (table, pk) {
var result = this.fullName(table, pk).concat(' AS __pk');
return quotes(result);
}
exports.groupby = function (column) {
return ['','GROUP BY',column,''].join(' ');
// same as castConcat expect that it wrapts everything in group
exports.group = function (table, columns, name) {
var result = this.client.mysql
? ["CONCAT_WS(' ',",this.cast(table, columns),')'].join('')
: this.concat(table, columns, ' ');
switch (true) {
case this.client.mysql:
result = ['GROUP_CONCAT(DISTINCT ',result,')'].join('')
break;
case this.client.pg:
result = ['string_agg(DISTINCT ',result,',\',\')'].join('')
break;
case this.client.sqlite:
result = ['GROUP_CONCAT(DISTINCT ',result,')'].join('')
break;
}
result = result.concat(' AS ', this.justName(name));
return result;
}
/**
@@ -139,24 +177,47 @@ exports.order = function (table, list) {
return result;
}
/**
* Return a join statement.
*
* @param {String} table
* @param {String} fk
* @param {String} refTable
* @param {String} pk
* @return {String}
* @api public
*/
exports.joins = function (table, fk, refTable, pk) {
var result = [' LEFT JOIN `',refTable,'` ON ',
this.fullName(table, fk),' = ',
this.fullName(refTable, pk)].join('');
return quotes(result);
// parentValue1,parentValue2 - ['childValue1,childValue2', 'childValue3,childValue4']
// ('pv1','pv2','cv1','cv2'),('pv1','pv2','cv3','cv4')
exports.values = function (parentValues, childValues) {
parentValues = parentValues.toString().split(',');
var result = [];
for (var i=0; i < childValues.length; i++) {
var recordValues = parentValues.concat(childValues[i].toString().split(','));
var values = [];
for (var j=0; j < recordValues.length; j++) {
values.push(escape(recordValues[j]));
}
result.push(['(',values.join(),')'].join(''));
}
return result.join();
}
// ['child1','child2'] - ['2,4', '6,7']
// `child1` IN (2,6) AND `child2` IN (4,7)
exports.in = function (table, column, value) {
var columns = column instanceof Array ? column : [column],
values = value instanceof Array ? value : [value];
var arr2 = [];
for (var i=0; i < columns.length; i++) {
var arr = [];
for (var j=0; j < values.length; j++) {
arr.push(escape(values[j].toString().split(',')[i]));
}
arr2.push(arr);
}
var result = [];
for (var i=0; i < columns.length; i++) {
result.push([this.fullName(table, columns[i]),' IN (',arr2[i].join(),')'].join(''));
}
result = result.join(' AND ');
return result
}
/**
* Return a where clause.
*
+4 -4
View File
@@ -9,8 +9,9 @@ function getArgs (req, res) {
settings : res.locals._admin.settings,
db : res.locals._admin.db,
debug : res.locals._admin.debug,
log : res.locals._admin.log,
slug : req.params[0],
id : req.params[1] == 'add' ? null : req.params[1],
id : req.params[1] == 'add' ? null : req.params[1].split(','),
data : req.body,
upload : req.files,
upath : res.locals._admin.config.app.upload
@@ -75,8 +76,7 @@ exports.post = function (req, res, next) {
// print out
if (args.debug) {
for (var i=0; i < args.queries.length; i++) {
console.log(args.queries[i]);
console.log('-------------------------');
console.log('edit'.yellow, args.queries[i]);
}
}
@@ -98,7 +98,7 @@ exports.post = function (req, res, next) {
case {}.hasOwnProperty.call(action, 'continue'):
req.session.success = true;
if (args.debug) return render(req, res, next, data, args);
res.redirect(res.locals.root+'/'+args.slug+'/'+args.id);
res.redirect(res.locals.root+'/'+args.slug+'/'+args.id.join());
break;
}
});
+1
View File
@@ -19,6 +19,7 @@ function getArgs (req, res) {
settings : res.locals._admin.settings,
db : res.locals._admin.db,
debug : res.locals._admin.debug,
log : res.locals._admin.log,
slug : req.params[0],
page : req.query.p || 0,
data : req.body