loopbackio / loopback-connector-mssql

LoopBack connector for Microsoft SQL Server
http://loopback.io/doc/en/lb3/SQL-Server-connector.html
Other
52 stars 84 forks source link

Loopback 4 Transaction returning undefined. #218

Closed metkuten closed 4 years ago

metkuten commented 4 years ago

@raymondfeng , @bajtos I have implemented Repository which is extending DefaultTransactionalRepository.

Issue is, for .beginTransaction(); getting undefined instead of Promise. function is immediately returning undefined

Using below NPM package "@loopback/authentication": "2.1.4", "@loopback/boot": "1.5.5", "@loopback/context": "1.22.1", "@loopback/core": "1.10.1", "@loopback/openapi-v3": "1.9.6", "@loopback/repository": "1.13.1", "@loopback/repository-json-schema": "1.9.7", "loopback-connector-mssql": "^3.3.0",

And getting below error in console. 2019-11-15T21:00:52.747Z [founders-mw-dev-699fbb8489-z6k46] error: {"APPLICATION_NAME":"FOUNDERS_SVC","MESSAGE":"uncaughtException: cb is not a function\nTypeError: cb is not a function\n at /u01/foundersplatform/founders-mw/node_modules/loopback-connector/lib/transaction.js:119:5\n at /u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/lib/transaction.js:25:7\n at /u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/mssql/lib/base.js:813:9\n at /u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/mssql/lib/tedious.js:333:11\n at Request.userCallback (/u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/tedious/lib/connection.js:1479:16)\n at Request._this.callback (/u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/tedious/lib/request.js:61:27)\n at Connection.endOfMessageMarkerReceived (/u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/tedious/lib/connection.js:1944:20)\n at Connection.dispatchEvent (/u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/tedious/lib/connection.js:1012:38)\n at Parser. (/u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/tedious/lib/connection.js:812:18)\n at Parser.emit (events.js:209:13)\n at Parser. (/u01/foundersplatform/founders-mw/node_modules/loopback-connector-mssql/node_modules/tedious/lib/token/token-stream-parser.js:54:15)\n at Parser.emit (events.js:209:13)\n at addChunk (/u01/foundersplatform/founders-mw/node_modules/readable-stream/lib/_stream_readable.js:291:12)\n at readableAddChunk (/u01/foundersplatform/founders-mw/node_modules/readable-stream/lib/_stream_readable.js:278:11)\n at Parser.Readable.push (/u01/foundersplatform/founders-mw/node_modules/readable-stream/lib/_stream_readable.js:245:10)\n at Parser.Transform.push (/u01/foundersplatform/founders-mw/node_modules/readable-stream/lib/_stream_transform.js:148:32)"}

Kindly look into this.

dhmlau commented 4 years ago

@metkuten, to help us to reproduce the problem, could you please provide a small application and steps to reproduce? Thanks. https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-4x-bugs

metkuten commented 4 years ago

@dhmlau : below is the code snapshot, unfortunately i work for a bank and its hard to provide more code details. but i am sure, i am following details provided on loopback4 tutorials(model, transaction repository, passing transaction object into repository call(create),etc).

all other insert, get quires works perfectly fine. code snapshot

  1. export class PersonRepository extends DefaultTransactionalRepository

  2. controller i have below code. async createTransaction(): Promise { return this.personRepo.beginTransaction(); }

const dbTransactionPromise = this.personsService.createTransaction(); dbTransaction = await dbTransactionPromise;

dhmlau commented 4 years ago

@metkuten, I have the following test code and it seems to be working:

/**
   * Test endpoint
   * @param id
   */
  @get('/customers/create-find/{id}', {
    responses: {
      '200': {
        description: 'Customer model instance',
        content: {'application/json': {schema: getModelSchemaRef(Customer)}},
      },
    },
  })
  async createAndFind(@param.path.string('id') id: string): Promise<Customer> {
    let tx: Transaction;
    tx = await this.customerRepository.beginTransaction(
      IsolationLevel.READ_COMMITTED,
    );
    const newCustomer = await this.customerRepository.create(
      {
        name: 'Bob',
        city: 'Toronto',
      },
      {transaction: tx},
    );
    await tx.commit();
    return this.customerRepository.findById(id);
  }

I'm using https://github.com/strongloop/loopback-next/blob/master/packages/repository-tests/src/crud/transactions.suite.ts as reference. Hope it helps.

metkuten commented 4 years ago

