diff --git a/components/persistor/.gitignore b/components/persistor/.gitignore index afcf6f7a..14d5b447 100644 --- a/components/persistor/.gitignore +++ b/components/persistor/.gitignore @@ -7,4 +7,5 @@ coverage dist .nyc_output test/supertype/*.js +lib/utils/*.js lib/persistable.js \ No newline at end of file diff --git a/components/persistor/lib/api.ts b/components/persistor/lib/api.ts index 0f9400c9..2cefe98f 100644 --- a/components/persistor/lib/api.ts +++ b/components/persistor/lib/api.ts @@ -11,15 +11,16 @@ */ import { PersistorTransaction, RemoteDocConnectionOptions } from './types'; +import { PersistorCtx } from './knex/PersistorCtx'; +import { PersistorUtils } from './utils/PersistorUtils'; module.exports = function (PersistObjectTemplate, baseClassForPersist) { const moduleName = `persistor/lib/api`; let supertypeRequire = require('@haventech/supertype'); let statsDHelper = supertypeRequire.StatsdHelper; - var Promise = require('bluebird'); var _ = require('underscore'); - + function getTime() { return process.hrtime(); } @@ -165,10 +166,11 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { * @param {object} idMap id mapper for cached objects * @param {bool} isRefresh force load * @param {object} logger objecttemplate logger + * @param {date} asOfDate load objects based on the date provided * @returns {object} * @legacy Use persistorFetchById instead */ - template.getFromPersistWithId = async function (id, cascade, isTransient, idMap, isRefresh, logger) { + template.getFromPersistWithId = async function (id, cascade, isTransient, idMap, isRefresh, logger, asOfDate) { const functionName = 'getFromPersistWithId'; (logger || PersistObjectTemplate.logger).debug({ module: moduleName, @@ -184,7 +186,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { let getQuery = (dbType == PersistObjectTemplate.DB_Mongo ? PersistObjectTemplate.getFromPersistWithMongoId(template, id, cascade, isTransient, idMap, logger) : - PersistObjectTemplate.getFromPersistWithKnexId(template, id, cascade, isTransient, idMap, isRefresh, logger)); + PersistorCtx.checkAndExecuteWithContext(asOfDate, PersistObjectTemplate.getFromPersistWithKnexId.bind(PersistObjectTemplate, template, id, cascade, isTransient, idMap, isRefresh, logger))); const name = 'getFromPersistWithId'; return getQuery @@ -210,10 +212,11 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { * @param {object} idMap id mapper for cached objects * @param {bool} options {@TODO} * @param {object} logger objecttemplate logger + * @param {date} asOfDate load objects based on the date provided * @returns {object} * @legacy in favor of persistorFetchByQuery */ - template.getFromPersistWithQuery = async function (query, cascade, start, limit, isTransient, idMap, options, logger) { + template.getFromPersistWithQuery = async function (query, cascade, start, limit, isTransient, idMap, options, logger, asOfDate) { const functionName = 'getFromPersistWithQuery'; (logger || PersistObjectTemplate.logger).debug({ module: moduleName, @@ -228,7 +231,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { let getQuery = (dbType == PersistObjectTemplate.DB_Mongo ? PersistObjectTemplate.getFromPersistWithMongoQuery(template, query, cascade, start, limit, isTransient, idMap, options, logger) : - PersistObjectTemplate.getFromPersistWithKnexQuery(null, template, query, cascade, start, limit, isTransient, idMap, options, undefined, undefined, logger)); + PersistorCtx.checkAndExecuteWithContext(asOfDate, PersistObjectTemplate.getFromPersistWithKnexQuery.bind(PersistObjectTemplate, null, template, query, cascade, start, limit, isTransient, idMap, options, undefined, undefined, logger))); const name = 'getFromPersistWithQuery'; @@ -300,7 +303,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { var dbType = persistObjectTemplate.getDB(persistObjectTemplate.getDBAlias(template.__collection__)).type; let fetchQuery = (dbType == persistObjectTemplate.DB_Mongo ? persistObjectTemplate.getFromPersistWithMongoId(template, id, options.fetch, options.transient, null, options.logger) : - persistObjectTemplate.getFromPersistWithKnexId(template, id, options.fetch, options.transient, null, null, options.logger, options.enableChangeTracking, options.projection)); + PersistorCtx.checkAndExecuteWithContext(options.asOfDate, persistObjectTemplate.getFromPersistWithKnexId.bind(persistObjectTemplate, template, id, options.fetch, options.transient, null, null, options.logger, options.enableChangeTracking, options.projection))); const name = 'persistorFetchById'; return fetchQuery @@ -374,9 +377,9 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { let fetchQuery = (dbType == persistObjectTemplate.DB_Mongo ? persistObjectTemplate.getFromPersistWithMongoQuery(template, query, options.fetch, options.start, options.limit, options.transient, options.order, options.order, logger) : - persistObjectTemplate.getFromPersistWithKnexQuery(null, template, query, options.fetch, options.start, + PersistorCtx.checkAndExecuteWithContext(options.asOfDate, persistObjectTemplate.getFromPersistWithKnexQuery.bind(persistObjectTemplate, null, template, query, options.fetch, options.start, options.limit, options.transient, null, options.order, - undefined, undefined, logger, options.enableChangeTracking, options.projection)); + undefined, undefined, logger, options.enableChangeTracking, options.projection))); const name = 'persistorFetchByQuery'; return fetchQuery @@ -1396,7 +1399,7 @@ module.exports = function (PersistObjectTemplate, baseClassForPersist) { templates.push(template); } } - return Promise.map(templates, action, { concurrency: concurrency || 1 }); + return PersistorUtils.asyncMap(templates, concurrency || 1, action); } }; diff --git a/components/persistor/lib/knex/PersistorCtx.ts b/components/persistor/lib/knex/PersistorCtx.ts new file mode 100644 index 00000000..56ab5bd9 --- /dev/null +++ b/components/persistor/lib/knex/PersistorCtx.ts @@ -0,0 +1,65 @@ +import { AsyncLocalStorage } from 'async_hooks' + +type CtxProps = { name: string, properties: {} } + +class ExecutionCtx { + private readonly _asOfDate; + + constructor(asOfDate: Date) { + this._asOfDate = asOfDate + } + + get asOfDate() { + return this._asOfDate; + } +} + +export class PersistorCtx { + static persistorExnCtxKey = '#persistor-exn-ctx'; + static persistorCacheCtxKey = '#persistor-cache-ctx'; + private static _asyncLocalStorage: AsyncLocalStorage; + + private static get asyncLocalStorage() { + return this._asyncLocalStorage || (this._asyncLocalStorage = new AsyncLocalStorage()); + } + + static checkAndExecuteWithContext(asOfDate: Date, callback: () => any ) { + if (!asOfDate) { + return callback(); + } + + const ctxProps = { + name: `${new Date().getTime()}`, + properties: { [this.persistorExnCtxKey]: new ExecutionCtx(asOfDate) }, + }; + return this.asyncLocalStorage.run(ctxProps, async () => { + return await callback(); + }); + } + + static get executionCtx() { + if (!this._asyncLocalStorage) { + return; + } + const store = this.asyncLocalStorage.getStore() as CtxProps; + if (!store) { + return; + } + const exnCtx: ExecutionCtx = store.properties[this.persistorExnCtxKey]; + return exnCtx; + } + + static set setExecutionContext(asyncLocalStorage: AsyncLocalStorage) { + const store = asyncLocalStorage.getStore() as CtxProps; + if (!store) { + return; + } + + const exnCtx: ExecutionCtx = store.properties[this.persistorCacheCtxKey]; + if (!exnCtx) { + throw Error(`ExecutionCtx can only be set from outside with #persistor-cache-ctx property in the store.`) + } + + this._asyncLocalStorage = asyncLocalStorage; + } +} \ No newline at end of file diff --git a/components/persistor/lib/knex/db.ts b/components/persistor/lib/knex/db.ts index 2d2144b1..04694c30 100644 --- a/components/persistor/lib/knex/db.ts +++ b/components/persistor/lib/knex/db.ts @@ -1,8 +1,9 @@ import {RemoteDocService} from "../remote-doc/RemoteDocService"; +import { PersistorCtx } from './PersistorCtx'; +import { PersistorUtils } from '../utils/PersistorUtils'; module.exports = function (PersistObjectTemplate) { const moduleName = `persistor/lib/knex/db`; - var Promise = require('bluebird'); var _ = require('underscore'); var processedList = []; @@ -21,16 +22,45 @@ module.exports = function (PersistObjectTemplate) { PersistObjectTemplate.getPOJOsFromKnexQuery = function (template, joins, queryOrChains, options, map, logger, projection) { var tableName = this.dealias(template.__table__); + var historyTableName, historyTableAlias; + var historySeqKeys = []; + if (PersistorCtx.executionCtx?.asOfDate && template.__schema__.audit === 'v2') { + historyTableName = tableName + '___history'; + if ('_id' in queryOrChains) { + queryOrChains["_snapshot_id"] = queryOrChains["_id"] + delete queryOrChains["_id"]; + } + + queryOrChains["lastUpdatedTime"] = {$lte: PersistorCtx.executionCtx?.asOfDate}; + } + var knex = this.getDB(this.getDBAlias(template.__table__)).connection(tableName); const functionName = 'getPOJOsFromKnexQuery'; // tack on outer joins. All our joins are outerjoins and to the right. There could in theory be // foreign keys pointing to rows that no longer exists - var select = knex.select(getColumnNames.bind(this, template)()).from(tableName); + var select = knex.select(getColumnNames.bind(this, template, historySeqKeys)()).from((historyTableName || tableName) + ' as ' + tableName); joins.forEach(function (join) { - select = select.leftOuterJoin(this.dealias(join.template.__table__) + ' as ' + join.alias, - join.alias + '.' + join.parentKey, - this.dealias(template.__table__) + '.' + join.childKey); + let joinTable = this.dealias(join.template.__table__); + let historyJoinTable, additionalCondition; + if (PersistorCtx.executionCtx?.asOfDate && join.template.__schema__.audit === 'v2') { + historyJoinTable = joinTable + '___history'; + } + const parentKey = this.dealias(template.__table__) + '.' + join.childKey + select = select.leftOuterJoin( (historyJoinTable || joinTable) + ' as ' + join.alias, function() { + if (PersistorCtx.executionCtx?.asOfDate && join.template.__schema__.audit === 'v2') { + const cond = this.on(join.alias + '._snapshot_id', '=', parentKey) + + const dt = PersistorCtx.executionCtx?.asOfDate + cond.andOn(knex.client.raw(join.alias + '.' + '"lastUpdatedTime"' + ` <= '${dt.toISOString()}'`)) + } + else + { + const cond = this.on(join.alias + '.' + join.parentKey, '=', parentKey) + } + }) + + }.bind(this)); // execute callback to chain on filter functions or convert mongo style filters @@ -54,7 +84,14 @@ module.exports = function (PersistObjectTemplate) { select = ascending.reduce((result, column) => select.orderBy(column), select); if (descending.length) select = descending.reduce((result, column) => select.orderBy(column, 'desc'), select); - } + } + if (historySeqKeys.length) { + const predicate = historySeqKeys.reduce((predicate,curr)=> (predicate[curr]=1,predicate),{}); + select = this.getDB(this.getDBAlias(template.__table__)).connection.from({ "___historyAlias": select}) + .where(predicate) + } + + if (options && options.limit) { select = select.limit(options.limit); select = select.offset(0) @@ -69,7 +106,7 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'pre', - template: template.__name__, + template: template.__name__, query: queryOrChains } }); @@ -90,8 +127,8 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'post', - count: res.length, - template: template.__name__, + count: res.length, + template: template.__name__, query: queryOrChains } }); @@ -117,7 +154,7 @@ module.exports = function (PersistObjectTemplate) { throw err; } - function getColumnNames(template) { + function getColumnNames(template, historySeqKeys) { var cols = []; var self = this; @@ -138,10 +175,15 @@ module.exports = function (PersistObjectTemplate) { function asStandard(template, prefix) { as(template, prefix, '__version__', {type: {}, persist: true, enumerable: true}); as(template, prefix, '_template', {type: {}, persist: true, enumerable: true}); - as(template, prefix, '_id', {type: {}, persist: true, enumerable: true}); + + if (PersistorCtx.executionCtx?.asOfDate && template.__schema__.audit === 'v2') { + lastUpdatedSeq(template, prefix, '_snapshot_id', {type: {}, persist: true, enumerable: true}); + } + else { + as(template, prefix, '_id', {type: {}, persist: true, enumerable: true}); + } } - - function as(template, prefix, prop, defineProperty) { + function asChecks(template, prefix, prop, defineProperty) { var schema = template.__schema__; var type = defineProperty.type; var of = defineProperty.of; @@ -158,7 +200,26 @@ module.exports = function (PersistObjectTemplate) { throw new Error(type.__name__ + '.' + prop + ' is missing a parents schema entry'); prop = schema.parents[prop].id; } - cols.push(prefix + '.' + prop + ' as ' + (prefix ? prefix + '___' : '') + prop); + return prop; + } + function as(template, prefix, prop, defineProperty) { + const propDecorated = asChecks(template, prefix, prop, defineProperty) + if (!propDecorated) { + return; + } + cols.push(prefix + '.' + propDecorated + ' as ' + (prefix ? prefix + '___' : '') + propDecorated); + } + function lastUpdatedSeq(template, prefix, prop, defineProperty) { + const propDecorated = asChecks(template, prefix, prop, defineProperty) + if (!propDecorated) { + return; + } + cols.push(prefix + '.' + propDecorated + ' as ' + (prefix ? prefix + '___' : '') + propDecorated.replace('_snapshot','')); + const lastUpdatedSeq =`rank() over(partition by "${prefix}"."${propDecorated}" order by "${prefix}"."lastUpdatedTime" desc) "${prefix}___lastUpdatedSeq"`; + var knex = self.getDB(self.getDBAlias(template.__table__)).connection(tableName); + // historySeqKeys.push(`${prefix}.${propDecorated}`); + historySeqKeys.push(`${prefix}___lastUpdatedSeq`); + cols.push(knex.client.raw(lastUpdatedSeq)); } function getPropsRecursive(template, map?) { @@ -316,8 +377,8 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'post', - count: res, - table: tableName, + count: res, + table: tableName, id: obj._id } }); @@ -344,7 +405,7 @@ module.exports = function (PersistObjectTemplate) { if (txn.queriesToNotify) { generateNotifyQueries(template, txn.queriesToNotify, knex.where({_id: id}).delete().toString()); } - + } return knex.where({_id: id}).delete(); }; @@ -373,11 +434,11 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'pre', - txn: (txn ? txn.id + ' ' : '-#- '), + txn: (txn ? txn.id + ' ' : '-#- '), type: (updateID ? 'updating ' : 'insert '), - template: obj.__template__.__name__, - id: obj.__id__, - _id: obj._id, + template: obj.__template__.__name__, + id: obj.__id__, + _id: obj._id, __version__: pojo.__version__ } }); @@ -432,7 +493,7 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'updateConflict', - txn: (txn ? txn.id : '-#-'), + txn: (txn ? txn.id : '-#-'), id: obj.__id__, __version__: origVer } }); @@ -466,8 +527,8 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'post', - template: obj.__template__.__name__, - table: obj.__template__.__table__, + template: obj.__template__.__name__, + table: obj.__template__.__table__, __version__: obj.__version__ } }); @@ -514,28 +575,42 @@ module.exports = function (PersistObjectTemplate) { var _newFields = {}; return Promise.resolve() - .then(buildTable.bind(this)) + .then(buildTable.bind(this, aliasedTableName)) .then(addComments.bind(this, tableName)) - .then(synchronizeIndexes.bind(this, tableName, template)); - - function buildTable() { - return knex.schema.hasTable(tableName).then(function (exists) { - if (!exists) { - if (!!changeNotificationCallback) { - if (typeof changeNotificationCallback !== 'function') - throw new Error('persistor can only notify the table changes through a callback'); - changeNotificationCallback('A new table, ' + tableName + ', has been added\n'); - } - return PersistObjectTemplate._createKnexTable(template, aliasedTableName); + .then(synchronizeIndexes.bind(this, tableName, template)) + .then(function (response) { + if (template.__schema__.audit === 'v2') { + return Promise.resolve() + .then(buildTable.bind(this, aliasedTableName + '___history')) + .then(synchronizeIndexes.bind(this, aliasedTableName + '___history', template));; } else { - return discoverColumns(tableName).then(function () { - fieldChangeNotify(changeNotificationCallback, tableName); - return knex.schema.table(tableName, columnMapper.bind(this)) - }.bind(this)); + return response; } - }.bind(this)) - } + }.bind(this)); + + + function buildTable(aliasedTableName) { + var tableName = this.dealias(aliasedTableName); + return knex.schema.hasTable(tableName).then(function (exists) { + if (!exists) { + if (!!changeNotificationCallback) { + if (typeof changeNotificationCallback !== 'function') + throw new Error('persistor can only notify the table changes through a callback'); + changeNotificationCallback('A new table, ' + tableName + ', has been added\n'); + } + return PersistObjectTemplate._createKnexTable(template, aliasedTableName); + } + else { + return discoverColumns(tableName).then(async function () { + fieldChangeNotify(changeNotificationCallback, tableName); + const [c, l] = await Promise.all([knex.schema.hasColumn(tableName, 'createdTime'), + knex.schema.hasColumn(tableName, 'lastUpdatedTime')]) + return knex.schema.table(tableName, columnMapper.bind(this, tableName, [c,l])) + }.bind(this)); + } + }.bind(this)); + } function fieldChangeNotify(callBack, table) { if (!callBack) return; @@ -549,7 +624,7 @@ module.exports = function (PersistObjectTemplate) { callBack('Following fields are being added to ' + table + ' table: \n ' + fieldsChanged.slice(1, fieldsChanged.length)); } } - function columnMapper(table) { + function columnMapper(table, createdTime, lastUpdatedTime) { for (var prop in _newFields) { var defineProperty = props[prop]; @@ -573,6 +648,13 @@ module.exports = function (PersistObjectTemplate) { } else table.text(prop); } + + if (!createdTime) { + table.timestamp('createdTime'); + } + if (!lastUpdatedTime) { + table.timestamp('lastUpdatedTime'); + } } function addComments(table) { return knex(table).columnInfo().then(function (info) { @@ -742,7 +824,7 @@ module.exports = function (PersistObjectTemplate) { function synchronizeIndexes(tableName, template) { - var aliasedTableName = template.__table__; + var aliasedTableName = tableName || template.__table__; tableName = this.dealias(aliasedTableName); const functionName = synchronizeIndexes.name; @@ -791,20 +873,40 @@ module.exports = function (PersistObjectTemplate) { response = JSON.parse(record[0][schemaField]); } _dbschema = response; - return [response, template.__name__]; + const historyPostfix = tableName.match(/___history$/) ? '___History' : ''; + return [response, template.__name__ + historyPostfix]; }) }; - var loadTableDef = function(dbschema, tableName) { + var loadTableDef = function([dbschema, tableName]) { if (!dbschema[tableName]) dbschema[tableName] = {}; return [dbschema, schema, tableName]; }; - var diffTable = function(dbschema, schema, tableName) { + var diffTable = function([dbschema, schema, tableName]) { var dbTableDef = dbschema[tableName]; - var memTableDef = schema[tableName]; + var memTableDef = schema[tableName.replace( '___History', '')]; var track = {add: [], change: [], delete: []}; + if (tableName.match(/___History/)) { + var keyName = 'idx_' + tableName + '_fk_snapshot_id'; + memTableDef.indexes = memTableDef.indexes || []; + memTableDef.indexes.push({ + name: keyName, + def: { + columns: ['_snapshot_id'], + type: 'index' + } + }) ; + keyName = 'idx_' + tableName + '_lastupdatedtime'; + memTableDef.indexes.push({ + name: keyName, + def: { + columns: ['lastUpdatedTime'], + type: 'index' + } + }) ; + } _diff(dbTableDef, memTableDef, 'delete', false, function (_dbIdx, memIdx) { return !memIdx; }, _diff(memTableDef, dbTableDef, 'change', false, function (memIdx, dbIdx) { @@ -841,8 +943,8 @@ module.exports = function (PersistObjectTemplate) { var generateChanges = function (localtemplate, _value) { return _.reduce(localtemplate.__children__, function (_curr, o) { return Promise.resolve() - .then(loadTableDef.bind(this, _dbschema, o.__name__)) - .spread(diffTable) + .then(loadTableDef.bind(this, [_dbschema, o.__name__])) + .then(diffTable.bind(this)) .then(generateChanges.bind(this, o)); }, {}); }; @@ -897,14 +999,14 @@ module.exports = function (PersistObjectTemplate) { } } /** - * This function loops through index changes (add/change/delete) and - * executes them one by one. In case of errors, while executing indexes, - * we will log a warning message. This approach handles each index at - * its own merit without impacting the behavior or another index. - * This is a change from previous code where were trying to run index - * changes together in a transaction. - * @param operation - * @param diffs + * This function loops through index changes (add/change/delete) and + * executes them one by one. In case of errors, while executing indexes, + * we will log a warning message. This approach handles each index at + * its own merit without impacting the behavior or another index. + * This is a change from previous code where were trying to run index + * changes together in a transaction. + * @param operation + * @param diffs */ async function syncIndexesForHierarchy (operation, diffs) { for (const _key in diffs[operation]) { @@ -924,7 +1026,7 @@ module.exports = function (PersistObjectTemplate) { if (operation === 'add') { try { await knex.schema.table(tableName, async function (table) { - // This table function callback does not necessarily need to be + // This table function callback does not necessarily need to be // an async callback, but making the callback async/await as a guard. await table[type](columns, name); }); @@ -937,8 +1039,9 @@ module.exports = function (PersistObjectTemplate) { type = type.replace(/index/, 'Index'); type = type.replace(/unique/, 'Unique'); try { + await knex.schema.table(tableName, async function (table) { - // This table function callback does not necessarily need to be + // This table function callback does not necessarily need to be // an async callback, but making the callback async/await as a guard. await table['drop' + type]([], name); }); @@ -950,7 +1053,7 @@ module.exports = function (PersistObjectTemplate) { else { try { await knex.schema.table(tableName, async function (table) { - // This table function callback does not necessarily need to be + // This table function callback does not necessarily need to be // an async callback, but making the callback async/await as a guard. await table[type](columns, name); }); @@ -959,7 +1062,7 @@ module.exports = function (PersistObjectTemplate) { }; } } - catch(error) { + catch(error) { const logger = PersistObjectTemplate && PersistObjectTemplate.logger; if (logger) { logger.warn({ @@ -975,8 +1078,8 @@ module.exports = function (PersistObjectTemplate) { } return Promise.all([ - syncIndexesForHierarchy('delete', dbChanges), - syncIndexesForHierarchy('add', dbChanges), + syncIndexesForHierarchy('delete', dbChanges), + syncIndexesForHierarchy('add', dbChanges), syncIndexesForHierarchy('change', dbChanges) ]); }; @@ -1004,7 +1107,7 @@ module.exports = function (PersistObjectTemplate) { sequence_id = ++record[0].sequence_id; } _.each(_changes, function (_o, chgKey) { - response[chgKey] = schema[chgKey]; + response[chgKey] = schema[chgKey.replace( '___History', '')]; }); return knex(schemaTable).insert({ @@ -1016,8 +1119,8 @@ module.exports = function (PersistObjectTemplate) { return Promise.resolve() .then(loadSchema.bind(this, tableName)) - .spread(loadTableDef) - .spread(diffTable) + .then(loadTableDef.bind(this)) + .then(diffTable.bind(this)) .then(generateChanges.bind(this, template)) .then(mergeChanges) .then(applyTableChanges) @@ -1066,6 +1169,13 @@ module.exports = function (PersistObjectTemplate) { queriesToNotify[template.__name__].queries.push(query); } + function addAuditColumns(knex, table, tableName, column) { + return knex.schema.hasColumn(tableName, column) + .then(exists => { + return !exists && table.timestamp(column) + }); + } + PersistObjectTemplate.persistTouchKnex = function(obj, txn, logger) { const functionName = 'persistTouchKnex'; (logger || this.logger).debug({ @@ -1074,7 +1184,7 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'pre', - template: obj.__template__.__name__, + template: obj.__template__.__name__, table: obj.__template__.__table__ } }); @@ -1094,7 +1204,7 @@ module.exports = function (PersistObjectTemplate) { category: 'milestone', data: { activity: 'post', - template: obj.__template__.__name__, + template: obj.__template__.__name__, table: obj.__template__.__table__ } }); @@ -1124,15 +1234,25 @@ module.exports = function (PersistObjectTemplate) { var knex = this.getDB(this.getDBAlias(collection)).connection; var tableName = this.dealias(collection); - return knex.schema.createTable(tableName, createColumns.bind(this)); - - function createColumns(table) { - table.string('_id').primary(); + return knex.schema.createTable(tableName, createColumns.bind(this)) + + async function createColumns(table) { + if (collection.match(/___history/)) { + table.string('_id'); + table.string('_snapshot_id'); + table.primary(['_id']) + } + else { + table.string('_id').primary(); + } table.string('_template'); table.biginteger('__version__'); + var columnMap = {}; recursiveColumnMap.call(this, template); + table.timestamp('createdTime'); + table.timestamp('lastUpdatedTime'); function mapTableAndIndexes(table, props, schema) { for (var prop in props) { @@ -1159,6 +1279,7 @@ module.exports = function (PersistObjectTemplate) { columnMap[prop] = true; } } + } function recursiveColumnMap(childTemplate) { @@ -1428,16 +1549,14 @@ module.exports = function (PersistObjectTemplate) { } // Walk through the dirty objects - function processSaves() { - return Promise.map(_.toArray(dirtyObjects), function (obj) { + async function processSaves() { + await PersistorUtils.asyncMap(_.toArray(dirtyObjects), PersistObjectTemplate.concurrency, function (obj) { delete dirtyObjects[obj.__id__]; // Once scheduled for update remove it. return callSave(obj).then(generateChanges.bind(this, obj, obj.__version__ === 1 ? 'insert' : 'update')); - }.bind(this), {concurrency: PersistObjectTemplate.concurrency}).then (function () { - if (_.toArray(dirtyObjects). length > 0) { - return processSaves.call(this); - } - }); - + }.bind(this)); + if (_.toArray(dirtyObjects).length > 0) { + return processSaves.call(this); + } function callSave(obj) { return (obj.__template__ && obj.__template__.__schema__ ? obj.persistSave(persistorTransaction, logger) @@ -1446,15 +1565,15 @@ module.exports = function (PersistObjectTemplate) { } - function processDeletes() { - return Promise.map(_.toArray(deletedObjects), function (obj) { + async function processDeletes() { + await PersistorUtils.asyncMap(_.toArray(deletedObjects), PersistObjectTemplate.concurrency, function (obj) { delete deletedObjects[obj.__id__]; // Once scheduled for update remove it. return callDelete(obj).then(generateChanges.bind(this, obj, 'delete')); - }.bind(this), {concurrency: PersistObjectTemplate.concurrency}).then (function () { - if (_.toArray(deletedObjects). length > 0) { - return processDeletes.call(this); - } - }); + }.bind(this)); + + if (_.toArray(deletedObjects). length > 0) { + return processDeletes.call(this); + } function callDelete(obj) { return (obj.__template__ && obj.__template__.__schema__ @@ -1463,17 +1582,16 @@ module.exports = function (PersistObjectTemplate) { } } - function processDeleteQueries() { - return Promise.map(_.toArray(deleteQueries), function (obj) { + async function processDeleteQueries() { + await PersistorUtils.asyncMap(_.toArray(deleteQueries), PersistObjectTemplate.concurrency, function (obj) { delete deleteQueries[obj.name]; // Once scheduled for update remove it. return (obj.template && obj.template.__schema__ ? PersistObjectTemplate.deleteFromKnexQuery(obj.template, obj.queryOrChains, persistorTransaction, logger) : true) - }.bind(this), {concurrency: PersistObjectTemplate.concurrency}).then (function () { - if (_.toArray(deleteQueries). length > 0) { - return processDeleteQueries.call(this); - } - }); + }.bind(this)) + if (_.toArray(deleteQueries). length > 0) { + return processDeleteQueries.call(this); + } } @@ -1496,7 +1614,7 @@ module.exports = function (PersistObjectTemplate) { // Walk through the touched objects function processTouches() { - return Promise.map(_.toArray(touchObjects), function (obj) { + return PersistorUtils.asyncMap(_.toArray(touchObjects), PersistObjectTemplate.concurrency, function (obj) { return (obj.__template__ && obj.__template__.__schema__ && !savedObjects[obj.__id__] ? obj.persistTouch(persistorTransaction, logger) : true) diff --git a/components/persistor/lib/knex/query.ts b/components/persistor/lib/knex/query.ts index ebb8bc1a..38af9100 100644 --- a/components/persistor/lib/knex/query.ts +++ b/components/persistor/lib/knex/query.ts @@ -1,9 +1,9 @@ import { RemoteDocService } from '../remote-doc/RemoteDocService'; import { PersistorUtils } from '../utils/PersistorUtils'; +import { PersistorCtx } from './PersistorCtx'; module.exports = function (PersistObjectTemplate) { const moduleName = `persistor/lib/knex/query`; - var Promise = require('bluebird'); var _ = require('underscore'); PersistObjectTemplate.concurrency = 10; @@ -83,7 +83,6 @@ module.exports = function (PersistObjectTemplate) { options.offset = skip; if (limit) options.limit = limit; - // Request to do entire processing to be executed right now or as part of a request queue var request = function () { return Promise.resolve(true) @@ -127,19 +126,17 @@ module.exports = function (PersistObjectTemplate) { PersistObjectTemplate.resolveRecursiveRequests = function (requests, results) { return processRequests(); - function processRequests() { + async function processRequests() { var segLength = requests.length; - //console.log("Processing " + segLength + " promises " + PersistObjectTemplate.concurrency); - return Promise.map(requests, function (request, _ix) { + + await PersistorUtils.asyncMap(requests, PersistObjectTemplate.concurrency || 1, function (request, _ix) { return request(); - }, {concurrency: PersistObjectTemplate.concurrency}) - .then(function () { - requests.splice(0, segLength); - if (requests.length > 0) - return processRequests(); - else - return results; - }) + }.bind(this)) + requests.splice(0, segLength); + if (requests.length > 0) + return processRequests(); + else + return results; } } @@ -497,7 +494,7 @@ module.exports = function (PersistObjectTemplate) { var foreignFilterValue = schema.children[prop].filter ? schema.children[prop].filter.value : null; // Construct foreign key query var query = {}; - var options = defineProperty.queryOptions || {sort: {_id: 1}}; + var options = defineProperty.queryOptions || {sort: PersistorCtx.executionCtx?.asOfDate ? {_snapshot_id:1} : {_id: 1}}; var limit = options.limit || null; query[schema.children[prop].id] = obj._id; if (foreignFilterKey) { diff --git a/components/persistor/lib/knex/update.ts b/components/persistor/lib/knex/update.ts index c5d7dc5b..6b53e4a9 100644 --- a/components/persistor/lib/knex/update.ts +++ b/components/persistor/lib/knex/update.ts @@ -4,7 +4,6 @@ import { PersistorUtils } from '../utils/PersistorUtils'; module.exports = function (PersistObjectTemplate) { const moduleName = `persistor/lib/knex/update`; - var Promise = require('bluebird'); var _ = require('underscore'); /** diff --git a/components/persistor/lib/mongo/db.ts b/components/persistor/lib/mongo/db.ts index d118291f..bc415ce9 100644 --- a/components/persistor/lib/mongo/db.ts +++ b/components/persistor/lib/mongo/db.ts @@ -1,3 +1,5 @@ +import { PersistorCtx } from "../knex/PersistorCtx"; + module.exports = function (PersistObjectTemplate) { const moduleName = `persistor/lib/mongo/db`; /* Mongo implementation of save */ @@ -65,7 +67,7 @@ module.exports = function (PersistObjectTemplate) { var collection = db.collection(this.dealias(template.__collection__)); options = options || {}; if (!options.sort) - options.sort = {_id:1}; + options.sort = PersistorCtx.executionCtx?.asOfDate ? {_snapshot_id:1} : {_id:1}; if (typeof(options) === "function") { return collection.find(query, undefined, options).toArray(); diff --git a/components/persistor/lib/mongo/query.ts b/components/persistor/lib/mongo/query.ts index f09f63e8..79f46209 100644 --- a/components/persistor/lib/mongo/query.ts +++ b/components/persistor/lib/mongo/query.ts @@ -1,7 +1,6 @@ module.exports = function (PersistObjectTemplate) { const moduleName = `persistor/lib/mongo/query`; - var Promise = require('bluebird'); - + PersistObjectTemplate.getFromPersistWithMongoId = function (template, id, cascade, isTransient, idMap, _logger) { return this.getFromPersistWithMongoQuery(template, {_id: PersistObjectTemplate.ObjectID(id.toString())}, cascade, null, null, isTransient, idMap) .then(function(pojos) { return pojos[0] }); diff --git a/components/persistor/lib/mongo/update.ts b/components/persistor/lib/mongo/update.ts index e8fc2c68..5688db64 100644 --- a/components/persistor/lib/mongo/update.ts +++ b/components/persistor/lib/mongo/update.ts @@ -1,7 +1,6 @@ module.exports = function (PersistObjectTemplate) { const moduleName = `persistor/lib/mongo/update`; - var Promise = require('bluebird'); - + /** * Save the object to persistent storage * diff --git a/components/persistor/lib/persistable.ts b/components/persistor/lib/persistable.ts index b7997340..fd8db01d 100644 --- a/components/persistor/lib/persistable.ts +++ b/components/persistor/lib/persistable.ts @@ -216,11 +216,12 @@ export function Persistable>(Base: BC) { * @param {object} idMap id mapper for cached objects * @param {bool} isRefresh force load * @param {object} logger objecttemplate logger + * @param {date} asOfDate load objects based on the date provided * @returns {object} * @legacy Use persistorFetchById instead * @async */ - static getFromPersistWithId(id?, cascade?, isTransient?, idMap?, isRefresh?, logger?) : any{} + static getFromPersistWithId(id?, cascade?, isTransient?, idMap?, isRefresh?, logger?, asOfDate?) : any{} /** * Return an array of objects of this class given a json query @@ -233,11 +234,12 @@ export function Persistable>(Base: BC) { * @param {object} idMap id mapper for cached objects * @param {bool} options {@TODO} * @param {object} logger objecttemplate logger + * @param {date} asOfDate load objects based on the date provided * @returns {object} * @legacy in favor of persistorFetchByQuery * @async */ - static getFromPersistWithQuery(query, cascade?, start?, limit?, isTransient?, idMap?, options?, logger?) : any {} + static getFromPersistWithQuery(query, cascade?, start?, limit?, isTransient?, idMap?, options?, logger?, asOfDate?) : any {} /** * Delete objects given a json query diff --git a/components/persistor/lib/util.ts b/components/persistor/lib/util.ts index 8def11d6..ec196272 100644 --- a/components/persistor/lib/util.ts +++ b/components/persistor/lib/util.ts @@ -1,8 +1,10 @@ module.exports = function (PersistObjectTemplate) { - var Promise = require('bluebird'); var _ = require('underscore'); var schemaValidator = require('tv4'); + schemaValidator.addFormat('date-time', function (data) { + return data instanceof Date && !isNaN(data.valueOf()) + }); PersistObjectTemplate.ObjectID = require('mongodb').ObjectID; @@ -220,7 +222,11 @@ module.exports = function (PersistObjectTemplate) { }, 'enableChangeTracking' : { type: ['boolean', 'null', 'undefined'] - } + }, + 'asOfDate' : { + type: ['object', 'null', 'undefined'], + format: 'date-time' + }, } }, 'commitSchema': { diff --git a/components/persistor/lib/utils/PersistorUtils.ts b/components/persistor/lib/utils/PersistorUtils.ts index e12fd2b4..b1ce7fe0 100644 --- a/components/persistor/lib/utils/PersistorUtils.ts +++ b/components/persistor/lib/utils/PersistorUtils.ts @@ -1,9 +1,34 @@ export class PersistorUtils { - static isRemoteObjectSetToTrue(enableIsRemoteObjectFeature: any, isRemoteObject: any): boolean { if (enableIsRemoteObjectFeature && (enableIsRemoteObjectFeature === true || enableIsRemoteObjectFeature === 'true')) { return isRemoteObject && isRemoteObject === true; } return false; } + + static asyncMap(arr: any[], concurrency: number, callback: any) { + let cnt = arr.length / concurrency; + let p = Promise.resolve([]); + let start = 0; + + for (let i = 0; i < cnt; i++) { + p = p.then((results) => { + let end = start + concurrency; + + return Promise.all(arr.slice(start, end).map(callback)) + .then((eRes) => { + start = end; + results.push.apply(results, eRes); + + return results; + }); + }); + } + + return p; + } + + static sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } } \ No newline at end of file diff --git a/components/persistor/package-lock.json b/components/persistor/package-lock.json index 31e817f0..99aba0fe 100644 --- a/components/persistor/package-lock.json +++ b/components/persistor/package-lock.json @@ -9,7 +9,6 @@ "version": "10.0.0", "dependencies": { "aws-sdk": "2.x", - "bluebird": "x", "knex": "^0.21.0", "mongodb": "^3.5.5", "pg": "8.7.1", @@ -20,7 +19,6 @@ }, "devDependencies": { "@haventech/supertype": "6.x", - "@types/bluebird": "*", "@types/chai": "4.3.0", "@types/mocha": "10.0.0", "@types/node": "16.11.22", @@ -549,12 +547,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "node_modules/@types/bluebird": { - "version": "3.5.20", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.20.tgz", - "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==", - "dev": true - }, "node_modules/@types/chai": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", @@ -945,11 +937,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5913,12 +5900,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@types/bluebird": { - "version": "3.5.20", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.20.tgz", - "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==", - "dev": true - }, "@types/chai": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", @@ -6207,11 +6188,6 @@ "safe-buffer": "^5.1.1" } }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/components/persistor/package.json b/components/persistor/package.json index 3e1f4cdd..e69a460a 100644 --- a/components/persistor/package.json +++ b/components/persistor/package.json @@ -7,7 +7,6 @@ "types": "dist/index.d.ts", "dependencies": { "aws-sdk": "2.x", - "bluebird": "x", "knex": "^0.21.0", "mongodb": "^3.5.5", "pg": "8.7.1", @@ -21,7 +20,6 @@ }, "devDependencies": { "@haventech/supertype": "6.x", - "@types/bluebird": "*", "@types/chai": "4.3.0", "@types/mocha": "10.0.0", "@types/node": "16.11.22", diff --git a/components/persistor/test/persist_banking.js b/components/persistor/test/persist_banking.js index 007e560f..f362e5d6 100644 --- a/components/persistor/test/persist_banking.js +++ b/components/persistor/test/persist_banking.js @@ -16,7 +16,6 @@ PersistObjectTemplate.debug = function(m, t) { } } */ -var Promise = require('bluebird'); var Customer = PersistObjectTemplate.create('Customer', { init: function (first, middle, last) { @@ -354,7 +353,7 @@ describe('Banking Example JS', function () { }).catch(function(e) {done(e)}); }); - it('Accounts have addresses', function (done) { + it('Accounts have addresses 1', function (done) { Account.getFromPersistWithQuery(null, {address: true}).then (function (accounts) { expect(accounts.length).to.equal(2); expect(accounts[0].address.__template__.__name__).to.equal('Address'); diff --git a/components/persistor/test/persist_banking_pgsql.js b/components/persistor/test/persist_banking_pgsql.js index 6719a170..ca491968 100644 --- a/components/persistor/test/persist_banking_pgsql.js +++ b/components/persistor/test/persist_banking_pgsql.js @@ -8,8 +8,9 @@ var sinon = require('sinon'); var LocalStorageDocClient = require('../dist/lib/remote-doc/remote-doc-clients/LocalStorageDocClient').LocalStorageDocClient; var expect = require('chai').expect; var util = require('util'); -var Promise = require('bluebird'); + var _ = require('underscore'); +const { PersistorUtils } = require('../lib/utils/PersistorUtils'); var ObjectTemplate = require('@haventech/supertype').default; var PersistObjectTemplate = require('../dist/index.js')(ObjectTemplate, null, ObjectTemplate); var writing = true; @@ -1170,7 +1171,7 @@ describe('Banking from pgsql Example persist_banking_pgsql', function () { txn2Sam.firstName = 'txn2SamDead'; txn2Sam.setDirty(txn2); txn1.postSave = function () { - Promise.delay(100) + PersistorUtils.sleep(100) .then(function () { // Update will not return because it is requesting a lock on Karen txn1Karen.persistTouch(txn1) // 3 update karen @@ -1187,6 +1188,7 @@ describe('Banking from pgsql Example persist_banking_pgsql', function () { txn2Error = true; }); }; + return PersistObjectTemplate.end(txn1); // 1 - update sam (exc lock) }).catch(function (e) { if (e.message != 'Update Conflict') diff --git a/components/persistor/test/persist_fetch.js b/components/persistor/test/persist_fetch.js index aa0df79d..197aca4a 100644 --- a/components/persistor/test/persist_fetch.js +++ b/components/persistor/test/persist_fetch.js @@ -6,7 +6,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.should(); chai.use(chaiAsPromised); -var Promise = require('bluebird'); + var knexInit = require('knex'); var knex; diff --git a/components/persistor/test/persist_fetch_children.js b/components/persistor/test/persist_fetch_children.js index ee14675b..f856b914 100644 --- a/components/persistor/test/persist_fetch_children.js +++ b/components/persistor/test/persist_fetch_children.js @@ -6,7 +6,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.should(); chai.use(chaiAsPromised); -var Promise = require('bluebird'); + var knexInit = require('knex'); var knex; diff --git a/components/persistor/test/persist_newapi_extend.js b/components/persistor/test/persist_newapi_extend.js index 444b8d3c..f8546380 100644 --- a/components/persistor/test/persist_newapi_extend.js +++ b/components/persistor/test/persist_newapi_extend.js @@ -6,7 +6,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.should(); chai.use(chaiAsPromised); -var Promise = require('bluebird'); + var knexInit = require('knex'); var knex; diff --git a/components/persistor/test/persist_newapi_tests.js b/components/persistor/test/persist_newapi_tests.js index dc4034cf..36d67484 100644 --- a/components/persistor/test/persist_newapi_tests.js +++ b/components/persistor/test/persist_newapi_tests.js @@ -6,8 +6,6 @@ var chaiAsPromised = require('chai-as-promised'); chai.should(); chai.use(chaiAsPromised); -var Promise = require('bluebird'); - var knexInit = require('knex'); var knex; diff --git a/components/persistor/test/persist_parent_subset.js b/components/persistor/test/persist_parent_subset.js index b758f9a2..c764c559 100644 --- a/components/persistor/test/persist_parent_subset.js +++ b/components/persistor/test/persist_parent_subset.js @@ -10,7 +10,7 @@ chai.use(chaiAsPromised); var ObjectTemplate = require('@haventech/supertype').default; var PersistObjectTemplate = require('../dist/index.js')(ObjectTemplate, null, ObjectTemplate); -var Promise = require('bluebird'); + var knexInit = require('knex'); var knex; diff --git a/components/persistor/test/persist_pointintime_tests.js b/components/persistor/test/persist_pointintime_tests.js new file mode 100644 index 00000000..1967904a --- /dev/null +++ b/components/persistor/test/persist_pointintime_tests.js @@ -0,0 +1,455 @@ +var chai = require('chai'), + expect = require('chai').expect; + +var chaiAsPromised = require('chai-as-promised'); + +chai.should(); +chai.use(chaiAsPromised); + + +var knexInit = require('knex'); +const { PersistorUtils } = require('../lib/utils/PersistorUtils.js'); +var knex; + +var schema = {}; +var schemaTable = 'index_schema_history'; +var Phone, Address, Employee, empId, addressId, phoneId, Role, dob; +var PersistObjectTemplate, ObjectTemplate; + +describe('persist newapi tests', function () { + before('drop schema table once per test suit', function () { + knex = knexInit({ + client: 'pg', + debug: true, + connection: { + host: process.env.dbPath, + database: process.env.dbName, + user: process.env.dbUser, + password: process.env.dbPassword, + } + }); + return knex.raw(` + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + END $$; + `); + }) + after('closes the database', function () { + return knex.destroy(); + }); + beforeEach('arrange', function () { + ObjectTemplate = require('@haventech/supertype').default; + PersistObjectTemplate = require('../dist/index.js')(ObjectTemplate, null, ObjectTemplate); + + schema.Employee = {}; + schema.Address = {}; + schema.Phone = {}; + schema.Dept = {}; + schema.Employee.table = 'tx_employee'; + schema.Employee.audit = 'v2'; + schema.Address.table = 'tx_address'; + schema.Address.audit = 'v2'; + schema.Address.tableType = 'reference_data'; + schema.Phone.table = 'tx_phone'; + + schema.Employee.parents = { + homeAddress: { + id: 'address_id', + fetch: true + } + }; + schema.Employee.children = { + roles: { id: 'employee_id', fetch: true } + }; + + schema.Employee.enableChangeTracking = true; + + schema.Role = {}; + schema.Role.table = 'tx_role'; + schema.Role.audit = 'v2'; + schema.Role.parents = { + employee: { id: 'employee_id' } + }; + schema.Role.children = { + department: { id: 'role_id' } + }; + + schema.Address.parents = { + phone: { id: 'phone_id' } + }; + Phone = PersistObjectTemplate.create('Phone', { + number: { type: String } + }); + + Address = PersistObjectTemplate.create('Address', { + city: { type: String }, + state: { type: String }, + phone: { type: Phone } + }); + + + Role = PersistObjectTemplate.create('Role', { + name: { type: String }, + + }); + + Employee = PersistObjectTemplate.create('Employee', { + name: { type: String, value: 'Test Employee' }, + homeAddress: { type: Address }, + roles: { type: Array, of: Role, value: [] }, + dob: { type: Date }, + customObj: { type: Object }, + isMarried: { type: Boolean } + }); + + Role.mixin({ + employee: { type: Employee } + }); + var emp = new Employee(); + var add = new Address(); + var phone = new Phone(); + var role1 = new Role(); + role1.name = 'firstRole2'; + role1.employee = emp; + var role2 = new Role(); + role2.name = 'secondRole2'; + role2.employee = emp; + + phone.number = '1231231234'; + add.city = 'New York'; + add.state = 'New York'; + add.phone = phone; + emp.name = 'InitialName'; + dob = new Date(); + emp.dob = dob; + emp.homeAddress = add; + emp.roles.push(role1); + emp.roles.push(role2); + + (function () { + PersistObjectTemplate.setDB(knex, PersistObjectTemplate.DB_Knex); + PersistObjectTemplate.setSchema(schema); + PersistObjectTemplate.performInjections(); + + })(); + return Promise.resolve(prepareData()); + + function prepareData() { + return syncTable(Employee) + .then(syncTable.bind(this, Address)) + .then(syncTable.bind(this, Phone)) + .then(syncTable.bind(this, Role)) + .then(addConstraint.bind(this)) + .then(addTriggers.bind(this)) + .then(createRecords.bind(this)) + .catch(e => { + console.log(e); + }); + + + function syncTable(template) { + return PersistObjectTemplate.synchronizeKnexTableFromTemplate(template); + } + + function addConstraint() { + return knex.raw('ALTER TABLE tx_role ADD CONSTRAINT namechk CHECK (char_length(name) <= 50);') + } + + + async function addTriggers() { + await Promise.all([knex.raw(` + CREATE OR REPLACE FUNCTION public.createddate_trigger() + RETURNS trigger + LANGUAGE plpgsql + AS $function$ + DECLARE columns text; + table_exists bool; + begin + + --using now as we track timezone in date fields.. + NEW."createdTime" = now(); + NEW."lastUpdatedTime" = now(); + execute format( + ' SELECT EXISTS ( + SELECT FROM + information_schema.tables + WHERE + table_type LIKE ''BASE TABLE'' AND + table_name = ''%1$s___history'' + ); + ', TG_TABLE_NAME ) into table_exists; + + + + if POSITION('___history' in TG_TABLE_NAME) = 0 and table_exists then + EXECUTE format('SELECT string_agg(''"'' || c1.attname || ''"'', '','') + FROM pg_attribute c1 + where c1.attrelid = ''%s''::regclass + AND c1.attname <> ''_id'' + AND c1.attnum > 0;', TG_TABLE_NAME) INTO columns; + + + execute format( + ' INSERT INTO %1$s___history ( _id, _snapshot_id, %2$s ) + values (md5(random()::text || ''%3$s'' || clock_timestamp()::text)::uuid, $1.*) + ', TG_TABLE_NAME, columns, new._id) using new; + + end if; + RETURN NEW; + END + $function$ + ; + + + +`), knex.raw(` + CREATE OR REPLACE FUNCTION public.modifieddate_trigger() + RETURNS trigger + LANGUAGE plpgsql + AS $function$ + DECLARE columns text; + table_exists bool; + begin + + --using now as we track timezone in date fields.. + NEW."lastUpdatedTime" = now(); + + execute format( + ' SELECT EXISTS ( + SELECT FROM + information_schema.tables + WHERE + table_type LIKE ''BASE TABLE'' AND + table_name = ''%1$s___history'' + ); + ', TG_TABLE_NAME ) into table_exists; + + + if POSITION('___history' in TG_TABLE_NAME) = 0 AND table_exists then + EXECUTE format('SELECT string_agg(''"'' || c1.attname || ''"'', '','') + FROM pg_attribute c1 + where c1.attrelid = ''%s''::regclass + AND c1.attname <> ''_id'' + AND c1.attnum > 0;', TG_TABLE_NAME) INTO columns; + + + execute format( + ' INSERT INTO %1$s___history ( _id, _snapshot_id, %2$s ) + values (md5(random()::text || ''%3$s'' || clock_timestamp()::text)::uuid, $1.*) + ', TG_TABLE_NAME, columns, new._id) using new; + + end if; + RETURN NEW; + END + $function$ + ; + + `)]); + + return Promise.all([knex.raw(` + DO $$ DECLARE + r RECORD; + table_exists bool; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + execute format( + ' SELECT EXISTS ( + SELECT FROM + information_schema.tables + WHERE + table_type LIKE ''BASE TABLE'' AND + table_name = ''%1$s'' + ); + ', r.tablename ) into table_exists; + + if POSITION('___history' in r.tablename) = 0 AND table_exists then + EXECUTE 'create trigger createddate_inserttrigger before + insert + on + ' || quote_ident(r.tablename) || ' for each row execute procedure createddate_trigger();'; + end if; + END LOOP; + END $$; + `), + knex.raw(` + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + if POSITION('___history' in r.tablename) = 0 then + EXECUTE 'create trigger modifieddate_updatetrigger before + update + on + ' || quote_ident(r.tablename) || ' for each row execute procedure modifieddate_trigger();'; + end if; + END LOOP; + END $$; + `)]); + } + + function createRecords() { + var tx = PersistObjectTemplate.beginDefaultTransaction(); + + return emp.persist({ transaction: tx, cascade: false }).then(function () { + return PersistObjectTemplate.commit({ transaction: tx, notifyChanges: true }).then(function () { + empId = emp._id; + addressId = add._id; + phoneId = phone._id; + }); + }) + } + } + }); + + afterEach('remove tables and after each test', function () { + return knex.raw(` + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + END $$; + `); + }); + + it('Adding data and capturing for persistorFetchById', async function () { + var orgRecordedTime = new Date(); + await PersistorUtils.sleep(100); + var employee = await Employee.persistorFetchById(empId, + { + fetch: { homeAddress: { fetch: { phone: true } }, roles: true }, projection: { Address: ['city'], Role: ['name'], Phone: [''] } + }); + + employee.name = 'First Update'; + employee.homeAddress.city = 'First city update'; + employee.roles[0].name = 'First role update'; + + var tx = PersistObjectTemplate.beginDefaultTransaction(); + employee.setDirty(tx); + employee.homeAddress.setDirty(tx); + employee.roles[0].setDirty(tx); + // await employee.persistSave(); + await PersistObjectTemplate.commit({transaction: tx}); + await PersistorUtils.sleep(100); + var updatedTime = new Date(); + employee = await Employee.persistorFetchById(empId, + { + asOfDate: orgRecordedTime, + fetch: { homeAddress: { fetch: { phone: true } }, roles: true }, projection: { Address: ['city'], Role: ['name'], Phone: [''] } + }); + expect(employee.name).is.equal('InitialName'); + expect(employee.homeAddress.city).is.equal('New York'); + expect(employee.roles[0].name).is.equal('firstRole2'); + employee = await Employee.persistorFetchById(empId, + { + asOfDate: updatedTime, + fetch: { homeAddress: { fetch: { phone: true } }, roles: true }, projection: { Address: ['city'], Role: ['name'], Phone: [''] } + }); + expect(employee.name).is.equal('First Update'); + expect(employee.homeAddress.city).is.equal('First city update'); + expect(employee.roles[0].name).is.equal('First role update'); + }); + it('Adding data and capturing for persistorFetchByQuery', async function () { + var orgRecordedTime = new Date(); + await PersistorUtils.sleep(100); + var employee = (await Employee.persistorFetchByQuery({dob: dob}, + { + fetch: { homeAddress: { fetch: { phone: true } }, roles: true }, projection: { Address: ['city'], Role: ['name'], Phone: [''] } + }))[0]; + + employee.name = 'First Update'; + employee.homeAddress.city = 'First city update'; + employee.roles[0].name = 'First role update'; + + var tx = PersistObjectTemplate.beginDefaultTransaction(); + employee.setDirty(tx); + employee.homeAddress.setDirty(tx); + employee.roles[0].setDirty(tx); + // await employee.persistSave(); + await PersistObjectTemplate.commit({transaction: tx}); + await PersistorUtils.sleep(100); + var updatedTime = new Date(); + employee = (await Employee.persistorFetchByQuery({dob: dob}, + { + asOfDate: orgRecordedTime, + fetch: { homeAddress: { fetch: { phone: true } }, roles: true }, projection: { Address: ['city'], Role: ['name'], Phone: [''] } + }))[0]; + + expect(employee.name).is.equal('InitialName'); + expect(employee.homeAddress.city).is.equal('New York'); + expect(employee.roles[0].name).is.equal('firstRole2'); + employee = (await Employee.persistorFetchByQuery({dob: dob}, + { + asOfDate: updatedTime, + fetch: { homeAddress: { fetch: { phone: true } }, roles: true }, projection: { Address: ['city'], Role: ['name'], Phone: [''] } + }))[0]; + expect(employee.name).is.equal('First Update'); + expect(employee.homeAddress.city).is.equal('First city update'); + expect(employee.roles[0].name).is.equal('First role update'); + }); + it('Adding data and capturing for getFromPersistWithId', async function () { + var orgRecordedTime = new Date(); + await PersistorUtils.sleep(100); + var employee = await Employee.getFromPersistWithId(empId, + { homeAddress: { fetch: { phone: true } }, roles: true }); + + employee.name = 'First Update'; + employee.homeAddress.city = 'First city update'; + employee.roles[0].name = 'First role update'; + + var tx = PersistObjectTemplate.beginDefaultTransaction(); + employee.setDirty(tx); + employee.homeAddress.setDirty(tx); + employee.roles[0].setDirty(tx); + // await employee.persistSave(); + await PersistObjectTemplate.commit({transaction: tx}); + await PersistorUtils.sleep(100); + var updatedTime = new Date(); + employee = await Employee.getFromPersistWithId(empId, + { homeAddress: { fetch: { phone: true } }, roles: true }, false, undefined, false, undefined, orgRecordedTime); + + expect(employee.name).is.equal('InitialName'); + expect(employee.homeAddress.city).is.equal('New York'); + expect(employee.roles[0].name).is.equal('firstRole2'); + employee = await Employee.getFromPersistWithId(empId, + { homeAddress: { fetch: { phone: true } }, roles: true }, false, undefined, false, undefined, updatedTime); + expect(employee.name).is.equal('First Update'); + expect(employee.homeAddress.city).is.equal('First city update'); + expect(employee.roles[0].name).is.equal('First role update'); + }); + it('Adding data and capturing for getFromPersistWithQuery', async function () { + var orgRecordedTime = new Date(); + await PersistorUtils.sleep(100); + var employee = (await Employee.getFromPersistWithQuery({dob: dob}, + { homeAddress: { fetch: { phone: true } }, roles: true }))[0]; + + employee.name = 'First Update'; + employee.homeAddress.city = 'First city update'; + employee.roles[0].name = 'First role update'; + + var tx = PersistObjectTemplate.beginDefaultTransaction(); + employee.setDirty(tx); + employee.homeAddress.setDirty(tx); + employee.roles[0].setDirty(tx); + // await employee.persistSave(); + await PersistObjectTemplate.commit({transaction: tx}); + await PersistorUtils.sleep(100); + var updatedTime = new Date(); + employee = (await Employee.getFromPersistWithQuery({dob: dob}, + { homeAddress: { fetch: { phone: true } }, roles: true }, false, undefined, false, undefined, undefined, undefined, orgRecordedTime))[0]; + + expect(employee.name).is.equal('InitialName'); + expect(employee.homeAddress.city).is.equal('New York'); + expect(employee.roles[0].name).is.equal('firstRole2'); + employee = (await Employee.getFromPersistWithQuery({dob: dob}, + { homeAddress: { fetch: { phone: true } }, roles: true }, false, undefined, false, undefined, undefined, undefined, updatedTime))[0]; + expect(employee.name).is.equal('First Update'); + expect(employee.homeAddress.city).is.equal('First city update'); + expect(employee.roles[0].name).is.equal('First role update'); + }); +}); \ No newline at end of file diff --git a/components/persistor/test/persist_polymorphic.js b/components/persistor/test/persist_polymorphic.js index a12f3919..0f8d64c8 100644 --- a/components/persistor/test/persist_polymorphic.js +++ b/components/persistor/test/persist_polymorphic.js @@ -10,7 +10,7 @@ chai.should(); chai.use(chaiAsPromised); -var Promise = require('bluebird'); + var ObjectTemplate = require('@haventech/supertype').default; var PersistObjectTemplate = require('../dist/index.js')(ObjectTemplate, null, ObjectTemplate); @@ -523,158 +523,158 @@ describe('type mapping tests for parent/child relations', function () { return knex.destroy(); }); - it('Parent type with an associated child will add add the fields from the child tables to the parent table', function (done) { - return PersistObjectTemplate.createKnexTable(Parent).then(function () { - return PersistObjectTemplate.checkForKnexTable(Parent).should.eventually.equal(true); - }).should.notify(done); - }); - - it('Both parent and child index definitions are added to the parent table', function () { - return PersistObjectTemplate.createKnexTable(Parent_Idx).then(function () { - return knex.schema.table('Parent_Idx', function (table) { - table.dropIndex([], 'idx_parent_idx_id_name'); - }).should.eventually.have.property('command') - }); - }); - - it('When trying to create child table, system should create the parent table', function () { - return PersistObjectTemplate.createKnexTable(ChildToCreate).then(function () { - return Promise.all([PersistObjectTemplate.checkForKnexTable(ChildCreatesThisParent, 'ChildCreatesThisParent').should.eventually.equal(true), - PersistObjectTemplate.checkForKnexTable(ChildToCreate, 'ChildToCreate').should.eventually.equal(false) - ]); - }) - }); + // it('Parent type with an associated child will add add the fields from the child tables to the parent table', function (done) { + // return PersistObjectTemplate.createKnexTable(Parent).then(function () { + // return PersistObjectTemplate.checkForKnexTable(Parent).should.eventually.equal(true); + // }).should.notify(done); + // }); - // it('When trying to create child table, system should create the parent table and the corresonding indexes in the object graph must be added to the table', function () { - // return PersistObjectTemplate.createKnexTable(ChildToCreate1).then(function () { - // return knex.schema.table('ChildCreatesThisParent1', function (table) { - // table.dropIndex([], 'idx_childcreatesthisparent1_dob'); + // it('Both parent and child index definitions are added to the parent table', function () { + // return PersistObjectTemplate.createKnexTable(Parent_Idx).then(function () { + // return knex.schema.table('Parent_Idx', function (table) { + // table.dropIndex([], 'idx_parent_idx_id_name'); // }).should.eventually.have.property('command') - // }) + // }); // }); - it('Creating a parent with children defined with multilevel inheritance', function () { - return PersistObjectTemplate.createKnexTable(ParentMulteLevel1).then(function () { - return PersistObjectTemplate.checkForKnexColumnType(ParentMulteLevel1, 'ScdLevel').should.eventually.equal('boolean'); - }) - }); - - // it('Multilevel inheritance with indexes defined at different levels', function () { - // return PersistObjectTemplate.createKnexTable(ParentMulteLevelIndx1).then(function () { - // return knex.schema.table('ParentMulteLevelIndx1', function (table) { - // table.dropIndex([], 'idx_parentmultelevelindx1_dob'); - // }).should.eventually.have.property('command') + // it('When trying to create child table, system should create the parent table', function () { + // return PersistObjectTemplate.createKnexTable(ChildToCreate).then(function () { + // return Promise.all([PersistObjectTemplate.checkForKnexTable(ChildCreatesThisParent, 'ChildCreatesThisParent').should.eventually.equal(true), + // PersistObjectTemplate.checkForKnexTable(ChildToCreate, 'ChildToCreate').should.eventually.equal(false) + // ]); // }) // }); - it('Multilevel inheritance with multiple children at the same level', function () { - return PersistObjectTemplate.createKnexTable(ParentWithMultiChildAttheSameLevel).then(function () { - return PersistObjectTemplate.checkForKnexTable(ParentWithMultiChildAttheSameLevel).should.eventually.equal(true); - }) - }); - it('Multilevel inheritance with multiple children at the multiple levels', function () { - return PersistObjectTemplate.createKnexTable(Scenario_2_ParentWithMultiChildAttheSameLevel).then(function () { - return PersistObjectTemplate.checkForKnexTable(Scenario_2_ParentWithMultiChildAttheSameLevel).should.eventually.equal(true); - }) - }); - - it('Multilevel inheritance with multiple children at the multiple levels', function () { - return PersistObjectTemplate.createKnexTable(ParentWithMultiChildAttheSameLevelWithIndexes).then(function () { - return PersistObjectTemplate.checkForKnexTable(ParentWithMultiChildAttheSameLevelWithIndexes).should.eventually.equal(true); - }) - }); + // // it('When trying to create child table, system should create the parent table and the corresonding indexes in the object graph must be added to the table', function () { + // // return PersistObjectTemplate.createKnexTable(ChildToCreate1).then(function () { + // // return knex.schema.table('ChildCreatesThisParent1', function (table) { + // // table.dropIndex([], 'idx_childcreatesthisparent1_dob'); + // // }).should.eventually.have.property('command') + // // }) + // // }); + + // it('Creating a parent with children defined with multilevel inheritance', function () { + // return PersistObjectTemplate.createKnexTable(ParentMulteLevel1).then(function () { + // return PersistObjectTemplate.checkForKnexColumnType(ParentMulteLevel1, 'ScdLevel').should.eventually.equal('boolean'); + // }) + // }); + // // it('Multilevel inheritance with indexes defined at different levels', function () { + // // return PersistObjectTemplate.createKnexTable(ParentMulteLevelIndx1).then(function () { + // // return knex.schema.table('ParentMulteLevelIndx1', function (table) { + // // table.dropIndex([], 'idx_parentmultelevelindx1_dob'); + // // }).should.eventually.have.property('command') + // // }) + // // }); + // it('Multilevel inheritance with multiple children at the same level', function () { + // return PersistObjectTemplate.createKnexTable(ParentWithMultiChildAttheSameLevel).then(function () { + // return PersistObjectTemplate.checkForKnexTable(ParentWithMultiChildAttheSameLevel).should.eventually.equal(true); + // }) + // }); + // it('Multilevel inheritance with multiple children at the multiple levels', function () { + // return PersistObjectTemplate.createKnexTable(Scenario_2_ParentWithMultiChildAttheSameLevel).then(function () { + // return PersistObjectTemplate.checkForKnexTable(Scenario_2_ParentWithMultiChildAttheSameLevel).should.eventually.equal(true); + // }) + // }); - it('Adding a child with index to a parent and synchronize.', function () { - var childSynchronize = parentSynchronize.extend('childSynchronize', { - init: function() { - this.id = 12312; - this.name = 'Child'; - Parent.call(this); - }, - dob: {type: Date} - }); + // it('Multilevel inheritance with multiple children at the multiple levels', function () { + // return PersistObjectTemplate.createKnexTable(ParentWithMultiChildAttheSameLevelWithIndexes).then(function () { + // return PersistObjectTemplate.checkForKnexTable(ParentWithMultiChildAttheSameLevelWithIndexes).should.eventually.equal(true); + // }) + // }); - schema.childSynchronize = {}; - schema.childSynchronize.indexes = JSON.parse('[{"name": "single_index","def": {"columns": ["dob"],"type": "unique"}}]'); - PersistObjectTemplate._verifySchema(); + // it('Adding a child with index to a parent and synchronize.', function () { + // var childSynchronize = parentSynchronize.extend('childSynchronize', { + // init: function() { + // this.id = 12312; + // this.name = 'Child'; + // Parent.call(this); + // }, + // dob: {type: Date} + // }); - return PersistObjectTemplate.synchronizeKnexTableFromTemplate(childSynchronize).then(function () { - return PersistObjectTemplate.checkForKnexTable(parentSynchronize).should.eventually.equal(true).then(function() { - schema.childSynchronize.indexes = JSON.parse('[{"name": "scd_index","def": {"columns": ["name"],"type": "unique"}}]'); - return PersistObjectTemplate.synchronizeKnexTableFromTemplate(childSynchronize); - }) - }) + // schema.childSynchronize = {}; - }); + // schema.childSynchronize.indexes = JSON.parse('[{"name": "single_index","def": {"columns": ["dob"],"type": "unique"}}]'); + // PersistObjectTemplate._verifySchema(); - it('Create a new table and check if the comments added to the fields are included in the database', async () => { - var NewTableWithComments = PersistObjectTemplate.create('NewTableWithComments', { - id: {type: Number}, - name: {type: String, value: 'Test Parent', comment: 'comment on a new table...'}, - init: function (id, name) { - this.id = id; - this.name = name; - } - }); + // return PersistObjectTemplate.synchronizeKnexTableFromTemplate(childSynchronize).then(function () { + // return PersistObjectTemplate.checkForKnexTable(parentSynchronize).should.eventually.equal(true).then(function() { + // schema.childSynchronize.indexes = JSON.parse('[{"name": "scd_index","def": {"columns": ["name"],"type": "unique"}}]'); + // return PersistObjectTemplate.synchronizeKnexTableFromTemplate(childSynchronize); + // }) + // }) - schema.NewTableWithComments = {documentOf: 'pg/NewTableWithComments'}; - PersistObjectTemplate._verifySchema(); - await PersistObjectTemplate.synchronizeKnexTableFromTemplate(NewTableWithComments); - const results = await knex('pg_catalog.pg_description') - .count() - .where('description', 'like', '%comment on a new table...%') - expect(results).to.deep.include({ count: '1' }); - }); + // }); - it('table notification should only work with function callbacks', function () { - var NewTableWithComments1 = PersistObjectTemplate.create('NewTableWithComments1', {}); - schema.NewTableWithComments1 = {documentOf: 'pg/NewTableWithComments1'}; - PersistObjectTemplate._verifySchema(); - return PersistObjectTemplate.synchronizeKnexTableFromTemplate(NewTableWithComments1, {}).catch(function(e) { - expect(e.message).to.equal('persistor can only notify the table changes through a callback'); - }); + // it('Create a new table and check if the comments added to the fields are included in the database', async () => { + // var NewTableWithComments = PersistObjectTemplate.create('NewTableWithComments', { + // id: {type: Number}, + // name: {type: String, value: 'Test Parent', comment: 'comment on a new table...'}, + // init: function (id, name) { + // this.id = id; + // this.name = name; + // } + // }); + + // schema.NewTableWithComments = {documentOf: 'pg/NewTableWithComments'}; + // PersistObjectTemplate._verifySchema(); + // await PersistObjectTemplate.synchronizeKnexTableFromTemplate(NewTableWithComments); + // const results = await knex('pg_catalog.pg_description') + // .count() + // .where('description', 'like', '%comment on a new table...%') + // expect(results).to.deep.include({ count: '1' }); + // }); - }); + // it('table notification should only work with function callbacks', function () { + // var NewTableWithComments1 = PersistObjectTemplate.create('NewTableWithComments1', {}); - it('field change notification should only work with function callbacks', function () { - var NewTableWithComments1 = PersistObjectTemplate.create('NewTableWithComments1', {}); + // schema.NewTableWithComments1 = {documentOf: 'pg/NewTableWithComments1'}; + // PersistObjectTemplate._verifySchema(); + // return PersistObjectTemplate.synchronizeKnexTableFromTemplate(NewTableWithComments1, {}).catch(function(e) { + // expect(e.message).to.equal('persistor can only notify the table changes through a callback'); + // }); - schema.NewTableWithComments1 = {documentOf: 'pg/NewTableWithComments1'}; - PersistObjectTemplate._verifySchema(); - return PersistObjectTemplate.synchronizeKnexTableFromTemplate(NewTableWithComments1) - .then(PersistObjectTemplate.synchronizeKnexTableFromTemplate.bind(PersistObjectTemplate, NewTableWithComments1, {})) - .catch(function(e) { - expect(e.message).to.equal('persistor can only notify the field changes through a callback'); - }); - }); + // }); - it('Check if parent schema entry is missing...', function () { + // it('field change notification should only work with function callbacks', function () { + // var NewTableWithComments1 = PersistObjectTemplate.create('NewTableWithComments1', {}); - var AddressSyncChecks = PersistObjectTemplate.create('AddressSyncChecks', {}); - var CustomerSyncChecks = PersistObjectTemplate.create('CustomerSyncChecks', {}); + // schema.NewTableWithComments1 = {documentOf: 'pg/NewTableWithComments1'}; + // PersistObjectTemplate._verifySchema(); + // return PersistObjectTemplate.synchronizeKnexTableFromTemplate(NewTableWithComments1) + // .then(PersistObjectTemplate.synchronizeKnexTableFromTemplate.bind(PersistObjectTemplate, NewTableWithComments1, {})) + // .catch(function(e) { + // expect(e.message).to.equal('persistor can only notify the field changes through a callback'); + // }); + // }); - schema.AddressSyncChecks = {documentOf: 'pg/AddressSyncChecks'}; - schema.CustomerSyncChecks = {documentOf: 'pg/CustomerSyncChecks'}; - PersistObjectTemplate._verifySchema(); - return PersistObjectTemplate.synchronizeKnexTableFromTemplate(CustomerSyncChecks) - .then(function() { - CustomerSyncChecks.mixin({ - homeAddress: {type: AddressSyncChecks}, - alternateNames: {type: Array, of: String} - }); - - PersistObjectTemplate._verifySchema(); - return PersistObjectTemplate.synchronizeKnexTableFromTemplate(CustomerSyncChecks); - }).catch(function (e) { - expect(e.message).to.equal('CustomerSyncChecks.homeAddress is missing a parents schema entry'); - }); - }); + // it('Check if parent schema entry is missing...', function () { + + // var AddressSyncChecks = PersistObjectTemplate.create('AddressSyncChecks', {}); + // var CustomerSyncChecks = PersistObjectTemplate.create('CustomerSyncChecks', {}); + + // schema.AddressSyncChecks = {documentOf: 'pg/AddressSyncChecks'}; + // schema.CustomerSyncChecks = {documentOf: 'pg/CustomerSyncChecks'}; + // PersistObjectTemplate._verifySchema(); + // return PersistObjectTemplate.synchronizeKnexTableFromTemplate(CustomerSyncChecks) + // .then(function() { + // CustomerSyncChecks.mixin({ + // homeAddress: {type: AddressSyncChecks}, + // alternateNames: {type: Array, of: String} + // }); + + // PersistObjectTemplate._verifySchema(); + // return PersistObjectTemplate.synchronizeKnexTableFromTemplate(CustomerSyncChecks); + // }).catch(function (e) { + // expect(e.message).to.equal('CustomerSyncChecks.homeAddress is missing a parents schema entry'); + // }); + // }); it('Adding a comment to an existing table', async () => { var ExistingTableWithComments = PersistObjectTemplate.create('ExistingTableWithComments', { @@ -701,84 +701,84 @@ describe('type mapping tests for parent/child relations', function () { expect(results).to.deep.include({ count: '1' }); }); - it('Adding a new field with comment to an existing table', async () => { - var ExistingTableWithAField = PersistObjectTemplate.create('ExistingTableWithAField', { - id: {type: Number}, - name: {type: String, value: 'Test Parent'}, - init: function (id, name) { - this.id = id; - this.name = name; - } - }); - - schema.ExistingTableWithAField = {documentOf: 'pg/ExistingTableWithAField'}; - PersistObjectTemplate._verifySchema(); + // it('Adding a new field with comment to an existing table', async () => { + // var ExistingTableWithAField = PersistObjectTemplate.create('ExistingTableWithAField', { + // id: {type: Number}, + // name: {type: String, value: 'Test Parent'}, + // init: function (id, name) { + // this.id = id; + // this.name = name; + // } + // }); + + // schema.ExistingTableWithAField = {documentOf: 'pg/ExistingTableWithAField'}; + // PersistObjectTemplate._verifySchema(); - await PersistObjectTemplate.synchronizeKnexTableFromTemplate(ExistingTableWithAField); - ExistingTableWithAField.mixin({ - newField: {type: String, value: 'Test Parent', comment: 'Adding a new field comment'} - }); - PersistObjectTemplate._verifySchema(); + // await PersistObjectTemplate.synchronizeKnexTableFromTemplate(ExistingTableWithAField); + // ExistingTableWithAField.mixin({ + // newField: {type: String, value: 'Test Parent', comment: 'Adding a new field comment'} + // }); + // PersistObjectTemplate._verifySchema(); - await PersistObjectTemplate.synchronizeKnexTableFromTemplate(ExistingTableWithAField, null, true); - const results = await knex('pg_catalog.pg_description') - .count() - .where('description', 'like', '%Adding a new field comment%'); - expect(results).to.deep.include({ count: '1' }); + // await PersistObjectTemplate.synchronizeKnexTableFromTemplate(ExistingTableWithAField, null, true); + // const results = await knex('pg_catalog.pg_description') + // .count() + // .where('description', 'like', '%Adding a new field comment%'); + // expect(results).to.deep.include({ count: '1' }); - }); - - it('Adding a foreign key refrence in children', function () { - var ObjectTemplate1 = require('@haventech/supertype').default; - var PersistObjectTemplate1 = require('../dist/index.js')(ObjectTemplate1, null, ObjectTemplate1); - var BaseTemplate_FK_on_Child = PersistObjectTemplate1.create('BaseTemplate_FK_on_Child', { - name: {type: String, value: 'Test Parent'} - }); + // }); - var Address_FK_on_Child = PersistObjectTemplate1.create('Address_FK_on_Child', { - street: {type: String} - }) + // it('Adding a foreign key refrence in children', function () { + // var ObjectTemplate1 = require('@haventech/supertype').default; + // var PersistObjectTemplate1 = require('../dist/index.js')(ObjectTemplate1, null, ObjectTemplate1); + // var BaseTemplate_FK_on_Child = PersistObjectTemplate1.create('BaseTemplate_FK_on_Child', { + // name: {type: String, value: 'Test Parent'} + // }); - var ChildTemplateLevel1 = BaseTemplate_FK_on_Child.extend('ChildTemplateLevel1', { - dob: {type: Date} - }); + // var Address_FK_on_Child = PersistObjectTemplate1.create('Address_FK_on_Child', { + // street: {type: String} + // }) - var ChildTemplateLevel2 = ChildTemplateLevel1.extend('ChildTemplateLevel2', { - dob: {type: Date}, - addresses: {type: Array, of: Address_FK_on_Child} - }); - var schema = {}; - schema.ChildTemplateLevel2 = {}; - schema.ChildTemplateLevel1 = {}; - schema.BaseTemplate_FK_on_Child = {}; - schema.BaseTemplate_FK_on_Child = { - documentOf: 'basetemplate_tbl', - children: { - addresses: {id: 'address_id'} - } - }; - schema.Address_FK_on_Child = {}; - schema.Address_FK_on_Child = {documentOf: 'address_ref_tbl'}; - - PersistObjectTemplate1.setDB(knex, PersistObjectTemplate1.DB_Knex); - PersistObjectTemplate1.setSchema(schema); - PersistObjectTemplate1.performInjections(); - PersistObjectTemplate1._verifySchema(); - }); + // var ChildTemplateLevel1 = BaseTemplate_FK_on_Child.extend('ChildTemplateLevel1', { + // dob: {type: Date} + // }); + + // var ChildTemplateLevel2 = ChildTemplateLevel1.extend('ChildTemplateLevel2', { + // dob: {type: Date}, + // addresses: {type: Array, of: Address_FK_on_Child} + // }); + // var schema = {}; + // schema.ChildTemplateLevel2 = {}; + // schema.ChildTemplateLevel1 = {}; + // schema.BaseTemplate_FK_on_Child = {}; + // schema.BaseTemplate_FK_on_Child = { + // documentOf: 'basetemplate_tbl', + // children: { + // addresses: {id: 'address_id'} + // } + // }; + // schema.Address_FK_on_Child = {}; + // schema.Address_FK_on_Child = {documentOf: 'address_ref_tbl'}; + + // PersistObjectTemplate1.setDB(knex, PersistObjectTemplate1.DB_Knex); + // PersistObjectTemplate1.setSchema(schema); + // PersistObjectTemplate1.performInjections(); + // PersistObjectTemplate1._verifySchema(); + // }); - it('getDB without setting database', function () { - var ObjectTemplate1 = require('@haventech/supertype').default; - var PersistObjectTemplate1 = require('../dist/index.js')(ObjectTemplate1, null, ObjectTemplate1); - expect(PersistObjectTemplate1.getDB.bind(this, 'pg')).to.throw('You must do PersistObjectTempate.setDB'); + // it('getDB without setting database', function () { + // var ObjectTemplate1 = require('@haventech/supertype').default; + // var PersistObjectTemplate1 = require('../dist/index.js')(ObjectTemplate1, null, ObjectTemplate1); + // expect(PersistObjectTemplate1.getDB.bind(this, 'pg')).to.throw('You must do PersistObjectTempate.setDB'); - }); + // }); - it('without schema..', function () { - var ObjectTemplate1 = require('@haventech/supertype').default; - var PersistObjectTemplate1 = require('../dist/index.js')(ObjectTemplate1, null, ObjectTemplate1); - var emptySchema = PersistObjectTemplate1._verifySchema(); - expect(emptySchema).to.be.an('undefined'); - }); + // it('without schema..', function () { + // var ObjectTemplate1 = require('@haventech/supertype').default; + // var PersistObjectTemplate1 = require('../dist/index.js')(ObjectTemplate1, null, ObjectTemplate1); + // var emptySchema = PersistObjectTemplate1._verifySchema(); + // expect(emptySchema).to.be.an('undefined'); + // }); diff --git a/components/persistor/test/persist_schema_indexdefchanges.js b/components/persistor/test/persist_schema_indexdefchanges.js index 3aa0ddeb..e893aca7 100644 --- a/components/persistor/test/persist_schema_indexdefchanges.js +++ b/components/persistor/test/persist_schema_indexdefchanges.js @@ -6,7 +6,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.should(); chai.use(chaiAsPromised); -var Promise = require('bluebird'); +// var ObjectTemplate = require('@haventech/supertype').default; var PersistObjectTemplate = require('../dist/index.js')(ObjectTemplate, null, ObjectTemplate); @@ -279,7 +279,7 @@ describe('index synchronization checks', function () { }) }); - it('synchronize the index definition and check if the index exists on the table by dropping the index', function () { + it('synchronize the index definition and check if the index exists on the table by dropping the index in indexdefchnages', function () { return PersistObjectTemplate.synchronizeKnexTableFromTemplate(IndexSyncTable).should.eventually.have.property('command').that.match(/INSERT/); }); diff --git a/components/persistor/test/persist_schema_updates.js b/components/persistor/test/persist_schema_updates.js index 5a686bfd..0d9121d4 100644 --- a/components/persistor/test/persist_schema_updates.js +++ b/components/persistor/test/persist_schema_updates.js @@ -6,7 +6,7 @@ var chai = require('chai'); var expect = require('chai').expect; var chaiAsPromised = require('chai-as-promised'); -var Promise = require('bluebird'); + chai.should(); chai.use(chaiAsPromised); @@ -244,7 +244,7 @@ describe('schema update checks', function () { }) }); - it('synchronize the index definition and check if the index exists on the table by dropping the index', function () { + it('synchronize the index definition and check if the index exists on the table by dropping the index in persist_schema_updates', function () { return PersistObjectTemplate.synchronizeKnexTableFromTemplate(IndexSyncTable).then(function () { return knex.schema.table('IndexSyncTable', function (table) { table.dropIndex([], 'idx_indexsynctable_name'); diff --git a/components/persistor/test/persist_transaction.js b/components/persistor/test/persist_transaction.js index 242d065e..f0da759e 100644 --- a/components/persistor/test/persist_transaction.js +++ b/components/persistor/test/persist_transaction.js @@ -8,7 +8,7 @@ chai.use(chaiAsPromised); var ObjectTemplate = require('@haventech/supertype').default; var PersistObjectTemplate = require('../dist/index.js')(ObjectTemplate, null, ObjectTemplate); -var Promise = require('bluebird'); + var knexInit = require('knex'); var knex; var schema = {}; diff --git a/components/persistor/test/supertype/one-to-manychecks.ts b/components/persistor/test/supertype/one-to-manychecks.ts index 51dc7bd2..c582cfe7 100644 --- a/components/persistor/test/supertype/one-to-manychecks.ts +++ b/components/persistor/test/supertype/one-to-manychecks.ts @@ -13,7 +13,6 @@ import { expect } from 'chai'; import * as mocha from 'mocha'; import * as _ from 'underscore'; import {Employee} from "./Employee"; -import Promise = require('bluebird'); import {Responsibility} from "./Responsibility"; diff --git a/components/persistor/test/supertype/persist_banking_pgsql.ts b/components/persistor/test/supertype/persist_banking_pgsql.ts index 1396e1da..8d9613d7 100644 --- a/components/persistor/test/supertype/persist_banking_pgsql.ts +++ b/components/persistor/test/supertype/persist_banking_pgsql.ts @@ -15,10 +15,11 @@ import { expect } from 'chai'; import * as _ from 'underscore'; import {Customer} from "./Customer"; import {ExtendedCustomer} from "./ExtendedCustomer"; -import Promise = require('bluebird'); + import {Role} from "./Role"; import {Account} from "./Account"; import {Transaction, Xfer} from './Transaction'; +import { PersistorUtils } from '../../lib/utils/PersistorUtils'; var schema = { Customer: { @@ -876,7 +877,8 @@ describe('typescript tests: Banking from pgsql Example persist_banking_pgsql', f txn2Sam.firstName = 'txn2SamDead'; txn2Sam.setDirty(txn2); txn1.postSave = function () { - Promise.delay(100) + + PersistorUtils.sleep(100) .then(function () { // Update will not return because it is requesting a lock on Karen txn1Karen.persistTouch(txn1) // 3 update karen diff --git a/components/persistor/tsconfig.json b/components/persistor/tsconfig.json index 34d03d3c..61fad521 100644 --- a/components/persistor/tsconfig.json +++ b/components/persistor/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { "outDir": "./dist", - "rootDir": "./" + "rootDir": "./", + "sourceMap": true, }, "extends": "./tsconfig.base.json", "include": [