Automattic / Automattic/mongoose
Improve error message when passing circular data to query
- Dominant language
- JavaScript
- Stars
- 27.5k
- Forks
- 4k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 35
Description
**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
```javascript
const options = {
attributes: studyAttributes,
include: [
{model: exaDB.models.patients, attributes: patientAttributes},
],
where: {has_deleted: false},
offset,
limit
}
```
```error
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
```javascript
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
```javascript
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.
Contributor guide
Research direction
Start with the stack-trace locations lib/helpers/isBsonType.js and lib/helpers/clone.js, then inspect the getStudyFromExa entry point and its findAll(options) call. Compare the failing example with the unit-test case mentioned in the issue. Done means circular query data produces a clear, catchable error instead of an unhandled RangeError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, mongodb, node.js
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100