1148 lines
22 KiB
JavaScript
1148 lines
22 KiB
JavaScript
|
|
var Connection = require ('./connection');
|
|
var VnModel = require ('vn/model');
|
|
|
|
/**
|
|
* Class to handle the Database select results. Also allows
|
|
* updates, insertions and deletions on tables where the primary key is
|
|
* selected.
|
|
*
|
|
* Note that table and column names must be unique in the selection query,
|
|
* otherwise updates are not allowed on that table/column. If two tables or
|
|
* columns have the same name, an alias should be used to make it updatable.
|
|
*/
|
|
var Klass = new Class ();
|
|
module.exports = Klass;
|
|
|
|
var Status = VnModel.Status;
|
|
var SortWay = VnModel.SortWay;
|
|
|
|
var Mode =
|
|
{
|
|
ON_CHANGE : 1
|
|
,ON_DEMAND : 2
|
|
};
|
|
|
|
var Operation =
|
|
{
|
|
INSERT : 1 << 1
|
|
,UPDATE : 1 << 2
|
|
,DELETE : 1 << 3
|
|
};
|
|
|
|
Klass.extend
|
|
({
|
|
Status: Status
|
|
,SortWay: SortWay
|
|
,Mode: Mode
|
|
,Operation: Operation
|
|
});
|
|
|
|
Klass.implement
|
|
({
|
|
Extends: VnModel
|
|
,Tag: 'db-model'
|
|
,Properties:
|
|
{
|
|
/**
|
|
* The connection used to execute the statement.
|
|
*/
|
|
conn:
|
|
{
|
|
type: Connection
|
|
,set: function (x)
|
|
{
|
|
this._conn = x;
|
|
this._autoLoad ();
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._conn;
|
|
}
|
|
},
|
|
/**
|
|
* The result index.
|
|
*/
|
|
resultIndex:
|
|
{
|
|
type: Number
|
|
,set: function (x)
|
|
{
|
|
this._resultIndex = x;
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._resultIndex;
|
|
}
|
|
},
|
|
/**
|
|
* The lot used to execute the statement.
|
|
*/
|
|
lot:
|
|
{
|
|
type: Vn.LotIface
|
|
,set: function (x)
|
|
{
|
|
this.link ({_lot: x}, {'change': this._onLotChange});
|
|
this._onLotChange ();
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._lot;
|
|
}
|
|
},
|
|
/**
|
|
* The remote filter used for query.
|
|
*/
|
|
filter:
|
|
{
|
|
type: Sql.Filter
|
|
,set: function (x)
|
|
{
|
|
this._filter = x;
|
|
this.refresh ();
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._filter;
|
|
}
|
|
},
|
|
/**
|
|
* The model select statement.
|
|
*/
|
|
stmt:
|
|
{
|
|
type: Sql.Stmt
|
|
,set: function (x)
|
|
{
|
|
this._stmt = x;
|
|
this._autoLoad ();
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._stmt;
|
|
}
|
|
},
|
|
/**
|
|
* The model query.
|
|
*/
|
|
query:
|
|
{
|
|
type: String
|
|
,set: function (x)
|
|
{
|
|
this.stmt = new Sql.String ({query: x});
|
|
}
|
|
,get: function ()
|
|
{
|
|
if (this._stmt)
|
|
return this._stmt.render (null);
|
|
else
|
|
return null;
|
|
}
|
|
},
|
|
/**
|
|
* The main table.
|
|
*/
|
|
mainTable:
|
|
{
|
|
type: String
|
|
,set: function (x)
|
|
{
|
|
this._mainTable = null;
|
|
this._requestedMainTable = x;
|
|
this._refreshMainTable ();
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._mainTable;
|
|
}
|
|
},
|
|
/**
|
|
* Determines if the model is updatable.
|
|
*/
|
|
updatable:
|
|
{
|
|
type: Boolean
|
|
,set: function (x)
|
|
{
|
|
this._updatable = false;
|
|
this._requestedUpdatable = x;
|
|
this._refreshUpdatable ();
|
|
}
|
|
,get: function ()
|
|
{
|
|
return this._updatable;
|
|
}
|
|
},
|
|
/**
|
|
* Update mode.
|
|
*/
|
|
mode:
|
|
{
|
|
enumType: Mode
|
|
,value: Mode.ON_CHANGE
|
|
},
|
|
/**
|
|
* Wether to execute the model query automatically.
|
|
*/
|
|
autoLoad:
|
|
{
|
|
type: Boolean
|
|
,value: true
|
|
}
|
|
}
|
|
|
|
,_conn: null
|
|
,_resultIndex: 0
|
|
,_lot: null
|
|
,_stmt: null
|
|
,result: null
|
|
,tables: null
|
|
,columns: null
|
|
,columnMap: null
|
|
,_updatable: false
|
|
|
|
,_requestedSortIndex: -1
|
|
|
|
,_requestedUpdatable: false
|
|
,_operations: null
|
|
,_operationsMap: null
|
|
,_defaults: []
|
|
,_requestedMainTable: null
|
|
|
|
,initialize: function (props)
|
|
{
|
|
this.parent (props);
|
|
this._cleanData ();
|
|
this._setStatus (Status.CLEAN);
|
|
}
|
|
|
|
,appendChild: function (child)
|
|
{
|
|
if (child.nodeType === Node.TEXT_NODE)
|
|
this.query = child.textContent;
|
|
}
|
|
|
|
,_getLotParams: function ()
|
|
{
|
|
if (!this._stmt)
|
|
return null;
|
|
|
|
var holders = this._stmt.findHolders ();
|
|
|
|
if (!holders)
|
|
return null;
|
|
|
|
var lotParams = this._lot ? this._lot.params : {};
|
|
|
|
if (lotParams == null)
|
|
lotParams = {};
|
|
|
|
var params = {};
|
|
|
|
for (var i = 0; i < holders.length; i++)
|
|
params[holders[i]] = lotParams[holders[i]];
|
|
|
|
return params;
|
|
}
|
|
|
|
,_onLotChange: function ()
|
|
{
|
|
var lotParams = this._getLotParams ();
|
|
|
|
if (!Vn.Value.equals (lotParams, this._lastLotParams))
|
|
this._autoLoad ();
|
|
}
|
|
|
|
,_autoLoad: function ()
|
|
{
|
|
if (this.autoLoad)
|
|
this.refresh ();
|
|
else
|
|
this.clean ();
|
|
}
|
|
|
|
,_isReady: function (params)
|
|
{
|
|
if (!this._stmt || !this._conn)
|
|
return false;
|
|
|
|
var holders = this._stmt.findHolders ();
|
|
|
|
if (!holders)
|
|
return true;
|
|
|
|
for (var i = 0; i < holders.length; i++)
|
|
if (params[holders[i]] === undefined)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Refresh the model data reexecuting the query on the database.
|
|
*/
|
|
,refresh: function (params)
|
|
{
|
|
var lotParams = this._getLotParams ();
|
|
|
|
var myParams = {};
|
|
Object.assign (myParams, lotParams);
|
|
Object.assign (myParams, params);
|
|
|
|
if (this._filter && (!params || params.filter === undefined))
|
|
myParams.filter = this._filter;
|
|
|
|
this._lastLotParams = lotParams;
|
|
|
|
if (this._isReady (myParams))
|
|
{
|
|
this._setStatus (Status.LOADING);
|
|
this._conn.execStmt (this._stmt, this._selectDone.bind (this), myParams);
|
|
}
|
|
else
|
|
this.clean ();
|
|
}
|
|
|
|
,clean: function ()
|
|
{
|
|
this._cleanData ();
|
|
this._setStatus (Status.CLEAN);
|
|
}
|
|
|
|
,_selectDone: function (resultSet)
|
|
{
|
|
var result;
|
|
var dataResult;
|
|
|
|
this._cleanData ();
|
|
|
|
try {
|
|
for (var i = 0; dataResult = resultSet.fetchResult (); i++)
|
|
if (i == this._resultIndex)
|
|
result = dataResult;
|
|
|
|
if (!result || typeof result !== 'object')
|
|
throw new Error ('The provided statement doesn\'t return a result set');
|
|
}
|
|
catch (e)
|
|
{
|
|
this._setStatus (Status.ERROR);
|
|
throw e;
|
|
}
|
|
|
|
this.result = result;
|
|
this.tables = result.tables;
|
|
this.columns = result.columns;
|
|
this.columnMap = {};
|
|
|
|
for (var i = 0; i < this.columns.length; i++)
|
|
this.columnMap[this.columns[i].name] = i;
|
|
|
|
this._repairColumns ();
|
|
this._refreshMainTable ();
|
|
|
|
if (this._requestedSortIndex !== -1)
|
|
{
|
|
sortColumn = this.getColumnName (this._requestedSortIndex);
|
|
|
|
if (sortColumn)
|
|
this._requestedSortName = sortColumn;
|
|
}
|
|
|
|
this._setData (result.data);
|
|
}
|
|
|
|
,_cleanData: function ()
|
|
{
|
|
this.result = null;
|
|
this._data = null;
|
|
this.tables = null;
|
|
this.columns = null;
|
|
this.columnMap = null;
|
|
this._sortColumn = -1;
|
|
this._indexes = [];
|
|
this._resetOperations ();
|
|
}
|
|
|
|
,_refreshUpdatable: function ()
|
|
{
|
|
var oldValue = this._updatable;
|
|
this._updatable = this._mainTable !== null && this._requestedUpdatable;
|
|
|
|
if (oldValue != this._updatable)
|
|
this.emit ('updatable-changed');
|
|
}
|
|
|
|
,_refreshMainTable: function ()
|
|
{
|
|
var newMainTable = null;
|
|
var tables = this.tables;
|
|
|
|
if (tables)
|
|
for (var i = 0; i < tables.length; i++)
|
|
if (tables[i].pks.length > 0)
|
|
if (!this._requestedMainTable
|
|
|| tables[i].name === this._requestedMainTable)
|
|
{
|
|
newMainTable = i;
|
|
break;
|
|
}
|
|
|
|
this._mainTable = newMainTable;
|
|
this._refreshUpdatable ();
|
|
}
|
|
|
|
/**
|
|
* Checks if the column index exists.
|
|
*
|
|
* @param {Number} column The column index
|
|
* @return {Boolean} %true if column exists, %false otherwise
|
|
*/
|
|
,checkColumnIndex: function (column)
|
|
{
|
|
return this.columns
|
|
&& column >= 0
|
|
&& column < this.columns.length;
|
|
}
|
|
|
|
,_checkTableUpdatable: function (tableIndex)
|
|
{
|
|
var tableUpdatable = tableIndex !== null
|
|
&& this.tables[tableIndex].pks.length > 0;
|
|
|
|
if (!tableUpdatable)
|
|
console.warn ("Db.Model: Table %s is not updatable",
|
|
this.tables[tableIndex].name);
|
|
|
|
return tableUpdatable;
|
|
}
|
|
|
|
/**
|
|
* Get the index of the column from its name.
|
|
*
|
|
* @param {String} columnName The column name
|
|
* @return {Number} The column index or -1 if column not exists
|
|
*/
|
|
,getColumnIndex: function (columnName)
|
|
{
|
|
var index;
|
|
|
|
if (this.columnMap
|
|
&& (index = this.columnMap[columnName]) !== undefined)
|
|
return index;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get the index of the column name from its index.
|
|
*
|
|
* @param {String} columnIndex The column index
|
|
* @return {Number} The column name or undefined if column not exists
|
|
*/
|
|
,getColumnName: function (columnIndex)
|
|
{
|
|
if (!(this.columns && this.checkColumnIndex (columnIndex)))
|
|
return null;
|
|
|
|
return this.columns[columnIndex].name;
|
|
}
|
|
|
|
/**
|
|
* Updates a value on the model using the column name.
|
|
*
|
|
* @param {Number} rowIndex The row index
|
|
* @param {Number} columnName The column name
|
|
* @param {*} value The new value
|
|
*/
|
|
,set: function (rowIndex, columnName, value)
|
|
{
|
|
if (!this.checkRowExists (rowIndex))
|
|
return;
|
|
|
|
var columnIndex = this.getColumnIndex (columnName);
|
|
|
|
if (columnIndex === -1)
|
|
return;
|
|
|
|
var tableIndex = this.columns[columnIndex].table;
|
|
|
|
if (!this._checkTableUpdatable (tableIndex))
|
|
return;
|
|
|
|
var row = this._data[rowIndex];
|
|
|
|
var op = this._createOperation (rowIndex);
|
|
op.type |= Operation.UPDATE;
|
|
|
|
if (!op.oldValues)
|
|
op.oldValues = {};
|
|
if (!op.tables)
|
|
op.tables = {};
|
|
|
|
var tableOp = op.tables[tableIndex];
|
|
|
|
if (!tableOp)
|
|
{
|
|
tableOp = Operation.UPDATE;
|
|
var pks = this.tables[tableIndex].pks;
|
|
|
|
for (var i = 0; i < pks.length; i++)
|
|
{
|
|
var pkName = this.columns[pks[i]].name;
|
|
|
|
if (!row[pkName] && !op.oldValues[pkName])
|
|
{
|
|
tableOp = Operation.INSERT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
op.tables[tableIndex] = tableOp;
|
|
}
|
|
|
|
if (tableOp & Operation.UPDATE
|
|
&& op.oldValues[columnName] === undefined)
|
|
op.oldValues[columnName] = row[columnName];
|
|
|
|
this.parent (rowIndex, columnName, value);
|
|
|
|
if (this.mode == Mode.ON_CHANGE
|
|
&& !(op.type & Operation.INSERT))
|
|
this.performOperations ();
|
|
}
|
|
|
|
/**
|
|
* Gets a value from the model.
|
|
*
|
|
* @param {Number} rowIndex The row index
|
|
* @param {String} columnIndex The column index
|
|
* @return {*} The value
|
|
*/
|
|
,getByIndex: function (rowIndex, columnIndex)
|
|
{
|
|
var columName = this.getColumnName (columnIndex);
|
|
|
|
if (columName == null)
|
|
return undefined;
|
|
|
|
return this.get (rowIndex, columName);
|
|
}
|
|
|
|
/**
|
|
* Updates a value on the model.
|
|
*
|
|
* @param {Number} rowIndex The row index
|
|
* @param {String} columnIndex The column index
|
|
* @param {*} value The new value
|
|
*/
|
|
,setByIndex: function (rowIndex, columnIndex, value)
|
|
{
|
|
var columName = this.getColumnName (columnIndex);
|
|
|
|
if (columName == null)
|
|
{
|
|
console.warn ('Column %s doesn\'t exist', columnIndex);
|
|
return;
|
|
}
|
|
|
|
this.set (rowIndex, columName, value);
|
|
}
|
|
|
|
/**
|
|
* Returns an array with model column names.
|
|
*
|
|
* @return {Array} The column names
|
|
*/
|
|
,keys: function ()
|
|
{
|
|
return this.ready ?
|
|
Object.keys (this._model.columnMap) : null;
|
|
}
|
|
|
|
/**
|
|
* Sets the default value for inserted rows.
|
|
*
|
|
* @param {String} column The destination column name
|
|
* @param {String} table The destination table name
|
|
* @param {Sql.Expr} expr The default value expression
|
|
*/
|
|
,setDefault: function (column, table, expr)
|
|
{
|
|
this._defaults.push
|
|
({
|
|
column: column
|
|
,table: table
|
|
,expr: expr
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets the default value for inserted rows.
|
|
*
|
|
* @param {String} column The destination column name
|
|
* @param {String} table The destination table name
|
|
* @param {*} value The default value
|
|
*/
|
|
,setDefaultFromValue: function (column, table, value)
|
|
{
|
|
this._defaults.push
|
|
({
|
|
column: column
|
|
,table: table
|
|
,value: value
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets the default value for inserted rows from another column in the
|
|
* model.
|
|
*
|
|
* @param {String} column The destination column name
|
|
* @param {String} table The destination table name
|
|
* @param {String} srcColumn The source column
|
|
*/
|
|
,setDefaultFromColumn: function (column, table, srcColumn)
|
|
{
|
|
this._defaults.push
|
|
({
|
|
column: column
|
|
,table: table
|
|
,srcColumn: srcColumn
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deletes a row from the model.
|
|
*
|
|
* @param {Number} rowIndex The row index
|
|
*/
|
|
,deleteRow: function (rowIndex)
|
|
{
|
|
if (!this.checkRowExists (rowIndex)
|
|
|| !this._checkTableUpdatable (this._mainTable))
|
|
return;
|
|
|
|
var op = this._createOperation (rowIndex);
|
|
op.type |= Operation.DELETE;
|
|
|
|
if (!this._requestedMainTable)
|
|
{
|
|
this.parent (rowIndex);
|
|
}
|
|
else
|
|
{
|
|
this.emit ('row-updated-before', rowIndex);
|
|
|
|
if (!op.oldValues)
|
|
op.oldValues = {};
|
|
|
|
var cols = this.columns;
|
|
var updatedCols = [];
|
|
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].table === this._mainTable)
|
|
{
|
|
var columnName = cols[i].name;
|
|
|
|
if (op.oldValues[columnName] === undefined)
|
|
op.oldValues[columnName] = op.row[columnName];
|
|
|
|
op.row[columnName] = null;
|
|
updatedCols.push (columnName);
|
|
}
|
|
|
|
this.emit ('row-updated', rowIndex, updatedCols);
|
|
}
|
|
|
|
if (this.mode === Mode.ON_CHANGE)
|
|
this.performOperations ();
|
|
}
|
|
|
|
/**
|
|
* Inserts a new row on the model.
|
|
*
|
|
* @return The index of the inserted row
|
|
*/
|
|
,insertRow: function ()
|
|
{
|
|
if (!this._checkTableUpdatable (this._mainTable))
|
|
return -1;
|
|
|
|
var cols = this.columns;
|
|
var newRow = {};
|
|
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].table === this._mainTable)
|
|
newRow[cols[i].name] = cols[i].def;
|
|
else
|
|
newRow[cols[i].name] = null;
|
|
|
|
var rowIndex = this.parent (newRow);
|
|
|
|
var op = this._createOperation (rowIndex);
|
|
op.type |= Operation.INSERT;
|
|
|
|
return rowIndex;
|
|
}
|
|
|
|
/**
|
|
* Orders the model by the specified column.
|
|
*
|
|
* @param {Number} columnIndex The column index
|
|
* @param {SortWay} way The sort way
|
|
*/
|
|
,sortByIndex: function (columnIndex, way)
|
|
{
|
|
this._requestedSortIndex = columnIndex;
|
|
var columnName = this.getColumnName (columnIndex);
|
|
|
|
if (columnName == null)
|
|
return;
|
|
|
|
this._sort (columnName, way);
|
|
}
|
|
|
|
,sort: function (columnName, way)
|
|
{
|
|
this._requestedSortIndex = -1;
|
|
this._sort (columnName, way);
|
|
}
|
|
|
|
/**
|
|
* Searchs a value on the model and returns the row index of the first
|
|
* ocurrence.
|
|
* If an index have been built on that column, it will be used, for more
|
|
* information see the indexColumn() method.
|
|
*
|
|
* @param {String} columnIndex The column index
|
|
* @param {Object} value The value to search
|
|
* @return {Number} The column index
|
|
*/
|
|
,searchByIndex: function (columnIndex, value)
|
|
{
|
|
var columnName = this.getColumnName (columnIndex);
|
|
return this.search (columnName, value);
|
|
}
|
|
|
|
/**
|
|
* Performs all model changes on the database.
|
|
*/
|
|
,performOperations: function ()
|
|
{
|
|
var ops = this._operations;
|
|
|
|
if (ops.length === 0)
|
|
{
|
|
this.emit ('operations-done');
|
|
return;
|
|
}
|
|
|
|
var stmts = new Sql.MultiStmt ();
|
|
|
|
var query = new Sql.String ({query: 'START TRANSACTION'});
|
|
stmts.push (query);
|
|
|
|
for (var i = 0; i < ops.length; i++)
|
|
{
|
|
query = null;
|
|
var op = ops[i];
|
|
|
|
if (op.type & Operation.DELETE)
|
|
{
|
|
if (op.type & Operation.INSERT)
|
|
continue;
|
|
|
|
var where = this._createWhere (this._mainTable, op, true);
|
|
|
|
if (where)
|
|
{
|
|
query = new Sql.Delete ({
|
|
where: where,
|
|
limit: 1
|
|
});
|
|
query.addTarget (this._createTarget (this._mainTable));
|
|
}
|
|
}
|
|
else if (op.type & (Operation.INSERT | Operation.UPDATE))
|
|
{
|
|
query = new Sql.MultiStmt ();
|
|
|
|
for (var tableIndex in op.tables)
|
|
{
|
|
var stmt = this._createDmlQuery (op, parseInt (tableIndex));
|
|
query.push (stmt);
|
|
}
|
|
}
|
|
|
|
if (query)
|
|
{
|
|
stmts.push (query);
|
|
}
|
|
else
|
|
{
|
|
console.warn ('Db.Model: %s', _('ErrorSavingChanges'));
|
|
return;
|
|
}
|
|
}
|
|
|
|
var query = new Sql.String ({query: 'COMMIT'});
|
|
stmts.push (query);
|
|
|
|
this._conn.execStmt (stmts,
|
|
this._onOperationsDone.bind (this, ops));
|
|
|
|
this._resetOperations ();
|
|
}
|
|
|
|
,_createDmlQuery: function (op, tableIndex)
|
|
{
|
|
var where = this._createWhere (tableIndex, op, false);
|
|
|
|
if (!where)
|
|
return null;
|
|
|
|
var multiStmt = new Sql.MultiStmt ();
|
|
var target = this._createTarget (tableIndex);
|
|
|
|
var select = new Sql.Select ({
|
|
where: where,
|
|
limit: 1
|
|
});
|
|
select.addTarget (target);
|
|
|
|
var row = op.row;
|
|
var cols = this.columns;
|
|
|
|
if (op.tables[tableIndex] & Operation.INSERT)
|
|
{
|
|
var dmlQuery = new Sql.Insert ();
|
|
var table = this.tables[tableIndex];
|
|
|
|
for (var i = 0; i < this._defaults.length; i++)
|
|
{
|
|
var def = this._defaults[i];
|
|
|
|
if (def.table === table.name)
|
|
{
|
|
if (def.value)
|
|
dmlQuery.addSet (def.column, def.value);
|
|
else if (def.expr)
|
|
dmlQuery.addExpr (def.column, def.expr);
|
|
else if (def.srcColumn)
|
|
dmlQuery.addSet (def.column, row[def.srcColumn]);
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].table === tableIndex)
|
|
{
|
|
if (row[i] !== null)
|
|
dmlQuery.addSet (cols[i].orgname, row[cols[i].name]);
|
|
|
|
select.addField (cols[i].orgname);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var updateWhere = this._createWhere (tableIndex, op, true);
|
|
|
|
if (!updateWhere)
|
|
return null;
|
|
|
|
var dmlQuery = new Sql.Update ({
|
|
where: updateWhere,
|
|
limit: 1
|
|
});
|
|
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].table === tableIndex && op.oldValues[cols[i].name] !== undefined)
|
|
{
|
|
var fieldName = cols[i].orgname;
|
|
dmlQuery.addSet (fieldName, row[cols[i].name]);
|
|
select.addField (fieldName);
|
|
}
|
|
}
|
|
|
|
dmlQuery.addTarget (target);
|
|
|
|
multiStmt.push (dmlQuery);
|
|
multiStmt.push (select);
|
|
return multiStmt;
|
|
}
|
|
|
|
,_onOperationsDone: function (ops, resultSet)
|
|
{
|
|
var error = resultSet.getError ();
|
|
|
|
if (error)
|
|
{
|
|
this._operations = this._operations.concat (ops);
|
|
|
|
for (var i = 0; i < ops.length; i++)
|
|
this._operationsMap[ops[i].row.index] = ops[i];
|
|
|
|
throw error;
|
|
}
|
|
|
|
resultSet.fetchResult ();
|
|
|
|
for (var i = 0; i < ops.length; i++)
|
|
{
|
|
var op = ops[i];
|
|
var row = op.row;
|
|
var type = op.type;
|
|
|
|
if (type & Operation.DELETE)
|
|
{
|
|
resultSet.fetchResult ();
|
|
}
|
|
else if (type & (Operation.INSERT | Operation.UPDATE))
|
|
{
|
|
this.emit ('row-updated-before', row.index);
|
|
|
|
var updatedCols = [];
|
|
var cols = this.columns;
|
|
|
|
for (var tableIndex in op.tables)
|
|
{
|
|
var j = 0;
|
|
tableIndex = parseInt (tableIndex);
|
|
|
|
resultSet.fetchResult ();
|
|
var newValues = resultSet.fetchRow ();
|
|
|
|
if (op.tables[tableIndex] & Operation.INSERT)
|
|
{
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].table === tableIndex)
|
|
{
|
|
row[cols[i].name] = newValues[j++];
|
|
updatedCols.push (cols[i].name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].table === tableIndex
|
|
&& op.oldValues[cols[i].name] !== undefined)
|
|
{
|
|
row[cols[i].name] = newValues[j++];
|
|
updatedCols.push (cols[i].name);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.emit ('row-updated', row.index, updatedCols);
|
|
}
|
|
}
|
|
|
|
resultSet.fetchResult ();
|
|
this.emit ('operations-done');
|
|
}
|
|
|
|
,_createTarget: function (tableIndex)
|
|
{
|
|
var table = this.tables[tableIndex];
|
|
|
|
return new Sql.Table
|
|
({
|
|
name: table.orgname
|
|
,schema: table.schema
|
|
});
|
|
}
|
|
|
|
,_createWhere: function (tableIndex, op, useOldValues)
|
|
{
|
|
var Type = Sql.Operation.Type;
|
|
var where = new Sql.Operation ({type: Type.AND});
|
|
var pks = this.tables[tableIndex].pks;
|
|
|
|
if (pks.length === 0)
|
|
return null;
|
|
|
|
for (var i = 0; i < pks.length; i++)
|
|
{
|
|
var column = this.columns[pks[i]];
|
|
|
|
var equalOp = new Sql.Operation ({type: Type.EQUAL});
|
|
equalOp.push (new Sql.Field ({name: column.orgname}));
|
|
where.push (equalOp);
|
|
|
|
var pkValue = null;
|
|
|
|
if (useOldValues && op.oldValues
|
|
&& op.oldValues[column.name] !== undefined)
|
|
pkValue = op.oldValues[column.name];
|
|
else
|
|
pkValue = op.row[column.name];
|
|
|
|
if (pkValue)
|
|
equalOp.push (new Sql.Value ({value: pkValue}));
|
|
else if (column.flags & Connection.Flag.AI && !useOldValues)
|
|
equalOp.push (new Sql.Function ({name: 'LAST_INSERT_ID'}));
|
|
else
|
|
return null;
|
|
}
|
|
|
|
return where;
|
|
}
|
|
|
|
,_createOperation: function (rowIndex)
|
|
{
|
|
var op = this._operationsMap[rowIndex];
|
|
|
|
if (!op)
|
|
{
|
|
op = {
|
|
type: 0,
|
|
row: this._data[rowIndex]
|
|
};
|
|
this._operations.push (op);
|
|
this._operationsMap[rowIndex] = op;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
/**
|
|
* Undoes all unsaved changes made to the model.
|
|
*/
|
|
,reverseOperations: function ()
|
|
{
|
|
for (var i = 0; i < this._operations.length; i++)
|
|
{
|
|
var op = this._operations[i];
|
|
var row = op.row;
|
|
|
|
if (op.type & Operation.DELETE
|
|
&& !(op.type & Operation.INSERT))
|
|
{
|
|
this._data.splice (row.index, 0, row);
|
|
this.emit ('row-inserted', row.index);
|
|
}
|
|
else if (op.type & Operation.UPDATE)
|
|
{
|
|
this.emit ('row-updated-before', row.index);
|
|
|
|
var updatedCols = [];
|
|
var cols = this.columns;
|
|
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (op.oldValues[cols[i].name] !== undefined)
|
|
{
|
|
var columnName = cols[i].name;
|
|
row[columnName] = op.oldValues[columnName];
|
|
updatedCols.push (columnName);
|
|
}
|
|
|
|
this.emit ('row-updated', row.index, updatedCols);
|
|
}
|
|
}
|
|
|
|
this._resetOperations ();
|
|
this._refreshRowIndexes (0);
|
|
}
|
|
|
|
,_resetOperations: function ()
|
|
{
|
|
this._operations = [];
|
|
this._operationsMap = {};
|
|
}
|
|
|
|
,_refreshRowIndexes: function (start)
|
|
{
|
|
this.parent (start);
|
|
|
|
if (this._operationsMap)
|
|
{
|
|
this._operationsMap = {};
|
|
|
|
for (var i = 0; i < this._operations.length; i++)
|
|
this._operationsMap[i] = this._operations[i];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overrides information about a table and its columns. If a parameter is
|
|
* not provided, the original will be preserved. This method should be used
|
|
* primarily to avoid the mysql bug that causes this information will not
|
|
* be set correctly.
|
|
* For more information see the following links:
|
|
* - https://bugs.mysql.com/bug.php?id=44660
|
|
* - https://bugs.mysql.com/bug.php?id=26894
|
|
*
|
|
* @param {String} table The table alias
|
|
* @param {String} orgtable The original table name
|
|
* @param {String} schema The original table schema
|
|
* @param {Array} pks Array with the names of primary keys
|
|
* @param {String} ai The autoincrement column name
|
|
*/
|
|
,setInfo: function (table, orgname, schema, pks, ai)
|
|
{
|
|
if (!this.tableInfo)
|
|
this.tableInfo = {};
|
|
|
|
this.tableInfo[table] =
|
|
{
|
|
orgname: orgname,
|
|
schema: schema,
|
|
pks: pks,
|
|
ai: ai
|
|
};
|
|
|
|
this._repairColumns ();
|
|
}
|
|
|
|
,_repairColumns: function ()
|
|
{
|
|
// Repairs wrong table info
|
|
|
|
if (this.tableInfo && this.tables)
|
|
for (var i = 0; i < this.tables.length; i++)
|
|
{
|
|
var table = this.tables[i];
|
|
var tableInfo = this.tableInfo[table.name];
|
|
|
|
if (!tableInfo)
|
|
continue;
|
|
|
|
table.orgname = tableInfo.orgname;
|
|
table.schema = tableInfo.schema;
|
|
|
|
if (tableInfo.pks)
|
|
{
|
|
table.pks = [];
|
|
|
|
for (var j = 0; j < tableInfo.pks.length; j++)
|
|
{
|
|
var colIndex = this.getColumnIndex (tableInfo.pks[j]);
|
|
|
|
if (colIndex !== -1)
|
|
table.pks.push (colIndex);
|
|
else
|
|
console.warn ('Db.Model: Can\'t repair primary key: `%s`.`%s`'
|
|
,tableInfo.orgname
|
|
,tableInfo.pks[j]
|
|
);
|
|
}
|
|
}
|
|
|
|
if (tableInfo.ai)
|
|
{
|
|
var colIndex = this.getColumnIndex (tableInfo.ai);
|
|
|
|
if (colIndex !== -1)
|
|
this.columns[colIndex].flags |= Connection.Flag.AI;
|
|
else
|
|
console.warn ('Db.Model: Can\'t repair autoincrement column: `%s`.`%s`'
|
|
,tableInfo.orgname
|
|
,tableInfo.ai
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|