Skip to content

Improve error message when passing circular data to query #10378

@MichaelLeeHobbs

Description

@MichaelLeeHobbs

Do you want to request a feature or report a bug? Bug

What is the current behavior? Maximum call stack size exceeded isBsonType.js:9

If the current behavior is a bug, please provide the steps to reproduce.
Calling a sequlize model.findAll(options) where options has an include array in a mongoose schema.statics.function will cause mongoose to throw a RangeError that is not catchable. I was able to confirm that even a simple include will cause the error to be thrown.

Example

    const options = {
        attributes: studyAttributes,
        include: [
            {model: exaDB.models.patients, attributes: patientAttributes},
        ],
        where: {has_deleted: false},
        offset,
        limit
    }
C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\isBsonType.js:9
function isBsonType(obj, typename) {
                   ^

RangeError: Maximum call stack size exceeded
    at isBsonType (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\isBsonType.js:9:20)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:74:7)
    at cloneArray (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:139:14)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:36:12)
    at cloneObject (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:120:17)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:59:16)
    at cloneObject (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:120:17)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:59:16)
    at cloneArray (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:139:14)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:36:12)
    at cloneObject (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:120:17)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:59:16)
    at cloneObject (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:120:17)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:59:16)
    at cloneArray (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:139:14)
    at clone (C:\Users\mhobb\WebstormProjects\vns\portal\portal-server\node_modules\mongoose\lib\helpers\clone.js:36:12)

What is the expected behavior? Not to throw and if throwing should be catchable.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
node v14.17.0
Mongoose: 5.12.14 also tested on 5.3
MongoDB: 4.4.6 and DocumentDB 4.0.0

getStudyFromExa

const exaDB = require('../../../exa/ExaDatabase')

const getStudyFromExa = async ({id, accession, offset = 0, limit = 100}) => {
    exaDB.models.studies.belongsTo(exaDB.models.patients, {foreignKey: 'patient_id', constraints: false})
    exaDB.models.studies.hasMany(exaDB.models.study_transcriptions, {foreignKey: 'study_id', constraints: false})
    exaDB.models.studies.hasOne(exaDB.models.peer_review, {foreignKey: 'study_id', constraints: false})
    exaDB.models.peer_review.belongsTo(exaDB.models.providers, {foreignKey: 'review_phys_id', as: 'reviewing', constraints: false})
    exaDB.models.study_transcriptions.belongsTo(exaDB.models.provider_contacts, {foreignKey: 'approving_provider_id', as: 'approving', constraints: false})
    exaDB.models.provider_contacts.belongsTo(exaDB.models.providers, {foreignKey: 'provider_id'})

    const studyAttributes = [
        'id', 'dicom_study_id', 'study_uid', 'accession_no', 'study_dt', 'study_description', 'body_part', 'department', 'reason_for_study', 'cpt_codes', 'stat_level', 'study_status',
        'study_info', 'notes', 'study_received_dt', 'study_details', 'modalities', 'priority', 'no_of_instances', 'institution', 'study_unread_dt', 'approved_dt'
    ]
    const patientAttributes = ['id', 'dicom_patient_id', 'account_no', 'alt_account_no', 'prefix_name', 'first_name', 'middle_name', 'last_name', 'suffix_name', 'gender', 'birth_date']
    const transcriptionsAttributes = ['id', 'study_id', 'transcription_data', 'created_dt', 'transcription_type', 'text_type', 'addendum_no', 'transcription_text', 'approved_dt']
    const reviewingAttributes = ['id', 'provider_code', 'first_name', 'middle_initial', 'last_name']
    const peerReviewAttributes = ['id', 'study_id', 'agree_id', 'agree_date', 'notes']
    const providerContactsAttributes = ['id', 'provider_id']
    console.log('options')
    const options = {
        attributes: studyAttributes,
        include: [
            {model: exaDB.models.patients, attributes: patientAttributes},
            {
                model: exaDB.models.study_transcriptions, attributes: transcriptionsAttributes, include: [{
                    model: exaDB.models.provider_contacts, as: 'approving', attributes: providerContactsAttributes,
                    include: [{model: exaDB.models.providers, attributes: reviewingAttributes}]
                }]
            },
            {model: exaDB.models.peer_review, attributes: peerReviewAttributes, include: [{model: exaDB.models.providers, as: 'reviewing', attributes: reviewingAttributes}]},
        ],
        where: {has_deleted: false},
        offset,
        limit
    }

    if (id) return await exaDB.models.studies.findByPk(id, options)
    if (accession) options.where.accession_no = accession
    try {
        console.log(options)
        // const studies = await exaDB.models.studies.findAll()     // this does not cause mongoose to throw
        const studies = await exaDB.models.studies.findAll(options) // this does cause mongoose to throw
        console.log(studies)
        return studies
    } catch (e) {
        console.error(e)
        throw new Error(e.message)
    }
}
module.exports = getStudyFromExa

Study Schema

schema.statics.exaSync = async function (accession, options) {
    debug('exaSync 1', accession)
    let studies = undefined
    // TODO this try/catch is temporary debugging remove and let caller handle
    try {
        studies = await getStudyFromExa({accession}) // running this line throws a RangeError that is not catchable
    } catch (e) {
        console.error(e)
        throw e
    }
    if (studies.length > 1) throw new Error(`More than one study returned for ${accession}!`)
    const exaStudyData = studies[0]
    debug('exaSync 2', exaStudyData.accession_no)
    let studyData = await reformatExaStudyData(exaStudyData)

    debug('exaSync 3', studyData.accession)
    let study = await _model.findOne({accession: studyData.accession})
    if (!study) return _model.create(studyData)
    study.update(studyData)
    return study.save
}

Edit: The getStudyFromExa given the same input in a unit test it returns the expected result.

Metadata

Metadata

Assignees

No one assigned

    Labels

    developer-experienceThis issue improves error messages, debugging, or reporting

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions