Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[backend] Stacktraces for PostgreSQL errors are uninformative #1357

@alxndrsn

Description

@alxndrsn

Reproduction

Command

Run this on a clean checkout of master:

patch lib/model/query/users.js <<EOF
diff --git a/lib/model/query/users.js b/lib/model/query/users.js
index fff1ddaa..87eaa724 100644
--- a/lib/model/query/users.js
+++ b/lib/model/query/users.js
@@ -64,7 +64,7 @@ const _getSql = (options) => sql\`
 select \${_unjoin.fields} from users
 join actors on users."actorId"=actors.id
 \${options.ifArg('q', (q) => sql\`
-  left join lateral
+  left join lateral lol
     greatest(word_similarity("displayName", \${q}) + similarity("displayName", \${q}),
       word_similarity(email, \${q}) + similarity(email, \${q})) as score on true\`)}
 where \${sqlEquals(options.condition)} and actors."deletedAt" is null
EOF

BCRYPT=insecure npx mocha --recursive test/integration/ --grep 'should search user display names if a query is given'

Output

  api: /users
    GET
::ffff:127.0.0.1 - - [10/Sep/2025:06:22:41 +0000] "POST /v1/sessions HTTP/1.1" 200 228
>> Outbound email: {"to":[{"address":"[email protected]","name":""}],"from":{"address":"[email protected]","name":""},"subject":"ODK Central account created","html":"<html>Hello!<p>An account has been provisioned for you on an ODK Central server.</p><p>If this message is unexpected, simply ignore it. Otherwise, please visit the following link to set your password and claim your account:</p><p><a href=\"/account/claim?token=85E$4ffsriiJXNX6P2NgUJacgKvhiN8UGCj6YbvPXQBIyBrI!tFRTBwesRM1zWGJ\">/account/claim?token=85E$4ffsriiJXNX6P2NgUJacgKvhiN8UGCj6YbvPXQBIyBrI!tFRTBwesRM1zWGJ</a></p><p>The link is valid for 24 hours. After that, you will have to request a new one by resetting your password:</p><p><a href=\"/reset-password\">/reset-password</a></p></html>","headers":{},"messageId":"<[email protected]>"}
::ffff:127.0.0.1 - - [10/Sep/2025:06:22:41 +0000] "POST /v1/users HTTP/1.1" 200 144
error: syntax error at or near "greatest"
    at Parser.parseErrorMessage (odk/backend/node_modules/pg-protocol/dist/parser.js:285:98)
    at Parser.handlePacket (odk/backend/node_modules/pg-protocol/dist/parser.js:122:29)
    at Parser.parse (odk/backend/node_modules/pg-protocol/dist/parser.js:35:38)
    at Socket.<anonymous> (odk/backend/node_modules/pg-protocol/dist/index.js:11:42)
    at Socket.emit (node:events:518:28)
    at Socket.emit (node:domain:489:12)
    at addChunk (node:internal/streams/readable:561:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
    at Readable.push (node:internal/streams/readable:392:5)
    at TCP.onStreamRead (node:internal/stream_base_commons:189:23) {
  length: 98,
  severity: 'ERROR',
  code: '42601',
  detail: undefined,
  hint: undefined,
  position: '509',
  internalPosition: undefined,
  internalQuery: undefined,
  where: undefined,
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'scan.l',
  line: '1176',
  routine: 'scanner_yyerror',
  notices: []
}
error: syntax error at or near "greatest"
    at Parser.parseErrorMessage (odk/backend/node_modules/pg-protocol/dist/parser.js:285:98)
    at Parser.handlePacket (odk/backend/node_modules/pg-protocol/dist/parser.js:122:29)
    at Parser.parse (odk/backend/node_modules/pg-protocol/dist/parser.js:35:38)
    at Socket.<anonymous> (odk/backend/node_modules/pg-protocol/dist/index.js:11:42)
    at Socket.emit (node:events:518:28)
    at Socket.emit (node:domain:489:12)
    at addChunk (node:internal/streams/readable:561:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
    at Readable.push (node:internal/streams/readable:392:5)
    at TCP.onStreamRead (node:internal/stream_base_commons:189:23) {
  length: 98,
  severity: 'ERROR',
  code: '42601',
  detail: undefined,
  hint: undefined,
  position: '509',
  internalPosition: undefined,
  internalQuery: undefined,
  where: undefined,
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'scan.l',
  line: '1176',
  routine: 'scanner_yyerror',
  notices: []
}
::ffff:127.0.0.1 - - [10/Sep/2025:06:22:41 +0000] "GET /v1/users?q=alice HTTP/1.1" 500 35
      1) should search user display names if a query is given

Related

Potential fix

It seems like because of the magic of async, you have to capture the stacktrace before the db function is called, so e.g. the container.all() function would change like this:

-  obj.all = (s) => db.any(s);
+  obj.all = async (s) => {
+    const stackAnchor = {};
+    Error.captureStackTrace(stackAnchor);
+    const { stack } = stackAnchor;
+
+    try {
+      return await db.any(s);
+    } catch(cause) {
+      const err = new Error('PostgreSQL error', { cause });
+      err.stack = stack;
+      throw err;
+    }
+  };

This seems expensive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendRequires a change to the API server

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions