hedera-web/js/db/model.js

1257 lines
28 KiB
JavaScript
Raw Normal View History

2016-09-26 09:28:47 +00:00
2022-05-24 10:18:44 +00:00
var Connection = require('./connection');
2016-09-26 09:28:47 +00:00
/**
* 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.
2022-05-24 10:18:44 +00:00
*/
var Model = new Class();
2016-09-26 09:28:47 +00:00
module.exports = Model;
2022-05-24 10:18:44 +00:00
var Status = {
2016-09-26 09:28:47 +00:00
CLEAN : 1
,LOADING : 2
,READY : 3
,ERROR : 4
};
2022-05-24 10:18:44 +00:00
var Mode = {
2016-09-26 09:28:47 +00:00
ON_CHANGE : 1
,ON_DEMAND : 2
};
2022-05-24 10:18:44 +00:00
var Operation = {
2016-09-26 09:28:47 +00:00
INSERT : 1 << 1
,UPDATE : 1 << 2
,DELETE : 1 << 3
};
2022-05-24 10:18:44 +00:00
var SortWay = {
2016-09-26 09:28:47 +00:00
ASC : 1
,DESC : 2
};
2022-05-24 10:18:44 +00:00
Model.extend({
2016-09-26 09:28:47 +00:00
Status: Status
,Mode: Mode
,Operation: Operation
,SortWay: SortWay
});
2022-05-24 10:18:44 +00:00
Model.implement({
Extends: Vn.Object
,Tag: 'db-model'
2022-05-24 10:18:44 +00:00
,Properties: {
/**
* The connection used to execute the statement.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
conn: {
2016-09-26 09:28:47 +00:00
type: Connection
2022-05-24 10:18:44 +00:00
,set: function(x) {
this._conn = x;
2022-05-24 10:18:44 +00:00
this._autoLoad();
}
2022-05-24 10:18:44 +00:00
,get: function() {
return this._conn;
}
},
/**
* The result index.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
resultIndex: {
type: Number
2022-05-24 10:18:44 +00:00
,set: function(x) {
this._resultIndex = x;
}
2022-05-24 10:18:44 +00:00
,get: function() {
return this._resultIndex;
}
},
/**
2022-05-30 01:30:33 +00:00
* The lot used to execute the statement.
2022-05-24 10:18:44 +00:00
*/
2022-05-30 01:30:33 +00:00
lot: {
type: Vn.LotIface
2022-05-24 10:18:44 +00:00
,set: function(x) {
2022-06-06 08:53:59 +00:00
this.link({_lot: x}, {'change': this._onLotChange});
2022-05-30 01:30:33 +00:00
this._onLotChange();
}
2022-05-24 10:18:44 +00:00
,get: function() {
2022-05-30 01:30:33 +00:00
return this._lot;
}
},
/**
* The model select statement.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
stmt: {
type: Sql.Stmt
2022-05-24 10:18:44 +00:00
,set: function(x) {
this._stmt = x;
2022-05-24 10:18:44 +00:00
this._autoLoad();
}
2022-05-24 10:18:44 +00:00
,get: function() {
return this._stmt;
}
},
/**
* The model query.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
query: {
type: String
2022-05-24 10:18:44 +00:00
,set: function(x) {
this.stmt = new Sql.String({query: x});
}
2022-05-24 10:18:44 +00:00
,get: function() {
if (this._stmt)
2022-05-24 10:18:44 +00:00
return this._stmt.render(null);
else
return null;
}
},
/**
* The main table.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
mainTable: {
type: String
2022-05-24 10:18:44 +00:00
,set: function(x) {
this._mainTable = null;
this._requestedMainTable = x;
2022-05-24 10:18:44 +00:00
this._refreshMainTable();
}
2022-05-24 10:18:44 +00:00
,get: function() {
return this._mainTable;
}
},
/**
* Determines if the model is updatable.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
updatable: {
type: Boolean
2022-05-24 10:18:44 +00:00
,set: function(x) {
this._updatable = false;
this._requestedUpdatable = x;
2022-05-24 10:18:44 +00:00
this._refreshUpdatable();
}
2022-05-24 10:18:44 +00:00
,get: function() {
return this._updatable;
}
},
/**
* The number of rows in the model.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
numRows: {
type: Number
2022-05-24 10:18:44 +00:00
,get: function() {
if (this.data)
return this.data.length;
return 0;
}
},
/**
* The current status of the model.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
status: {
type: Number
2022-05-24 10:18:44 +00:00
,get: function() {
return this._status;
}
},
/**
* Checks if the model data is ready.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
ready: {
type: Boolean
2022-05-24 10:18:44 +00:00
,get: function() {
2016-09-26 09:28:47 +00:00
return this._status == Status.READY;
}
2015-07-10 12:30:08 +00:00
},
/**
* Update mode.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
mode: {
2016-09-26 09:28:47 +00:00
enumType: Mode
,value: Mode.ON_CHANGE
2015-07-10 12:30:08 +00:00
},
/**
* Wether to execute the model query automatically.
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
autoLoad: {
2015-07-10 12:30:08 +00:00
type: Boolean
,value: true
}
}
2015-11-09 08:14:33 +00:00
,_conn: null
,_resultIndex: 0
2022-05-30 01:30:33 +00:00
,_lot: null
,_stmt: null
2016-09-26 09:28:47 +00:00
,_status: Status.CLEAN
,data: null
,tables: null
,columns: null
,columnMap: null
,_updatable: false
2022-06-06 16:02:17 +00:00
,_paramsChanged: true
,_requestedSortIndex: -1
,_requestedSortName: null
,_sortColumn: -1
,_sortWay: null
,_requestedIndexes: {}
,_indexes: []
,_requestedUpdatable: false
,_operations: null
,_operationsMap: null
,_defaults: []
,_requestedMainTable: null
2022-05-24 10:18:44 +00:00
,initialize: function(props) {
2022-06-06 16:02:17 +00:00
Vn.Object.prototype.initialize.call(this, props);
2022-05-24 10:18:44 +00:00
this._cleanData();
this._setStatus(Status.CLEAN);
}
2022-05-24 10:18:44 +00:00
,appendChild: function(child) {
if (child.nodeType === Node.TEXT_NODE)
this.query = child.textContent;
}
2022-05-24 10:18:44 +00:00
,loadXml: function(builder, node) {
2022-06-06 16:02:17 +00:00
Vn.Object.prototype.loadXml.call(this, builder, node);
var query = node.firstChild.nodeValue;
if (query)
this.query = query;
}
2022-06-06 08:53:59 +00:00
,_getHolders(stmt) {
if (!stmt) return null;
let holders = this._stmt.findHolders();
if (!holders) return null;
if (this._lot) {
const params = this._lot.params;
for (const holder of holders)
if (params[holder] instanceof Sql.Object) {
const paramHolders = params[holder].findHolders();
if (paramHolders)
holders = holders.concat(paramHolders);
}
}
return holders;
}
,_getHolderValues: function() {
let holders = this._getHolders(this._stmt);
if (!holders) return null;
const lotParams = this._lot ? this._lot.params : {};
const params = {};
for (const holder of holders)
if (!(lotParams[holder] instanceof Sql.Object))
params[holder] = lotParams[holder];
return params;
}
,_getHolderParams: function() {
let holders = this._getHolders(this._stmt);
if (!holders) return null;
const lotParams = this._lot ? this._lot.params : {};
const params = {};
for (const holder of holders)
params[holder] = lotParams[holder];
2022-05-30 01:30:33 +00:00
return params;
}
,_onLotChange: function() {
2022-06-06 08:53:59 +00:00
const params = this._getHolderValues();
2022-06-06 16:02:17 +00:00
this._paramsChanged = !Vn.Value.equals(params, this._lastParams);
2022-06-06 08:53:59 +00:00
2022-06-06 16:02:17 +00:00
if (this.autoLoad)
this.lazyRefresh();
2022-05-30 01:30:33 +00:00
}
2022-06-06 08:53:59 +00:00
2022-05-24 10:18:44 +00:00
,_autoLoad: function() {
2015-07-10 12:30:08 +00:00
if (this.autoLoad)
2022-05-24 10:18:44 +00:00
this.refresh();
else
2022-05-24 10:18:44 +00:00
this.clean();
2015-07-10 12:30:08 +00:00
}
2022-06-06 08:53:59 +00:00
2022-05-30 01:30:33 +00:00
,_isReady: function(params) {
if (!this._stmt || !this._conn)
return false;
2022-06-06 08:53:59 +00:00
for (const param in params)
if (params[param] === undefined)
return false;
2022-05-30 01:30:33 +00:00
return true;
}
2022-06-06 16:02:17 +00:00
,lazyRefresh: function() {
if (this._paramsChanged)
this.refresh();
}
/**
* Refresh the model data reexecuting the query on the database.
2022-05-24 10:18:44 +00:00
*/
2022-06-06 08:53:59 +00:00
,refresh: function() {
const params = this._getHolderParams();
2022-05-30 01:30:33 +00:00
2022-06-06 08:53:59 +00:00
if (this._isReady(params)) {
2022-05-24 10:18:44 +00:00
this._setStatus(Status.LOADING);
2022-07-15 05:55:18 +00:00
this._lastParams = this._getHolderValues();
2022-06-06 16:02:17 +00:00
this._paramsChanged = false;
2022-05-30 01:30:33 +00:00
this._conn.execStmt(this._stmt,
2022-06-06 08:53:59 +00:00
this._selectDone.bind(this), params);
2022-05-24 10:18:44 +00:00
} else
this.clean();
2015-09-28 15:21:37 +00:00
}
2022-06-06 08:53:59 +00:00
2022-05-24 10:18:44 +00:00
,clean: function() {
this._cleanData();
this._setStatus(Status.CLEAN);
}
2022-05-24 10:18:44 +00:00
,_selectDone: function(resultSet) {
var result;
var dataResult;
2022-05-24 10:18:44 +00:00
this._cleanData();
2016-10-11 14:45:10 +00:00
try {
2022-05-24 10:18:44 +00:00
for (var i = 0; result = resultSet.fetchResult(); i++)
if (i == this._resultIndex)
dataResult = result;
2016-10-11 14:45:10 +00:00
if (!dataResult || typeof dataResult !== 'object')
2022-05-24 10:18:44 +00:00
throw new Error('The provided statement doesn\'t return a result set');
} catch (e) {
this._setStatus(Status.ERROR);
2016-10-11 14:45:10 +00:00
throw e;
}
2016-10-11 14:45:10 +00:00
this.data = dataResult.data;
this.tables = dataResult.tables;
this.columns = dataResult.columns;
this.columnMap = dataResult.columnMap;
2022-05-24 10:18:44 +00:00
this._repairColumns();
this._refreshRowIndexes(0);
this._refreshMainTable();
2016-10-11 14:45:10 +00:00
for (column in this._requestedIndexes)
2022-05-24 10:18:44 +00:00
this._buildIndex(column);
2016-10-11 14:45:10 +00:00
var sortColumn = -1;
2016-10-11 14:45:10 +00:00
if (this._requestedSortName)
2022-05-24 10:18:44 +00:00
sortColumn = this.getColumnIndex(this._requestedSortName);
2016-10-11 14:45:10 +00:00
else if (this._requestedSortIndex !== -1
2022-05-24 10:18:44 +00:00
&& this.checkColExists(this._requestedSortIndex))
2016-10-11 14:45:10 +00:00
sortColumn = this._requestedSortIndex;
if (sortColumn !== -1)
2022-05-24 10:18:44 +00:00
this._realSort(sortColumn, this._sortWay);
2016-10-11 14:45:10 +00:00
2022-05-24 10:18:44 +00:00
this._setStatus(Status.READY);
}
2022-05-24 10:18:44 +00:00
,_refreshRowIndexes: function(start) {
for (var i = start; i < this.data.length; i++)
this.data[i].index = i;
2022-05-24 10:18:44 +00:00
if (this._operationsMap) {
this._operationsMap = {};
for (var i = 0; i < this._operations.length; i++)
this._operationsMap[i] = this._operations[i];
}
}
2022-05-24 10:18:44 +00:00
,_cleanData: function() {
this.data = null;
this.tables = null;
this.columns = null;
this.columnMap = null;
this._sortColumn = -1;
this._indexes = [];
2022-05-24 10:18:44 +00:00
this._resetOperations();
}
2022-05-24 10:18:44 +00:00
,_refreshUpdatable: function() {
var oldValue = this._updatable;
this._updatable = this._mainTable !== null && this._requestedUpdatable;
if (oldValue != this._updatable)
2022-05-30 01:30:33 +00:00
this.emit('updatable-changed');
}
2022-05-24 10:18:44 +00:00
,_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
2022-05-24 10:18:44 +00:00
|| tables[i].name === this._requestedMainTable) {
newMainTable = i;
break;
}
this._mainTable = newMainTable;
2022-05-24 10:18:44 +00:00
this._refreshUpdatable();
}
/**
* Sets the default value for inserted fields.
*
* @param {String} field The destination field name
* @param {String} table The destination table name
* @param {Sql.Expr} srcColumn The default value expression
2022-05-24 10:18:44 +00:00
*/
,setDefault: function(field, table, expr) {
2022-05-28 15:49:46 +00:00
this._defaults.push({field, table, expr});
}
/**
* Sets the default value for inserted fields.
*
* @param {String} field The destination field name
* @param {String} table The destination table name
* @param {Object} value The default value
2022-05-24 10:18:44 +00:00
*/
,setDefaultFromValue: function(field, table, value) {
2022-05-28 15:49:46 +00:00
this._defaults.push({field, table, value});
}
/**
* Sets the default value for inserted fields from another column in the
* model.
*
* @param {String} field The destination field name
* @param {String} table The destination table name
* @param {String} srcColumn The source column
2022-05-24 10:18:44 +00:00
*/
,setDefaultFromColumn: function(field, table, srcColumn) {
2022-05-28 15:49:46 +00:00
this._defaults.push({field, table, srcColumn});
}
/**
2022-05-28 15:49:46 +00:00
* Checks if column index exists.
*
* @param {integer} column The column index
* @return {Boolean} %true if column exists, %false otherwise
2022-05-24 10:18:44 +00:00
*/
,checkColExists: function(column) {
return this.columns
&& column >= 0
&& column < this.columns.length;
}
2022-05-28 15:49:46 +00:00
/**
* Checks if column name exists.
*
* @param {string} columnName The column name
* @return {Boolean} %true if column exists, %false otherwise
*/
,checkColName: function(columnName) {
return this.columnMap
&& this.columnMap[columnName] != null;
}
/**
* Checks if the row exists.
*
* @param {integer} rowIndex The row index
* @return {Boolean} %true if row exists, %false otherwise
2022-05-24 10:18:44 +00:00
*/
,checkRowExists: function(rowIndex) {
return this.data
&& rowIndex >= 0
&& rowIndex < this.data.length;
}
2022-05-24 10:18:44 +00:00
,_checkTableUpdatable: function(tableIndex) {
2015-04-01 08:24:15 +00:00
var tableUpdatable = tableIndex !== null
&& this.tables[tableIndex].pks.length > 0;
2022-07-15 05:55:18 +00:00
if (!tableUpdatable && !tableIndex) {
if (tableIndex)
console.warn("Db.Model: Table %s is not updatable",
this.tables[tableIndex].name);
else
console.warn("Db.Model: Model not updatable");
}
return tableUpdatable;
}
2022-05-28 15:49:46 +00:00
/**
* 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
2022-05-24 10:18:44 +00:00
*/
,getColumnIndex: function(columnName) {
2022-05-28 15:49:46 +00:00
if (this.checkColName(columnName))
return this.columnMap[columnName].index;
return -1;
}
2022-05-24 10:18:44 +00:00
2022-05-28 15:49:46 +00:00
/**
* Get the index of the column from its name.
*
* @param {number} columnIndex The column name
* @return {string} The column index or -1 if column not exists
*/
,getColumnName: function(columnIndex) {
if (this.checkColExists(columnIndex))
return this.columns[columnIndex].name;
return null;
}
2022-05-24 10:18:44 +00:00
/**
* Gets the row as object.
*
* @param {number} rowIndex The row index
* @return {Object} The row
*/
,getObject: function(rowIndex) {
if (!this.checkRowExists(rowIndex))
return undefined;
2022-05-28 15:49:46 +00:00
return this.data[rowIndex];
2022-05-24 10:18:44 +00:00
}
/**
* Gets a value from the model.
*
* @param {number} rowIndex The row index
* @param {string} columnName The column name
* @return {mixed} The value
2022-05-24 10:18:44 +00:00
*/
,get: function(rowIndex, columnName) {
2022-05-28 15:49:46 +00:00
if (this.checkRowExists(rowIndex))
2022-05-30 01:30:33 +00:00
return this.data[rowIndex][columnName];
}
/**
* Updates a value on the model.
*
* @param {number} rowIndex The row index
* @param {string} columnName The column name
* @param {mixed} value The new value
2022-05-24 10:18:44 +00:00
*/
,set: function(rowIndex, columnName, value) {
if (!this.checkRowExists(rowIndex)
2022-05-28 15:49:46 +00:00
&& !this.checkColName(columnName))
return;
2022-05-28 15:49:46 +00:00
var tableIndex = this.columnMap[columnName].table;
2022-05-24 10:18:44 +00:00
if (!this._checkTableUpdatable(tableIndex))
return;
var row = this.data[rowIndex];
2022-05-24 10:18:44 +00:00
var op = this._createOperation(rowIndex);
2016-09-26 09:28:47 +00:00
op.type |= Operation.UPDATE;
if (!op.oldValues)
op.oldValues = [];
if (!op.tables)
op.tables = {};
var tableOp = op.tables[tableIndex];
2022-05-24 10:18:44 +00:00
if (!tableOp) {
2016-09-26 09:28:47 +00:00
tableOp = Operation.UPDATE;
var pks = this.tables[tableIndex].pks;
2022-05-28 15:49:46 +00:00
for (const pk of pks)
if (!row[pk] && !op.oldValues[pk]) {
2016-09-26 09:28:47 +00:00
tableOp = Operation.INSERT;
break;
}
op.tables[tableIndex] = tableOp;
}
2016-09-26 09:28:47 +00:00
if (tableOp & Operation.UPDATE
2022-05-28 15:49:46 +00:00
&& op.oldValues[columnName] === undefined)
op.oldValues[columnName] = row[columnName];
2022-05-30 01:30:33 +00:00
this.emit('row-updated-before', rowIndex);
2022-05-28 15:49:46 +00:00
row[columnName] = value;
2022-05-30 01:30:33 +00:00
this.emit('row-updated', rowIndex, [columnName]);
2016-09-26 09:28:47 +00:00
if (this.mode == Mode.ON_CHANGE
&& !(op.type & Operation.INSERT))
2022-05-24 10:18:44 +00:00
this.performOperations();
}
2022-05-28 15:49:46 +00:00
/**
* Gets a value from the model using the column index.
*
* @param {number} rowIndex The row index
* @param {number} columnIndex The column index
* @return {mixed} The value
*/
,getByIndex: function(rowIndex, columnIndex) {
var columnName = this.getColumnName(columnIndex);
if (columnName)
return this.get(rowIndex, columnName);
return undefined;
}
/**
* Updates a value on the model using the column index.
*
* @param {number} rowIndex The row index
* @param {number} columnIndex The column index
* @param {mixed} value The new value
*/
,setByIndex: function(rowIndex, columnIndex, value) {
var columnName = this.getColumnName(columnIndex);
if (columnName)
this.set(rowIndex, columnName, value);
else
console.warn('Db.Model: Column %d doesn\'t exist', columnIndex);
}
/**
* Deletes a row from the model.
*
* @param {number} rowIndex The row index
2022-05-24 10:18:44 +00:00
*/
,deleteRow: function(rowIndex) {
if (!this.checkRowExists(rowIndex)
|| !this._checkTableUpdatable(this._mainTable))
return;
2022-05-24 10:18:44 +00:00
var op = this._createOperation(rowIndex);
2016-09-26 09:28:47 +00:00
op.type |= Operation.DELETE;
2022-05-24 10:18:44 +00:00
if (!this._requestedMainTable) {
2022-05-30 01:30:33 +00:00
this.emit('row-deleted-before', rowIndex);
2022-05-24 10:18:44 +00:00
this.data.splice(rowIndex, 1);
2022-05-30 01:30:33 +00:00
this.emit('row-deleted', rowIndex);
2022-05-24 10:18:44 +00:00
this._refreshRowIndexes(rowIndex);
} else {
2022-05-30 01:30:33 +00:00
this.emit('row-updated-before', rowIndex);
if (!op.oldValues)
op.oldValues = [];
var updatedCols = [];
for (var i = 0; i < this.columns.length; i++)
2022-05-24 10:18:44 +00:00
if (this.columns[i].table == this._mainTable) {
2022-05-28 15:49:46 +00:00
const colName = this.columns[i].name;
2022-05-28 15:49:46 +00:00
if (op.oldValues[colName] === undefined)
op.oldValues[colName] = op.row[colName];
op.row[colName] = null;
2022-05-24 10:18:44 +00:00
updatedCols.push(i);
}
2022-05-30 01:30:33 +00:00
this.emit('row-updated', rowIndex, updatedCols);
}
2016-09-26 09:28:47 +00:00
if (this.mode === Mode.ON_CHANGE)
2022-05-24 10:18:44 +00:00
this.performOperations();
}
/**
* Inserts a new row on the model.
*
* @return The index of the inserted row
2022-05-24 10:18:44 +00:00
*/
,insertRow: function() {
if (!this._checkTableUpdatable(this._mainTable))
return -1;
var cols = this.columns;
2022-05-28 15:49:46 +00:00
var newRow = {};
for (var i = 0; i < cols.length; i++)
if (cols[i].table === this._mainTable)
2022-05-28 15:49:46 +00:00
newRow[cols[i].name] = cols[i].def;
else
2022-05-28 15:49:46 +00:00
newRow[cols[i].name] = null;
2022-05-24 10:18:44 +00:00
var rowIndex = this.data.push(newRow) - 1;
newRow.index = rowIndex;
2022-05-24 10:18:44 +00:00
var op = this._createOperation(rowIndex);
2016-09-26 09:28:47 +00:00
op.type |= Operation.INSERT;
2022-05-30 01:30:33 +00:00
this.emit('row-inserted', rowIndex);
return rowIndex;
}
/**
* Performs all model changes on the database.
2022-05-24 10:18:44 +00:00
*/
,performOperations: function() {
var ops = this._operations;
2022-05-24 10:18:44 +00:00
if (ops.length === 0) {
2022-05-30 01:30:33 +00:00
this.emit('operations-done');
return;
2015-07-10 12:30:08 +00:00
}
2022-05-24 10:18:44 +00:00
var stmts = new Sql.MultiStmt();
2022-05-24 10:18:44 +00:00
var query = new Sql.String({query: 'START TRANSACTION'});
2022-05-30 01:30:33 +00:00
stmts.push(query);
2022-05-24 10:18:44 +00:00
for (var i = 0; i < ops.length; i++) {
query = null;
var op = ops[i];
2022-05-24 10:18:44 +00:00
if (op.type & Operation.DELETE) {
2016-09-26 09:28:47 +00:00
if (op.type & Operation.INSERT)
continue;
2022-05-24 10:18:44 +00:00
var where = this._createWhere(this._mainTable, op, true);
2022-05-24 10:18:44 +00:00
if (where) {
2022-05-28 15:49:46 +00:00
query = new Sql.Delete({where});
2022-05-24 10:18:44 +00:00
query.addTarget(this._createTarget(this._mainTable));
}
2022-05-24 10:18:44 +00:00
} else if (op.type & (Operation.INSERT | Operation.UPDATE)) {
query = new Sql.MultiStmt();
2022-05-24 10:18:44 +00:00
for (var tableIndex in op.tables) {
var stmt = this._createDmlQuery(op, parseInt(tableIndex));
2022-05-30 01:30:33 +00:00
query.push(stmt);
}
}
2022-05-24 10:18:44 +00:00
if (query) {
2022-05-30 01:30:33 +00:00
stmts.push(query);
2022-05-24 10:18:44 +00:00
} else {
console.warn('Db.Model: %s', _('ErrorSavingChanges'));
return;
}
}
2015-07-10 12:30:08 +00:00
2022-05-24 10:18:44 +00:00
var query = new Sql.String({query: 'COMMIT'});
2022-05-30 01:30:33 +00:00
stmts.push(query);
2022-05-24 10:18:44 +00:00
this._conn.execStmt(stmts,
this._onOperationsDone.bind(this, ops));
2022-05-24 10:18:44 +00:00
this._resetOperations();
}
2022-05-24 10:18:44 +00:00
,_createDmlQuery: function(op, tableIndex) {
var where = this._createWhere(tableIndex, op, false);
if (!where)
return null;
2022-05-24 10:18:44 +00:00
var multiStmt = new Sql.MultiStmt();
var target = this._createTarget(tableIndex);
2022-05-28 15:49:46 +00:00
var select = new Sql.Select({where});
2022-05-24 10:18:44 +00:00
select.addTarget(target);
var row = op.row;
var cols = this.columns;
2022-05-24 10:18:44 +00:00
if (op.tables[tableIndex] & Operation.INSERT) {
var dmlQuery = new Sql.Insert();
var table = this.tables[tableIndex];
2022-07-15 05:55:18 +00:00
for (const def of this._defaults)
2022-05-28 15:49:46 +00:00
if (def.table === table.name) {
if (def.value)
dmlQuery.addSet(def.field, def.value);
else if (def.expr)
dmlQuery.addExpr(def.field, def.expr);
else if (def.srcColumn)
dmlQuery.addSet(def.field, row[def.srcColumn]);
}
2022-05-28 15:49:46 +00:00
for (const col of cols)
if (col.table === tableIndex) {
if (row[col.name] !== null)
dmlQuery.addSet(col.orgname, row[col.name]);
select.addField(col.orgname);
}
2022-05-24 10:18:44 +00:00
} else {
var updateWhere = this._createWhere(tableIndex, op, true);
if (!updateWhere)
return null;
2022-05-24 10:18:44 +00:00
var dmlQuery = new Sql.Update({where: updateWhere});
2022-05-28 15:49:46 +00:00
for (const col of cols)
if (col.table === tableIndex && op.oldValues[col.name] !== undefined) {
var fieldName = col.orgname;
dmlQuery.addSet(fieldName, row[col.name]);
2022-05-24 10:18:44 +00:00
select.addField(fieldName);
}
}
2022-05-24 10:18:44 +00:00
dmlQuery.addTarget(target);
2022-05-30 01:30:33 +00:00
multiStmt.push(dmlQuery);
multiStmt.push(select);
return multiStmt;
}
2022-05-24 10:18:44 +00:00
,_onOperationsDone: function(ops, resultSet) {
var error = resultSet.getError();
2016-10-30 22:48:18 +00:00
2022-05-24 10:18:44 +00:00
if (error) {
this._operations = this._operations.concat(ops);
for (var i = 0; i < ops.length; i++)
this._operationsMap[ops[i].row.index] = ops[i];
2015-07-21 14:16:07 +00:00
2016-10-30 22:48:18 +00:00
throw error;
}
2015-07-10 12:30:08 +00:00
2022-05-24 10:18:44 +00:00
resultSet.fetchResult();
var isOperation = false;
2022-05-24 10:18:44 +00:00
for (var i = 0; i < ops.length; i++) {
var op = ops[i];
var row = op.row;
2016-09-26 09:28:47 +00:00
if (!(op.type & Operation.DELETE
&& op.type & Operation.INSERT))
2022-06-18 21:04:34 +00:00
// eslint-disable-next-line no-unused-vars
isOperation = true;
2022-05-24 10:18:44 +00:00
if (op.type & Operation.DELETE) {
resultSet.fetchResult();
} else if (op.type & (Operation.INSERT | Operation.UPDATE)) {
2022-05-30 01:30:33 +00:00
this.emit('row-updated-before', row.index);
var updatedCols = [];
var cols = this.columns;
2022-05-24 10:18:44 +00:00
for (var tableIndex in op.tables) {
var j = 0;
2022-05-24 10:18:44 +00:00
tableIndex = parseInt(tableIndex);
2022-05-24 10:18:44 +00:00
resultSet.fetchResult();
var newValues = resultSet.fetchRow();
2022-05-24 10:18:44 +00:00
if (op.tables[tableIndex] & Operation.INSERT) {
for (var i = 0; i < cols.length; i++)
2022-05-24 10:18:44 +00:00
if (cols[i].table === tableIndex) {
2022-05-28 15:49:46 +00:00
row[cols[i].name] = newValues[j++];
2022-05-24 10:18:44 +00:00
updatedCols.push(i);
}
2022-05-24 10:18:44 +00:00
} else {
for (var i = 0; i < cols.length; i++)
if (cols[i].table === tableIndex
2022-05-24 10:18:44 +00:00
&& op.oldValues[i] !== undefined) {
2022-05-28 15:49:46 +00:00
row[cols[i].name] = newValues[j++];
2022-05-24 10:18:44 +00:00
updatedCols.push(i);
}
}
}
2022-05-30 01:30:33 +00:00
this.emit('row-updated', row.index, updatedCols);
}
}
2022-05-24 10:18:44 +00:00
resultSet.fetchResult();
2015-07-10 12:30:08 +00:00
// if (isOperation)
2022-05-30 01:30:33 +00:00
this.emit('operations-done');
}
/**
* Undoes all unsaved changes made to the model.
2022-05-24 10:18:44 +00:00
*/
,reverseOperations: function() {
for (var i = 0; i < this._operations.length; i++) {
var op = this._operations[i];
var row = op.row;
2016-09-26 09:28:47 +00:00
if (op.type & Operation.DELETE
2022-05-24 10:18:44 +00:00
&& !(op.type & Operation.INSERT)) {
this.data.splice(row.index, 0, row);
2022-05-30 01:30:33 +00:00
this.emit('row-inserted', row.index);
2022-05-24 10:18:44 +00:00
} else if (op.type & Operation.UPDATE) {
2022-05-30 01:30:33 +00:00
this.emit('row-updated-before', row.index);
var updatedCols = [];
var cols = this.columns;
for (var i = 0; i < cols.length; i++)
2022-05-24 10:18:44 +00:00
if (op.oldValues[i] !== undefined) {
2022-05-28 15:49:46 +00:00
const colName = cols[i].name;
row[colName] = op.oldValues[colName];
2022-05-24 10:18:44 +00:00
updatedCols.push(i);
}
2022-05-30 01:30:33 +00:00
this.emit('row-updated', row.index, updatedCols);
}
}
2022-05-24 10:18:44 +00:00
this._resetOperations();
this._refreshRowIndexes(0);
}
2022-05-24 10:18:44 +00:00
,_resetOperations: function() {
this._operations = [];
this._operationsMap = {};
}
/*
* Function used to sort the model ascending.
*/
2022-05-24 10:18:44 +00:00
,sortFunctionAsc: function(column, a, b) {
if (a[column] < b[column])
return -1;
else if (a[column] > b[column])
return 1;
return 0;
}
/*
* Function used to sort the model descending.
*/
2022-05-24 10:18:44 +00:00
,sortFunctionDesc: function(column, a, b) {
if (a[column] > b[column])
return -1;
else if (a[column] < b[column])
return 1;
return 0;
}
/**
* Orders the model by the specified column name.
*
2022-05-28 15:49:46 +00:00
* @param {integer} columnName The column name
2016-09-26 09:28:47 +00:00
* @param {SortWay} way The sort way
2022-05-24 10:18:44 +00:00
*/
,sortByName: function(columnName, way) {
this._requestedSortIndex = -1;
this._requestedSortName = columnName;
2022-05-28 15:49:46 +00:00
if (this.checkColName(columnName))
this._sort(columnName, way);
}
/**
* Orders the model by the specified column.
*
* @param {integer} column The column index
2016-09-26 09:28:47 +00:00
* @param {SortWay} way The sort way
2022-05-24 10:18:44 +00:00
*/
,sort: function(column, way) {
this._requestedSortIndex = column;
this._requestedSortName = null;
2022-05-28 15:49:46 +00:00
const columnName = this.getColumnName(column);
if (columnName) return;
2022-05-28 15:49:46 +00:00
this._sort(columnName, way);
}
2022-05-24 10:18:44 +00:00
,_sort: function(column, way) {
this._setStatus(Status.LOADING);
this._realSort(column, way);
this._setStatus(Status.READY);
}
2022-05-24 10:18:44 +00:00
,_realSort: function(column, way) {
if (column !== this._sortColumn) {
2016-09-26 09:28:47 +00:00
if (way === SortWay.DESC)
var sortFunction = this.sortFunctionDesc;
else
var sortFunction = this.sortFunctionAsc;
2022-05-24 10:18:44 +00:00
this.data.sort(sortFunction.bind(this, column));
} else if (way !== this._sortWay)
this.data.reverse();
this._sortColumn = column;
this._sortWay = way;
2022-05-24 10:18:44 +00:00
this._refreshRowIndexes(0);
}
2015-02-08 15:38:38 +00:00
/**
* Builds an internal hash index for the specified column, this speeds
* significantly searches on that column, specially when model has a lot of
* rows.
*
* FIXME: Not fully implemented.
2015-02-08 15:38:38 +00:00
*
* @param {String} column The column name
2022-05-24 10:18:44 +00:00
*/
,indexColumn: function(column) {
this._requestedIndexes[column] = true;
2015-02-08 15:38:38 +00:00
2016-09-26 09:28:47 +00:00
if (this._status === Status.READY)
2022-05-24 10:18:44 +00:00
this._buildIndex(column);
2015-02-08 15:38:38 +00:00
}
2022-05-28 15:49:46 +00:00
,_buildIndex: function(columnName) {
if (this.checkColName(columnName)) {
2015-02-08 15:38:38 +00:00
var index = {};
var data = this.data;
2022-05-28 15:49:46 +00:00
switch (this.columns[columnName].type) {
2016-09-26 09:28:47 +00:00
case Connection.Type.TIMESTAMP:
case Connection.Type.DATE_TIME:
case Connection.Type.DATE:
2015-02-08 15:38:38 +00:00
for (var i = 0; i < data.length; i++)
2022-05-28 15:49:46 +00:00
index[data[i][columnName].toString()] = i;
2015-02-08 15:38:38 +00:00
break;
default:
for (var i = 0; i < data.length; i++)
2022-05-28 15:49:46 +00:00
index[data[i][columnName]] = i;
2015-02-08 15:38:38 +00:00
}
2022-05-28 15:49:46 +00:00
this._indexes[columnName] = index;
2015-02-08 15:38:38 +00:00
}
}
/**
* 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.
*
2022-05-28 15:49:46 +00:00
* @param {String} columnName The column name
* @param {Object} value The value to search
* @return {integer} The column index
2022-05-24 10:18:44 +00:00
*/
2022-05-28 15:49:46 +00:00
,search: function(columnName, value) {
if (!this.checkColName(columnName))
return -1;
2015-02-08 15:38:38 +00:00
if (value)
2022-05-28 15:49:46 +00:00
switch (this.columnMap[columnName].type) {
2016-09-26 09:28:47 +00:00
case Connection.Type.BOOLEAN:
2015-02-08 15:38:38 +00:00
value = !!value;
break;
2016-09-26 09:28:47 +00:00
case Connection.Type.INTEGER:
2022-05-24 10:18:44 +00:00
value = parseInt(value);
2015-02-08 15:38:38 +00:00
break;
2016-09-26 09:28:47 +00:00
case Connection.Type.DOUBLE:
2022-05-24 10:18:44 +00:00
value = parseFloat(value);
2015-02-08 15:38:38 +00:00
break;
default:
2022-05-24 10:18:44 +00:00
value = value.toString();
2015-02-08 15:38:38 +00:00
}
2022-05-28 15:49:46 +00:00
let rowIndex = -1;
const index = this._indexes[columnName];
2015-02-08 15:38:38 +00:00
2022-05-24 10:18:44 +00:00
if (index) {
2022-05-28 15:49:46 +00:00
// Searchs the value using an internal index
2015-02-17 11:48:53 +00:00
if (index[value] !== undefined)
2022-05-28 15:49:46 +00:00
rowIndex = index[value];
} else {
// Searchs the value using a loop
2022-05-28 15:49:46 +00:00
var data = this.data;
switch (this.columnMap[columnName].type) {
2016-09-26 09:28:47 +00:00
case Connection.Type.TIMESTAMP:
case Connection.Type.DATE_TIME:
case Connection.Type.DATE:
for (var i = 0; i < data.length; i++)
2022-05-28 15:49:46 +00:00
if (value === data[i][columnName].toString()) {
rowIndex = i;
break;
}
break;
default:
for (var i = 0; i < data.length; i++)
2022-05-28 15:49:46 +00:00
if (value === data[i][columnName]) {
rowIndex = i;
break;
}
}
}
2022-05-28 15:49:46 +00:00
return rowIndex;
}
/**
* Searchs a value on the model and returns the row index of the first
* ocurrence.
*
* @param {integer} columnIndex The column index
* @param {Object} value The value to search
* @return {integer} The column index
*/
,searchByIndex: function(columnIndex, value) {
var columnName = this.getColumnName(columnIndex);
return this.search(columnName, value);
}
2022-05-24 10:18:44 +00:00
,_setStatus: function(status) {
this._status = status;
2022-05-30 01:30:33 +00:00
this.emit('status-changed', status);
this.emit('status-changed-after', status);
}
2022-05-24 10:18:44 +00:00
,_createTarget: function(tableIndex) {
var table = this.tables[tableIndex];
2015-03-31 15:14:44 +00:00
2022-05-24 10:18:44 +00:00
return new Sql.Table({
name: table.orgname
,schema: table.schema
});
}
2022-05-24 10:18:44 +00:00
,_createWhere: function(tableIndex, op, useOldValues) {
2022-05-28 15:49:46 +00:00
const where = new Sql.Operation({type: Sql.Operation.Type.AND});
const pks = this.tables[tableIndex].pks;
if (pks.length === 0)
return null;
2022-05-28 15:49:46 +00:00
for (const pk of pks) {
const column = this.columnMap[pk];
2022-05-28 15:49:46 +00:00
const equalOp = new Sql.Operation({type: Sql.Operation.Type.EQUAL});
2022-05-30 01:30:33 +00:00
equalOp.push(new Sql.Field({name: column.orgname}));
where.push(equalOp);
2022-05-28 15:49:46 +00:00
let pkValue = null;
if (useOldValues && op.oldValues
2022-05-28 15:49:46 +00:00
&& op.oldValues[pk] !== undefined)
pkValue = op.oldValues[pk];
else
2022-05-28 15:49:46 +00:00
pkValue = op.row[pk];
if (pkValue)
2022-05-30 01:30:33 +00:00
equalOp.push(new Sql.Value({value: pkValue}));
2016-09-26 09:28:47 +00:00
else if (column.flags & Connection.Flag.AI && !useOldValues)
2022-05-30 01:30:33 +00:00
equalOp.push(new Sql.Function({name: 'LAST_INSERT_ID'}));
else
return null;
}
return where;
}
2022-05-24 10:18:44 +00:00
,_createOperation: function(rowIndex) {
var op = this._operationsMap[rowIndex];
2022-05-24 10:18:44 +00:00
if (!op) {
op = {
type: 0,
row: this.data[rowIndex]
};
2022-05-24 10:18:44 +00:00
this._operations.push(op);
this._operationsMap[rowIndex] = op;
}
2015-02-08 15:38:38 +00:00
return op;
}
/**
* 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
2022-05-24 10:18:44 +00:00
*/
,setInfo: function(table, orgname, schema, pks, ai) {
if (!this.tableInfo)
this.tableInfo = {};
2022-05-28 15:49:46 +00:00
this.tableInfo[table] = {
orgname,
schema,
pks,
ai
2015-03-31 15:14:44 +00:00
};
2022-05-24 10:18:44 +00:00
this._repairColumns();
}
2022-05-24 10:18:44 +00:00
,_repairColumns: function() {
// Repairs wrong table info
2015-03-31 15:14:44 +00:00
if (this.tableInfo && this.tables)
2022-05-24 10:18:44 +00:00
for (var i = 0; i < this.tables.length; i++) {
var table = this.tables[i];
var tableInfo = this.tableInfo[table.name];
2015-03-31 15:14:44 +00:00
if (!tableInfo)
continue;
table.orgname = tableInfo.orgname;
2015-03-31 15:14:44 +00:00
table.schema = tableInfo.schema;
2022-05-24 10:18:44 +00:00
if (tableInfo.pks) {
table.pks = [];
2022-05-28 15:49:46 +00:00
for (const pk of tableInfo.pks) {
if (this.checkColName(pk))
table.pks.push(pk);
2015-12-10 23:24:14 +00:00
else
2022-05-24 10:18:44 +00:00
console.warn('Db.Model: Can\'t repair primary key: `%s`.`%s`'
2015-12-10 23:24:14 +00:00
,tableInfo.orgname
2022-05-28 15:49:46 +00:00
,pk
2015-12-10 23:24:14 +00:00
);
}
}
2022-05-24 10:18:44 +00:00
if (tableInfo.ai) {
2022-05-28 15:49:46 +00:00
if (this.checkColName(tableInfo.ai))
this.columnMap[tableInfo.ai].flags |= Connection.Flag.AI;
2015-12-10 23:24:14 +00:00
else
2022-05-24 10:18:44 +00:00
console.warn('Db.Model: Can\'t repair autoincrement column: `%s`.`%s`'
2015-12-10 23:24:14 +00:00
,tableInfo.orgname
,tableInfo.ai
);
}
}
}
});