@dhmlau : as i mentioned above , i am exactly implementing like this. and it's throwing an exception. look like call back "cb" is not working in the code.

below is the transaction.js code on server


// Node module: loopback-connector
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';
var assert = require('assert');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('loopback:connector:transaction');
var uuid = require('uuid');

module.exports = Transaction;

/**
 * Create a new Transaction object
 * @param {Connector} connector The connector instance
 * @param {*} connection A connection to the DB
 * @constructor
 */
function Transaction(connector, connection) {
  this.connector = connector;
  this.connection = connection;
  EventEmitter.call(this);
}

util.inherits(Transaction, EventEmitter);

// Isolation levels
Transaction.SERIALIZABLE = 'SERIALIZABLE';
Transaction.REPEATABLE_READ = 'REPEATABLE READ';
Transaction.READ_COMMITTED = 'READ COMMITTED';
Transaction.READ_UNCOMMITTED = 'READ UNCOMMITTED';

Transaction.hookTypes = {
  BEFORE_COMMIT: 'before commit',
  AFTER_COMMIT: 'after commit',
  BEFORE_ROLLBACK: 'before rollback',
  AFTER_ROLLBACK: 'after rollback',
  TIMEOUT: 'timeout',
};

/**
 * Commit a transaction and release it back to the pool
 * @param cb
 * @returns {*}
 */
Transaction.prototype.commit = function(cb) {
  return this.connector.commit(this.connection, cb);
};

/**
 * Rollback a transaction and release it back to the pool
 * @param cb
 * @returns {*|boolean}
 */
Transaction.prototype.rollback = function(cb) {
  return this.connector.rollback(this.connection, cb);
};

/**
 * Begin a new transaction
 * @param {Connector} connector The connector instance
 * @param {Object} [options] Options {isolationLevel: '...', timeout: 1000}
 * @param cb
 */
Transaction.begin = function(connector, options, cb) {
  if (typeof options === 'function' && cb === undefined) {
    cb = options;
    options = {};
  }
  if (typeof options === 'string') {
    options = {isolationLevel: options};
  }
  var isolationLevel = options.isolationLevel || Transaction.READ_COMMITTED;
  assert(isolationLevel === Transaction.SERIALIZABLE ||
    isolationLevel === Transaction.REPEATABLE_READ ||
    isolationLevel === Transaction.READ_COMMITTED ||
    isolationLevel === Transaction.READ_UNCOMMITTED, 'Invalid isolationLevel');

  debug('Starting a transaction with options: %j', options);
  assert(typeof connector.beginTransaction === 'function',
    'beginTransaction must be function implemented by the connector');
  connector.beginTransaction(isolationLevel, function(err, connection) {
    if (err) {
      return cb(err);
    }
    var tx = connection;

    // When the connector and juggler node module have different version of this module as a dependency,
    // the transaction is not an instanceof Transaction.
    // i.e. (connection instanceof Transaction) == false
    // Check for existence of required functions and properties, instead of prototype inheritance.
    if (connection.connector == undefined || connection.connection == undefined ||
      connection.commit == undefined || connection.rollback == undefined) {
      tx = new Transaction(connector, connection);
    }
    // Set an informational transaction id
    tx.id = uuid.v1();
    // NOTE(lehni) Handling of transaction timeouts here only works with recent
    // versions of `loopback-datasource-juggler` which make its own handling of
    // timeouts conditional based on the absence of an already set `tx.timeout`,
    // see: https://github.com/strongloop/loopback-datasource-juggler/pull/1484
    if (options.timeout) {
      tx.timeout = setTimeout(function() {
        var context = {
          transaction: tx,
          operation: 'timeout',
        };
        tx.notifyObserversOf('timeout', context, function(err) {
          if (!err) {
            tx.rollback(function() {
              debug('Transaction %s is rolled back due to timeout', tx.id);
            });
          }
        });
      }, options.timeout);
    }
    cb(err, tx);
  });
};```
dhmlau commented 4 years ago

@strongloop/loopback-maintainers, might need your help here. It seems like @metkuten and I are doing the same thing to call beginTransaction (my test code is here), but I wasn't able to reproduce the problem.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] commented 4 years ago

This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.

heroca60 commented 4 years ago

Why loopback4 in the transactions retains and does not release the auto incremental id despite using await tx.rollback () when the transaction fails. My repositories inherit from Default Transactional Repository as indicated in the official documentation