UNPKG

@easyquery/core

Version:

EasyQuery.JS Community core modules

1,471 lines (1,457 loc) 221 kB
/* * EasyQuery.JS Core v7.3.5 * Copyright 2025 Korzh.com * Licensed under MIT * * Build time: 3/23/2025 1:44:51 PM */ import { i18n, DataType, utils, ValueEditor, MetaEntity, MetaEntityAttr, EditorTag, MetaData, EventEmitter, AggregationSettings, HttpClient, EasyDataTable } from '@easydata/core'; export { i18n } from '@easydata/core'; /** * Represents a parser for format expressions in operators */ class FormatParser { constructor(format) { this.start(format); } /** * Starts parsing of the format string passed in the parameter * @param format */ start(format) { this.formatStr = format; this.pos = 0; this.exprNum = 0; this.tokenText = ''; } /** * Skips all spcaes till the beginning of next token */ skipSpaces() { while (this.pos < this.formatStr.length && this.formatStr.charAt(this.pos) === ' ') this.pos++; } /** * Scans the source and gets the next token */ next() { this.skipSpaces(); if (this.pos >= this.formatStr.length) return false; var npos = 0; if (this.formatStr.charAt(this.pos) === '{') { npos = this.formatStr.indexOf('}', this.pos); if (npos < 0) return false; this.tokenText = this.formatStr.substring(this.pos, npos + 1); if (this.tokenText.indexOf('{expr') === 0) { this.token = 'expression'; this.exprNum = parseInt(this.tokenText.substring(5, this.tokenText.length)); } this.pos = npos + 1; } else if (this.formatStr.charAt(this.pos) === '[' && this.pos < this.formatStr.length - 1 && this.formatStr.charAt(this.pos + 1) === '[') { this.pos += 2; npos = this.formatStr.indexOf(']]', this.pos); this.token = 'operator'; this.tokenText = this.formatStr.substring(this.pos, npos); this.pos = npos + 2; } else { this.token = 'text'; var npos1 = this.formatStr.indexOf('{', this.pos); if (npos1 < 0) npos1 = this.formatStr.length; var npos2 = this.formatStr.indexOf('[[', this.pos); if (npos2 < 0) npos2 = this.formatStr.length; npos = Math.min(npos1, npos2); this.tokenText = this.formatStr.substring(this.pos, npos).trim(); this.pos = npos; } return true; } /** * Returns current token */ getToken() { return { type: this.token, text: this.tokenText, index: this.exprNum - 1 }; } /** * Parses all source string passed in constructor and returns the list of tokens */ parse() { let result = []; while (this.next()) { result.push(this.getToken()); } return result; } } /** Represents type of linking of conditions in group (predicate).*/ var LinkType; (function (LinkType) { /** * None of the conditions must be truth (all must be false). * In SQL it will look like: `NOT (Condition1 OR Condition2 OR ...)`. */ LinkType[LinkType["None"] = 0] = "None"; /** At least one condition must be truth. In SQL they are connected by `OR` operator.*/ LinkType[LinkType["Any"] = 1] = "Any"; /** * Not all conditions must be truth (at least one must false). * In SQL it will look like: `NOT(Condition1 AND Condition2 AND ...)`. */ LinkType[LinkType["NotAll"] = 2] = "NotAll"; /** All conditions must be truth, in result SQL they are connected by `AND` operator;*/ LinkType[LinkType["All"] = 3] = "All"; })(LinkType || (LinkType = {})); var equtils; (function (equtils) { /** * Adds two paths and returns the result * Correctly processes leading and trailing slashes * @param path1 * @param path2 */ function combinePath(path1, path2) { let result = path1; if (result != null && result.length > 0) { if (result.charAt(result.length - 1) != '/') result += "/"; result += path2; } else { result = path2; } return result; } equtils.combinePath = combinePath; /** * Converts a string to a `LinkType` value * @param str */ function strToLinkType(str) { if (str == "All") { return LinkType.All; } else if (str == "NotAll") { return LinkType.NotAll; } else if (str == "Any") { return LinkType.Any; } else { return LinkType.None; } } equtils.strToLinkType = strToLinkType; /** * Converts a `LinkType` value to a string * @param type */ function linkTypeToStr(type) { if (type === LinkType.All) { return "All"; } else if (type === LinkType.NotAll) { return "NotAll"; } else if (type == LinkType.Any) { return "Any"; } else { return "None"; } } equtils.linkTypeToStr = linkTypeToStr; /** * Parses the operator's format string and returns a list of tokens * @param operator */ function parseOperatorFormat(operator) { let format = i18n.getText('Operators', operator.id, 'format'); if (!format) { format = i18n.getText('Operators', operator.id, 'displayFormat'); } if (!format) { format = operator.displayFormat; } const parser = new FormatParser(format); return parser.parse(); } equtils.parseOperatorFormat = parseOperatorFormat; //----------- value converstion utils -------------- /** * Converts a value from one DataType to another * @param value * @param fromDataType - the original data type * @param toDataType - the data type we want to get */ function convertValue(value, fromDataType, toDataType) { switch (toDataType) { case DataType.String: return value; case DataType.Autoinc, DataType.Byte, DataType.Int32, DataType.Int64, DataType.Word: let resInt = parseInt(value); return isNaN(resInt) ? '' : resInt.toString(); case DataType.Currency, DataType.Float: let resFloat = parseFloat(value); return isNaN(resFloat) ? '' : resFloat.toString(); default: return ''; } } equtils.convertValue = convertValue; })(equtils || (equtils = {})); /** * Represents an aggregate function */ class AggrFunction { /** The default constructor. */ constructor() { this.id = ""; this.caption = ""; this.sqlExpr = ""; this.displayFormat = ""; this.appliedTypes = []; } /** * Loads an aggregate function from its JSON representation object * @param aggrFunction The JSON representation object */ loadFromData(aggrFunction) { if (aggrFunction) { this.id = aggrFunction.id; this.caption = aggrFunction.cptn; this.displayFormat = aggrFunction.fmt; this.sqlExpr = aggrFunction.expr; this.appliedTypes = aggrFunction.dtypes || this.appliedTypes; } } getAppliedTypesOrDefault() { if (this.appliedTypes.length > 0) return this.appliedTypes; if (this.id === 'SUM' || this.id === 'AVG') { return [DataType.Autoinc, DataType.Byte, DataType.Currency, DataType.Float, DataType.Int32, DataType.Int64, DataType.Word]; } else if (this.id === 'MIN' || this.id === 'MAX') { return [DataType.Autoinc, DataType.BCD, DataType.Byte, DataType.Currency, DataType.Date, DataType.DateTime, DataType.Time, DataType.Float, DataType.Int32, DataType.Int64, DataType.Word]; } return utils.getAllDataTypes(); } } /** * Represents a value editor. */ class EqValueEditor extends ValueEditor { /** The default constructor. */ constructor() { super(); } /** * Loads value editor from its JSON representation object. * @param data The JSON representation object. */ loadFromData(data) { super.loadFromData(data); if (data) { if (data.sql) { this.statement = data.sql; } if (data.extraParams) { this.extraParams = data.extraParams; } } } } /** Represents expression kinds.*/ var DataKind; (function (DataKind) { /** Represents one value of some type: one constant or one attribute (field).*/ DataKind[DataKind["Scalar"] = 0] = "Scalar"; /** The same as Scalar but represents only one constant value of some type.*/ DataKind[DataKind["Const"] = 1] = "Const"; /** The same as Scalar but represents only one attribute.*/ DataKind[DataKind["Attribute"] = 2] = "Attribute"; /** Represents a list of scalar values. */ DataKind[DataKind["List"] = 4] = "List"; /** Special expression kind which represents a sub query.*/ DataKind[DataKind["Query"] = 5] = "Query"; /** Special expression kind which represents a binding to parent column.*/ DataKind[DataKind["ParentColumn"] = 6] = "ParentColumn"; })(DataKind || (DataKind = {})); /** * Represents a logical expression or predicate, like comparisions or `LIKE` predicate. */ class Operator { /** The default constructor. */ constructor() { this.id = ""; this.expr = ""; this.constValueFormat = "{const}"; this.caption = "{Unrecognized operator}"; this.displayFormat = "{expr1} [[{unrecognized operator}]] {expr2}"; this.isRange = false; this.caseIns = false; this.paramCount = 2; this.defaultOperand = new Operand(); this.appliedTypes = []; this.operands = new Array(); } /** * Loads operator from its JSON representation object. * @param model The Data Model. * @param data The JSON representation object. */ loadFromData(model, data) { if (data) { this.id = data.id; this.caption = data.cptn; this.caseIns = data.caseIns; this.isRange = data.isRange; this.displayFormat = data.fmt; this.paramCount = data.pcnt; this.expr = data.expr; this.appliedTypes = data.dtypes || this.appliedTypes; if (data.defOperand) { this.defaultOperand.loadFromData(model, data.defOperand); } if (data.editor) { this.defaultOperand.editor = model.getEditorById(data.editor) || new EqValueEditor(); } if (data.operands) { for (let i = 0; i < data.operands.length; i++) { let newOperand = new Operand(); newOperand.loadFromData(model, data.operands[i]); if (data.editor) { newOperand.editor = model.getEditorById(data.editor) || new EqValueEditor(); } this.operands.push(newOperand); } } } } } /** * Represents one operand in some operator's expression */ class Operand { /** The default constructor. */ constructor() { this.kind = DataKind.Scalar; this.dataType = DataType.Unknown; this.editor = new EqValueEditor(); this.defValue = ""; } /** * Loads operand from its JSON representation object. * @param model The Data Model. * @param operand A plain JS object that represents the operand. */ loadFromData(model, operand) { this.kind = operand.kind; this.dataType = operand.dtype; this.defValue = operand.val; this.defText = operand.txt; if (operand.editor) { this.editor = model.getEditorById(operand.editor) || new EqValueEditor(); } } /** * Copies operand from other operand. * @param src The source operand. */ copyFrom(src) { utils.assign(this, src); } } /** * Represents one entity. */ class Entity extends MetaEntity { /** The default constructor. */ constructor(parent) { super(parent); this.useInConditions = false; this.useInResult = false; this.useInSorting = false; } /** * Loads entity from its JSON representation object. * @param model The Data Model. * @param data The JSON representation object. */ loadFromData(model, data) { super.loadFromData(model, data); if (data) { this.useInConditions = data.uic; this.useInResult = data.uir; this.useInSorting = data.uis; } } scan(processAttribute, processEntity) { super.scan((attr, opts) => processAttribute && processAttribute(attr, opts), (entity, opts) => processEntity && processEntity(entity, opts)); } } /** * Represents entity attribute. */ class EntityAttr extends MetaEntityAttr { /** The default constructor. */ constructor(entity) { super(entity); /** * The parameters associated with this entity attribute */ this.params = []; this.useInConditions = true; this.useInResult = true; this.useInSorting = true; this.defaultOperator = ""; this.operators = new Array(); this.lookupAttr = ""; this.expr = ""; } /** * Loads entity attribute from JSON representation object. * @param model The Data Model. * @param data The JSON representation object. */ loadFromData(model, data) { super.loadFromData(model, data); if (data) { this.useInConditions = data.uic; this.useInResult = data.uir; this.useInSorting = data.uis; this.expr = data.sql; this.defaultOperator = data.defOperator; this.operators = data.ops; if (data.udata) this.userData = data.udata; } } } /** Represents editor tags*/ const EqEditorTag = Object.assign(Object.assign({}, EditorTag), { /** SqlList tag value */ SqlList: "SQLLIST", /** SubQuery tag value */ SubQuery: "SUBQUERY", /** Bind to parent column tag value */ BindToParentColumn: "BINDTOPARENTCOLUMN" }); /** * Represents a data model */ class DataModel extends MetaData { /** The default constructor. */ constructor() { super(); this.mainEntity = null; this.nullAttribute = new EntityAttr(null); this.nullOperator = new Operator(); this.operators = new Array(); this.rootEntity = new Entity(); this.aggrFunctions = new Array(); this.dateMacros = ['Today', 'Yesterday', 'Tomorrow', 'FirstDayOfMonth', 'LastDayOfMonth', 'FirstDayOfWeek', 'FirstDayOfYear', 'FirstDayOfNextWeek', 'FirstDayOfPrevMonth', 'FirstDayOfNextMonth', 'FirstDayOfNextYear', 'FirstDayOfPrevYear', 'FirstDayOfPrevWeek']; this.timeMacros = ['Now', 'HourStart', 'Midnight', 'Noon']; this.links = []; } /** * Gets the main entity of model * @return The main entity. */ getMainEntity() { return this.mainEntity; } /** * Loads data model from JSON. * @param stringJson The JSON string. */ loadFromJSON(stringJson) { let model = JSON.parse(stringJson); this.loadFromData(model); } /** * Loads data model from its JSON representation object. * @param data The JSON representation object. */ loadFromData(data) { super.loadFromData(data); //Operators this.operators = new Array(); if (data.operators) { for (let i = 0; i < data.operators.length; i++) { let newOperator = new Operator(); newOperator.loadFromData(this, data.operators[i]); this.operators.push(newOperator); } } //rootEntity this.rootEntity.loadFromData(this, data.entroot); //Aggr Functions this.aggrFunctions = new Array(); if (data.aggrfuncs) { for (let i = 0; i < data.aggrfuncs.length; i++) { let newAggrFunc = new AggrFunction(); newAggrFunc.loadFromData(data.aggrfuncs[i]); this.aggrFunctions.push(newAggrFunc); } } } /** * Gets the data model object. * @returns Tha data model. */ getObject() { return this; } /** * Sets data to data model. * @param model Its JSON representation object or JSON string. */ setData(model) { if (typeof model === 'string') { this.loadFromJSON(model); } else { this.loadFromData(model); } } /** * Creates entity. * @param parent The parent entity. * @returns The Entity. */ createEntity(parent) { return new Entity(parent); } /** * Creates entity attribute. * @param parent The parent entity. * @returns The entity attribute. */ createEntityAttr(parent) { return new EntityAttr(parent); } /** * Creates new EqValueEditor * @returns An instance of EqValueEditor class */ createValueEditor() { return new EqValueEditor(); } /** * Gets root entity of the data model. * @returns The root entity. */ getRootEntity() { return this.rootEntity; } /** * Finds editor by its ID. * @param editorId The editor ID. * @returns The value editor or `null`. */ getEditorById(editorId) { return super.getEditorById(editorId); } /** * Gets entity attribute by its ID. * This function runs through all attributes inside specified model (it's root entity and all its sub-entities). * @param attrId The attribute ID. * @returns The attribute or `null`. */ getAttributeById(attrId) { let attr = this.getEntityAttrById(this.getRootEntity(), attrId); if (!attr) { return null; } return attr; } /** * Gets entity attribute by its ID. * This function runs through all attributes inside specified model (it's root entity and all its sub-entities). * @param attrId The attribute ID. * @returns The attribute or `nullAttribute`. */ getAttributeByIdSafe(attrId) { const attr = this.getAttributeById(attrId); if (!attr) return this.nullAttribute; return attr; } /** * Checks wether attribute contains such property. * @param attrId The attribute ID. * @param propName The property name. * @returns `true` if the attribute contains the property, otherwise `false`. */ checkAttrProperty(attrId, propName) { let attribute = this.getAttributeById(attrId); if (attribute) { if (typeof attribute[propName] === 'undefined') { throw 'No such property: ' + propName; } if (attribute[propName]) { return true; } else if (attribute.lookupAttr) { attrId = attribute.lookupAttr; attribute = this.getAttributeById(attrId); return attribute && attribute[propName]; } else { return false; } } else return false; } /** * Gets entity attribute by its ID. * This function runs through all attributes inside specified entity and all its sub-entities. * @param entity * @param attrId * @returns The attribute or `null`. */ getEntityAttrById(entity, attrId) { return super.getEntityAttrById(entity, attrId); } _listByEntityWithFilter(entity, filterFunc) { let result = new Array(); let caption; let ent = null; if (entity.subEntities) { let subEntityCount = entity.subEntities.length; for (let entIdx = 0; entIdx < subEntityCount; entIdx++) { ent = entity.subEntities[entIdx]; if (!filterFunc || filterFunc(ent, null)) { caption = i18n.getText('Entities', ent.name); if (!caption) { caption = ent.caption; } let newEnt = utils.assign(new Entity(), { id: ent.name, text: caption, items: [], isEntity: true }); newEnt.items = this._listByEntityWithFilter(ent, filterFunc); if (newEnt.items.length > 0) result.push(newEnt); } } } let attr = null; if (entity.attributes) { let attrCount = entity.attributes.length; for (let attrIdx = 0; attrIdx < attrCount; attrIdx++) { attr = entity.attributes[attrIdx]; if (!filterFunc || filterFunc(entity, attr)) { caption = i18n.getText('Attributes', attr.id); if (!caption) caption = attr.caption; let newEnt = utils.assign(new Entity(), { id: attr.id, text: caption, dataType: attr.dataType }); result.push(newEnt); } } } return result; } _listByEntity(entity, opts, filterFunc) { opts = opts || {}; let resultEntities = []; let resultAttributes = []; let caption; let ent = null; if (entity.subEntities) { let subEntityCount = entity.subEntities.length; for (let entIdx = 0; entIdx < subEntityCount; entIdx++) { ent = entity.subEntities[entIdx]; if (!filterFunc || filterFunc(ent, null)) { if (opts.addUIC !== false && ent.useInConditions !== false || opts.addUIR !== false && ent.useInResult !== false || opts.addUIS !== false && ent.useInSorting !== false) { caption = i18n.getText('Entities', ent.name) || ent.caption; let newEnt = utils.assign(new Entity(), { id: ent.name, text: caption, items: [], isEntity: true, description: ent.description }); let newOpts = utils.assign({}, opts); newOpts.includeRootData = false; newEnt.items = this._listByEntity(ent, newOpts, filterFunc); if (newEnt.items.length > 0) { resultEntities.push(newEnt); } } } } } let attr = null; if (entity.attributes) { let attrCount = entity.attributes.length; for (let attrIdx = 0; attrIdx < attrCount; attrIdx++) { attr = entity.attributes[attrIdx]; if (!filterFunc || filterFunc(entity, attr)) { if (opts.addUIC !== false && attr.useInConditions !== false || opts.addUIR !== false && attr.useInResult !== false || opts.addUIS !== false && attr.useInSorting !== false) { caption = i18n.getText('Attributes', attr.id) || attr.caption; resultAttributes.push(utils.assign(new EntityAttr(entity), { id: attr.id, text: caption, dataType: attr.dataType, lookupAttr: attr.lookupAttr, description: attr.description })); } } } } let sortCheck = (a, b) => { if (a.text.toLowerCase() == b.text.toLowerCase()) { return 0; } if (a.text.toLowerCase() > b.text.toLowerCase()) { return 1; } return -1; }; if (opts.sortEntities) { resultEntities.sort(sortCheck); resultAttributes.sort(sortCheck); } let result; if (!opts.attrPlacement || opts.attrPlacement == 0) { result = resultEntities.concat(resultAttributes); } else { result = resultAttributes.concat(resultEntities); } if (opts.attrPlacement == 2) { result.sort(sortCheck); } if (opts.includeRootData) { caption = i18n.getText('Entities', entity.name); if (!caption) caption = entity.caption; return { id: entity.name, text: caption, items: result }; } else { return result; } } /** * Clears data model. */ clear() { super.clear(); this.operators = []; this.aggrFunctions = []; } /** * Add or update an operator. * @param desc The operator descriptor. * @returns The operator. */ addOrUpdateOperator(desc) { let op = utils.findItemById(this.operators, desc.id); if (!op) { op = new Operator(); op.id = desc.id; this.operators.push(op); } op.caption = desc.caption; op.expr = desc.expr || ''; op.displayFormat = desc.format; op.defaultOperand = new Operand(); op.defaultOperand.kind = desc.kind || DataKind.Scalar; if (desc.appliedTypes) op.appliedTypes = desc.appliedTypes; if (desc.paramCount > 0) { op.paramCount = desc.paramCount; } this.runThroughEntities((attr, opts) => { if (attr.operators.indexOf(op.id) < 0 && op.appliedTypes.indexOf(attr.dataType) >= 0) { attr.operators.push(op.id); } }); return op; } /** * Removes an operator. * @param id The id. * @param soft If `false` - removes operators from model and attributes. Otherwise only * from attributes. */ removeOperator(id, soft = false) { let op = utils.findItemById(this.operators, id); if (op) { if (!soft) utils.removeArrayItem(this.operators, op); this.runThroughEntities((attr, opts) => { utils.removeArrayItem(attr.operators, op.id); }); } } /** * Get operators for data type. * @param type The data type * @return The array of operator ids. */ getOperatorIdsByDataType(type) { switch (type) { case DataType.String: case DataType.Memo: return 'StartsWith,EndsWith,Contains,Equal,InList,NotStartsWith,NotEndsWith,NotContains,NotEqual,NotInList,IsNull,IsNotNull'.split(','); case DataType.Guid: return 'Equal,NotEqual'.split(','); case DataType.Date: case DataType.DateTime: return 'DateWithinToday,DateWithinThisWeek,DateWithinPrevWeek,DateWithinThisMonth,DateWithinPrevMonth,DateWithinThisYear,DateWithinPrevYear,DateBeforePrecise,DateAfterPrecise,DatePeriodPrecise,IsNull,IsNotNull'.split(','); case DataType.Time: return 'TimeBeforePrecise,TimeAfterPrecise,TimePeriodPrecise,IsNull,IsNotNull'.split(','); case DataType.Byte: case DataType.Word: case DataType.Int32: case DataType.Int64: case DataType.Float: case DataType.Currency: case DataType.BCD: case DataType.Autoinc: case DataType.Unknown: return 'Equal,Between,LessThan,LessOrEqual,GreaterThan,GreaterOrEqual,InList,NotEqual,NotBetween,NotInList,IsNull,IsNotNull'.split(','); case DataType.Bool: return 'IsTrue,NotTrue'.split(','); default: const result = []; for (let op of this.operators) if (op.appliedTypes.indexOf(type) >= 0) result.push(op.id); return result; } } findAggrFunctionById(funcId) { for (const func of this.aggrFunctions) { if (func.id === funcId) return func; } return null; } /** * Finds link between two entities. * @param entityFrom The entity `from`. * @param entityTo The entity `to`. * @return The link. */ findLink(entityFrom, entityTo) { for (let link of this.links) { if (link.entityFrom == entityFrom && link.entityTo == entityTo) return link; } return null; } /** * Get links with the entity. * @param entity The entity. * @return The link. */ getLinksByEntity(entity) { const result = []; for (let link of this.links) { if (link.entityFrom == entity || link.entityTo == entity) { result.push(link); } } return result; } /** * Gets entities tree. * @param opts The options. * @param filterFunc The filter function. * Takes two parameters, Entity and EntityAttr (second parameter will be null for entities), and returns boolean (true if the corresponding entity or attribute). * @returns The tree of the entities and their attributes according to options and the filter function */ getEntitiesTree(opts, filterFunc) { return this._listByEntity(this.getRootEntity(), opts, filterFunc); } /** * Gets entities tree due to filter. * @param filterFunc The filter function. * Takes two parameters, Entity and EntityAttr (second parameter will be null for entities), and returns boolean (true if the corresponding entity or attribute). * @returns The tree of the entities and their attributes according to the filter function */ getEntitiesTreeWithFilter(filterFunc) { return this._listByEntityWithFilter(this.getRootEntity(), filterFunc); } /** * Finds full entity path by attribute * @param attrId The attribute id. * @param sep The separator. * @returns The path. */ getFullEntityPathByAttr(attrId, sep) { sep = sep || ' '; return this.getEntityPathByAttr(this.getRootEntity(), attrId, sep, true); } /** * Finds entity path by attribute * @param entity The entity. * @param attrId The attribute id. * @param sep The separator. * @param root The root option. * @returns The path. */ getEntityPathByAttr(entity, attrId, sep, root) { return super.getEntityPathByAttr(entity, attrId, sep, root); } /** * Gets the attribute text. * @param attr The attribute. * @param format The format. * @returns Formatted text. */ getAttributeText(attr, format) { return super.getAttributeText(attr, format); } getFirstUICAttr() { return this.getFirstUICAttrInEntity(this.getRootEntity()); } /** * Gets first `UIC` attribute in specified entity * (`UIC` stands for `use in conditions` - so such attribute can be used in conditions) * @param entity Entity object to search our attribute in. * @returns The entity attribute or `null` */ getFirstUICAttrInEntity(entity) { if (entity.useInConditions !== false) { if (entity.attributes) { let attrCount = entity.attributes.length; for (let i = 0; i < attrCount; i++) { if (entity.attributes[i].useInConditions) { return entity.attributes[i]; } } } if (entity.subEntities) { let subEntityCount = entity.subEntities.length; for (let i = 0; i < subEntityCount; i++) { let result = this.getFirstUICAttrInEntity(entity.subEntities[i]); if (result) { return result; } } } } return null; } /** * Scans model's entity tree and calls the callback functions for each attribute and entity. * @param processAttribute The callback function which is called for each attribute in model's entity tree. * The processed attribute is passed in the first function parameter. * @param processEntity The callback function which is called for each entity in tree. * The processed entity is passed in the first function parameter. */ runThroughEntities(processAttribute, processEntity) { this.getRootEntity().scan(processAttribute, processEntity); } /** * Finds first attribute by filter. * @param filterFunc The filter function. Takes EntityAttr object in parameter and returns boolean */ getFirstAttributeByFilter(filterFunc) { let res = null; this.runThroughEntities(function (attr, opts) { if (filterFunc(attr)) { opts.stop = true; res = attr; } }, null); return res; } /** * Finds operator in model by its ID. * This function runs through all operators inside specified model and returns the one with specified ID. * @param operatorId An operator ID. * @returns The operator or `null`. */ findOperatorById(operatorId) { if (this.operators.length > 0) { let opCount = this.operators.length; for (let idx = 0; idx < opCount; idx++) { if (this.operators[idx].id == operatorId) { return this.operators[idx]; } } } return null; } /** * Finds operator in model by its ID. * his function runs through all operators inside specified model and returns the one with specified ID. * @param operatorId Operator ID. * @returns The operator or special `NullOperator` object if operator is not found. */ getOperatorById(operatorId) { let op = this.findOperatorById(operatorId); if (!op) return this.nullOperator; return op; } getDefaultOperatorIdForAttr(attr) { if (attr.defaultOperator) { return attr.defaultOperator; } else if (attr.operators.length > 0) { return attr.operators[0]; } else { return this.nullOperator.id; } } /** * Gets caption of the aggregate function. * @param funcId The function id. * @returns The caption. */ getAggrFunctionCaption(funcId) { let funcTexts = i18n.getText('AggregateFunctions', funcId); let funcCaption = funcTexts ? funcTexts.caption : i18n.getText('Aggr' + funcId.replace(' ', '') + '_Caption'); if (funcCaption) { return funcCaption; } let func = utils.findItemById(this.aggrFunctions, funcId); if (!func || !func.caption) return funcId; return func.caption; } /** * Gets format of the aggregate function. * @param funcId The function id. * @returns The caption. */ getAggrFunctionFormat(funcId) { let funcTexts = i18n.getText('AggregateFunctions', funcId); let funcFormat = funcTexts ? funcTexts.displayFormat : i18n.getText('Aggr' + funcId.replace(' ', '') + '_Format'); if (funcFormat) { return funcFormat; } let func = utils.findItemById(this.aggrFunctions, funcId); if (!func || !func.displayFormat) return ''; return func.displayFormat; } /** * Gets default operator for an attribute. * @param attr The attribute. * @returns The operator. */ getDefaultOperatorForAttr(attr) { let operatorId = this.getDefaultOperatorIdForAttr(attr); return this.getOperatorById(operatorId); } /** * Gets operand. * @param attr The attribute. * @param operator The operator. * @param index The index. * @returns The operand. */ getOperand(attr, operator, index) { let defOperand = new Operand(); if (operator && operator.defaultOperand) { defOperand.copyFrom(operator.defaultOperand); if (!defOperand.defValue) { defOperand.defValue = ''; } if (!defOperand.defText) { defOperand.defText = ''; } } else { defOperand.kind = DataKind.Scalar; defOperand.dataType = attr.dataType; defOperand.defValue = ''; defOperand.defText = ''; defOperand.editor = null; } let result = defOperand; if (result.dataType === DataType.Unknown && attr) { result.dataType = attr.dataType; } if (operator && (index >= 0)) { if (operator.operands && operator.operands[index - 1]) { result = utils.assign(result, operator.operands[index - 1]); } } if (!result.editor) { if (defOperand.editor) { result.editor = defOperand.editor; } else if (defOperand.kind === DataKind.Query) { result.editor.tag = EqEditorTag.SubQuery; } else if (attr && attr.defaultEditor) { result.editor = attr.defaultEditor; } else if (result.dataType === DataType.Date || result.dataType === DataType.DateTime || result.dataType === DataType.Time) { result.editor.tag = EqEditorTag.DateTime; } else { result.editor.tag = EqEditorTag.Edit; } } return result; } /** * Gets the list of aggegate functions. * @returns AN array of aggregate functions. */ getAggrFunctions() { return this.aggrFunctions; } /** * Checks wether macro value is date. * @param macroExpr The macro value. * @retruns `true` if the macro value is date, otherwise `false`. */ isDateMacro(macroExpr, macroProcessor) { const match = macroExpr.match(/\${{?(.*?)}?}/); if (match != null) { const macroId = match[1]; if (utils.indexOfArrayItem(this.dateMacros, macroId) >= 0) { if (macroProcessor) { macroProcessor(macroId); } return true; } } return false; } /** * Checks whether the expression passed in the parameter is an time macro. * @param macroExpr The macro expression * @retruns `true` if the expression is a time macro (like Now), otherwise `false`. */ isTimeMacro(macroExpr, macroProcessor) { const match = macroExpr.match(/\${{?(.*?)}?}/); if (match != null) { const macroId = match[1]; if (utils.indexOfArrayItem(this.timeMacros, macroId) >= 0) { if (macroProcessor) { macroProcessor(macroId); } return true; } } return false; } /** * Gets date by its macro value. * @param macroExpr The macro value. * @returns The date or `null` */ getDateMacroValue(macroExpr) { let d = new Date(); const res = this.isDateMacro(macroExpr, macroId => { switch (macroId) { case 'Today': break; case 'Yesterday': d.setDate(d.getDate() - 1); break; case 'Tomorrow': d.setDate(d.getDate() + 1); break; case 'FirstDayOfMonth': d.setDate(1); break; case 'LastDayOfMonth': d.setMonth(d.getMonth() + 1, 0); break; case 'FirstDayOfPrevWeek': var day = d.getDay(); day = (day == 0) ? 1 : 8 - day; //We start week from Monday, but js - from Sunday d.setDate(d.getDate() - day); break; case 'FirstDayOfWeek': var day = d.getDay(); day = (day == 0) ? 6 : day - 1; //We start week from Monday, but js - from Sunday d.setDate(d.getDate() - day); break; case 'FirstDayOfYear': d.setMonth(0, 1); break; case 'FirstDayOfNextWeek': var day = d.getDay(); day = (day == 0) ? 1 : 8 - day; //We start week from Monday, but js - from Sunday d.setDate(d.getDate() + day); break; case 'FirstDayOfNextMonth': d.setMonth(d.getMonth() + 1, 1); break; case 'FirstDayOfPrevMonth': d.setMonth(d.getMonth() - 1, 1); break; case 'FirstDayOfPrevYear': d.setFullYear(d.getFullYear() - 1, 0, 1); break; case 'FirstDayOfNextYear': d.setFullYear(d.getFullYear() + 1, 0, 1); break; } }); return res ? d : null; } /** * Gets the date value by a macro expression of the expression itself. * @param macroExpr The macro value. * @returns The date or macro valu. */ getDateOrMacroValue(macroExpr) { let d = this.getDateMacroValue(macroExpr); return d ? d : macroExpr; } /** * Gets time by its macro value. * @param macroExpr The macro value. * @returns The date or `null`. */ getTimeMacroValue(macroExpr) { let d = new Date(); const res = this.isDateMacro(macroExpr, macroId => { switch (macroId) { case 'Now': break; case 'HourStart': d.setMinutes(0, 0, 0); break; case 'Midnight': d.setHours(0, 0, 0, 0); break; case 'Noon': d.setHours(12, 0, 0, 0); break; } }); return res ? d : null; } /** * Gets a time value by macor or the macro itself. * @param time The macro or time value. * @returns The time value or a macro that represents a particular time. */ getTimeOrMacroValue(time) { var t = this.getTimeMacroValue(time); return t ? t : time; } // /** // * Gets all date marcos. // * @returns The array of marcos // */ getAllDateMacros() { return this.dateMacros; } /** * Gets all time marcos. * @returns The array of marcos */ getAllTimeMacros() { return this.timeMacros; } } /** Contains several constant definitions for expressions tag */ var ExprTag; (function (ExprTag) { /** Unknown expression */ ExprTag[ExprTag["Unknown"] = 0] = "Unknown"; /** Constant expression */ ExprTag[ExprTag["Constant"] = 1] = "Constant"; /** Entity attributre expression */ ExprTag[ExprTag["EntityAttribute"] = 2] = "EntityAttribute"; /** Parent entity attribute expression */ ExprTag[ExprTag["ParentEntityAttribute"] = 3] = "ParentEntityAttribute"; /** Agrregate function expression */ ExprTag[ExprTag["AggregateFunction"] = 4] = "AggregateFunction"; /** Parent column expression */ ExprTag[ExprTag["ParentColumn"] = 5] = "ParentColumn"; /** Query expression */ ExprTag[ExprTag["Query"] = 11] = "Query"; /** Custom sql expression */ ExprTag[ExprTag["CustomSql"] = 21] = "CustomSql"; })(ExprTag || (ExprTag = {})); /** * Represents expression object. */ class Expression { /** The default constructor. */ constructor(parent) { this.parent = parent; /** The tag. */ this.tag = ExprTag.Constant; /** The data kind. */ this.kind = DataKind.Scalar; /** The data type. */ this.dataType = DataType.Unknown; /** The value. */ this._value = ''; //DO NOT forget remove any /** * The distinct option. (e.g `SELECT DISTINCT`) */ this.distinct = false; this._isDefaultValue = false; this.getText = () => this.text || this._value; this.args = new Array(); } getParent() { return this.parent; } getModel() { return this.parent.getQuery().getModel(); } /** * Loads expression from its JSON representation object. * @param model The Data Model. * @param data The JSON representation object. */ loadFromData(model, data) { if (data) { this.tag = data.tag; if (data.val) { this._value = data.val; this.text = data.txt; } else if (data.id) { this.kind = DataKind.Attribute; this._value = data.id; this.text = data.txt; } if (typeof data.dtype !== 'undefined') { this.dataType = data.dtype; } if (this.tag == ExprTag.EntityAttribute || this.tag == ExprTag.ParentEntityAttribute) { this.kind = DataKind.Attribute; //We don't load other info for EntityAttr } else { if (typeof data.kind !== 'undefined') { this.kind = data.kind; } if (data.query) { this.subQuery = this.parent.getQuery().createSubQuery(); this.subQuery.loadFromDataOrJson(data.query); } if (typeof data.distinct !== 'undefined') { this.distinct = data.distinct; } if (data.func) { this.func = data.func; if (data.args) { for (let i = 0; i < data.args.length; i++) { const arg = new Expression(this.parent); arg.loadFromData(model, data.args[i]); this.args.push(arg); } } } if (data.sql) { this.sql = data.sql; this.baseAttrId = data.baseAttrId; } } } } /** * Saves expression from JSON representation object. * @returns The JSON representation object. */ saveToData() { let obj = { tag: this.tag }; obj.dtype = this.dataType; //Do not save other info for EntityAttr if (this.tag == ExprTag.EntityAttribute || this.tag == ExprTag.ParentEntityAttribute) { if (this._value) { obj.id = this._value; obj.val = this._value; } if (this.text) { obj.txt = this.text; } //Do not save other info for EntityAttr } else { if (this.subQuery) { obj.query = this.subQuery.toJSONData(); } if (typeof this.kind !== "undefined") { obj.kind = this.kind; } if (this._value) { obj.val = this._value; } if (this.text) { obj.txt = this.text; } if (this.distinct) { obj.distinct = this.distinct; } if (this.func) { obj.func = this.func; obj.args = []; for (let i = 0; i < this.args.length; i++) { obj.args.push(this.args[i].saveToData()); } } if (this.sql) { obj.sql = this.sql; obj.baseAttrId = this.baseAttrId; } } return obj; } getIndex() { return this.parent.getExpressionIndex(this); } get value() { return this._value; } setValue(val, silent = false) { this.setContent(val, undefined, silent); } setContent(val, txt, silent = false) { let oldValue = this.value; if (this.kind == DataKind.Attribute && val) { const attr = this.getModel().getAttributeById(val); if (!attr) { throw "No such entity attribute:" + val; } this.dataType = attr.dataType; } this._value = val; if (typeof txt !== 'undefined') { this.text = txt; } else if (typeof this.text !== 'undefined') { this.text = val; } this._isDefaultValue = false; if (!silent) { this.parent.expressionChanged(this, oldValue); } } isEmpty() { return !(this.text || this._value); } hasText() { return this.text && this.text.length > 0; } tryCopyValueFrom(expr) { if (this.kind == expr.kind) { if (this.data