Как запросить JSON в таблице Postgres из API Express REST

#node.js #json #postgresql #rest #express

Вопрос:

Я пытаюсь запросить базу данных Postgres через экспресс-API REST. Один столбец (json_object_data) включает json, по которому я хочу запросить ключ «имя».

Это прекрасно работает, если я ввожу запрос непосредственно в консоль БД. Что вызывает у меня проблему, так это передача поискового запроса в HTTP-запрос на экспресс-маршрут.

К вашему сведению, другие запросы в приложение Express (GET, POST, PUT и DELETE), которые не пытаются запросить столбец json, работают нормально.

Экспресс-код узла:

 app.get("/authorsname/:name", async(req, res) => {
    const { author_name } = req.params;
    console.log(req.params);
    try {
        const getAuthors = await pool.query("select json_object_data from "jason-elwood/authors"."authors" where lower(json_object_data ->> 'name') like lower($1%)", [author_name]);
        res.json(getAuthors);
        console.log(req.params);
    } catch (error) {
           console.error(error.message); 
    }
});
 

Пример json (из столбца json_object_data):

 {
    "name": "Jason Elwood", 
    "personal_name": "Jason Elwood", 
    "last_modified": {"type": "/type/datetime", "value": "2008-08-20T17:57:31.650087"}, 
    "key": "/authors/OL1000204A", 
    "birth_date": "1982", 
    "type": {"key": "/type/author"}, 
    "revision": 2
}
 

http-запрос на получение:

 http://localhost:3000/authorsname/jason
 

Результат:

 server is listening on port 3000
{ name: 'jason' }
node:events:371
      throw er; // Unhandled 'error' event
      ^

error: unable to bind
    at Parser.parseErrorMessage (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-protocol/dist/parser.js:287:98)
    at Parser.handlePacket (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-protocol/dist/parser.js:126:29)
    at Parser.parse (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-protocol/dist/parser.js:39:38)
    at Socket.<anonymous> (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-protocol/dist/index.js:11:42)
    at Socket.emit (node:events:394:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at Socket.Readable.push (node:internal/streams/readable:228:10)
    at TCP.onStreamRead (node:internal/stream_base_commons:199:23)
Emitted 'error' event on BoundPool instance at:
    at Client.idleListener (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-pool/index.js:57:10)
    at Client.emit (node:events:394:28)
    at Client._handleErrorEvent (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg/lib/client.js:319:10)
    at Client._handleErrorMessage (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg/lib/client.js:330:12)
    at Connection.emit (node:events:394:28)
    at /Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg/lib/connection.js:114:12
    at Parser.parse (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-protocol/dist/parser.js:40:17)
    at Socket.<anonymous> (/Users/jasonelwood/Development/OpenbookAPIs/node_modules/pg-protocol/dist/index.js:11:42)
    at Socket.emit (node:events:394:28)
    at addChunk (node:internal/streams/readable:315:12) {
  length: 92,
  severity: 'FATAL',
  code: 'XX000',
  detail: 'cannot get parse message ""',
  hint: undefined,
  position: undefined,
  internalPosition: undefined,
  internalQuery: undefined,
  where: undefined,
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'pool_proto_modules.c',
  line: '1414',
  routine: undefined,
  client: Client {
    _events: [Object: null prototype] { error: [Function (anonymous)] },
    _eventsCount: 1,
    _maxListeners: undefined,
    connectionParameters: ConnectionParameters {
      user: 'omitted',
      database: 'omitted',
      port: 5432,
      host: 'omitted',
      binary: false,
      options: undefined,
      ssl: false,
      client_encoding: '',
      replication: undefined,
      isDomainSocket: false,
      application_name: undefined,
      fallback_application_name: undefined,
      statement_timeout: false,
      idle_in_transaction_session_timeout: false,
      query_timeout: false,
      connect_timeout: 0
    },
    user: 'omitted',
    database: 'omitted',
    port: 5432,
    host: 'omitted',
    replication: undefined,
    _Promise: [Function: Promise],
    _types: TypeOverrides {
      _types: {
        getTypeParser: [Function: getTypeParser],
        setTypeParser: [Function: setTypeParser],
        arrayParser: { create: [Function: create] },
        builtins: {
          BOOL: 16,
          BYTEA: 17,
          CHAR: 18,
          INT8: 20,
          INT2: 21,
          INT4: 23,
          REGPROC: 24,
          TEXT: 25,
          OID: 26,
          TID: 27,
          XID: 28,
          CID: 29,
          JSON: 114,
          XML: 142,
          PG_NODE_TREE: 194,
          SMGR: 210,
          PATH: 602,
          POLYGON: 604,
          CIDR: 650,
          FLOAT4: 700,
          FLOAT8: 701,
          ABSTIME: 702,
          RELTIME: 703,
          TINTERVAL: 704,
          CIRCLE: 718,
          MACADDR8: 774,
          MONEY: 790,
          MACADDR: 829,
          INET: 869,
          ACLITEM: 1033,
          BPCHAR: 1042,
          VARCHAR: 1043,
          DATE: 1082,
          TIME: 1083,
          TIMESTAMP: 1114,
          TIMESTAMPTZ: 1184,
          INTERVAL: 1186,
          TIMETZ: 1266,
          BIT: 1560,
          VARBIT: 1562,
          NUMERIC: 1700,
          REFCURSOR: 1790,
          REGPROCEDURE: 2202,
          REGOPER: 2203,
          REGOPERATOR: 2204,
          REGCLASS: 2205,
          REGTYPE: 2206,
          UUID: 2950,
          TXID_SNAPSHOT: 2970,
          PG_LSN: 3220,
          PG_NDISTINCT: 3361,
          PG_DEPENDENCIES: 3402,
          TSVECTOR: 3614,
          TSQUERY: 3615,
          GTSVECTOR: 3642,
          REGCONFIG: 3734,
          REGDICTIONARY: 3769,
          JSONB: 3802,
          REGNAMESPACE: 4089,
          REGROLE: 4096
        }
      },
      text: {},
      binary: {}
    },
    _ending: true,
    _connecting: false,
    _connected: true,
    _connectionError: false,
    _queryable: false,
    connection: Connection {
      _events: [Object: null prototype] {
        newListener: [Function (anonymous)],
        connect: [Function (anonymous)],
        sslconnect: [Function (anonymous)],
        authenticationCleartextPassword: [Function: bound _handleAuthCleartextPassword],
        authenticationMD5Password: [Function: bound _handleAuthMD5Password],
        authenticationSASL: [Function: bound _handleAuthSASL],
        authenticationSASLContinue: [Function: bound _handleAuthSASLContinue],
        authenticationSASLFinal: [Function: bound _handleAuthSASLFinal],
        backendKeyData: [Function: bound _handleBackendKeyData],
        error: [Function: bound _handleErrorEvent],
        errorMessage: [Function: bound _handleErrorMessage],
        readyForQuery: [Function: bound _handleReadyForQuery],
        notice: [Function: bound _handleNotice],
        rowDescription: [Function: bound _handleRowDescription],
        dataRow: [Function: bound _handleDataRow],
        portalSuspended: [Function: bound _handlePortalSuspended],
        emptyQuery: [Function: bound _handleEmptyQuery],
        commandComplete: [Function: bound _handleCommandComplete],
        parseComplete: [Function: bound _handleParseComplete],
        copyInResponse: [Function: bound _handleCopyInResponse],
        copyData: [Function: bound _handleCopyData],
        notification: [Function: bound _handleNotification],
        end: [
          [Function: bound onceWrapper] {
            listener: [Function (anonymous)]
          },
          [Function: bound onceWrapper] {
            listener: [Function (anonymous)]
          },
          [Function: bound onceWrapper] {
            listener: [Function (anonymous)]
          }
        ]
      },
      _eventsCount: 23,
      _maxListeners: undefined,
      stream: <ref *1> Socket {
        connecting: false,
        _hadError: false,
        _parent: null,
        _host: 'db.bit.io',
        _readableState: ReadableState {
          objectMode: false,
          highWaterMark: 16384,
          buffer: BufferList { head: null, tail: null, length: 0 },
          length: 0,
          pipes: [],
          flowing: true,
          ended: false,
          endEmitted: false,
          reading: false,
          constructed: true,
          sync: false,
          needReadable: true,
          emittedReadable: false,
          readableListening: false,
          resumeScheduled: false,
          errorEmitted: false,
          emitClose: false,
          autoDestroy: true,
          destroyed: true,
          errored: null,
          closed: true,
          closeEmitted: false,
          defaultEncoding: 'utf8',
          awaitDrainWriters: null,
          multiAwaitDrain: false,
          readingMore: false,
          dataEmitted: true,
          decoder: null,
          encoding: null,
          [Symbol(kPaused)]: false
        },
        _events: [Object: null prototype] {
          end: [
            [Function: onReadableStreamEnd],
            [Function (anonymous)],
            [Function (anonymous)]
          ],
          error: [Function: reportStreamError],
          close: [Function (anonymous)],
          data: [Function (anonymous)]
        },
        _eventsCount: 4,
        _maxListeners: undefined,
        _writableState: <ref *2> WritableState {
          objectMode: false,
          highWaterMark: 16384,
          finalCalled: false,
          needDrain: false,
          ending: false,
          ended: false,
          finished: false,
          destroyed: true,
          decodeStrings: false,
          defaultEncoding: 'utf8',
          length: 0,
          writing: false,
          corked: 0,
          sync: false,
          bufferProcessing: false,
          onwrite: [Function: bound onwrite],
          writecb: null,
          writelen: 0,
          afterWriteTickInfo: {
            count: 1,
            cb: [Function (anonymous)],
            stream: [Circular *1],
            state: [Circular *2]
          },
          buffered: [],
          bufferedIndex: 0,
          allBuffers: true,
          allNoop: true,
          pendingcb: 1,
          constructed: true,
          prefinished: false,
          errorEmitted: false,
          emitClose: false,
          autoDestroy: true,
          errored: null,
          closed: true,
          closeEmitted: false,
          [Symbol(kOnFinished)]: []
        },
        allowHalfOpen: false,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        [Symbol(async_id_symbol)]: 22,
        [Symbol(kHandle)]: null,
        [Symbol(kSetNoDelay)]: true,
        [Symbol(lastWriteQueueSize)]: 0,
        [Symbol(timeout)]: null,
        [Symbol(kBuffer)]: null,
        [Symbol(kBufferCb)]: null,
        [Symbol(kBufferGen)]: null,
        [Symbol(kCapture)]: false,
        [Symbol(kBytesRead)]: 784,
        [Symbol(kBytesWritten)]: 297
      },
      _keepAlive: false,
      _keepAliveInitialDelayMillis: 0,
      lastBuffer: false,
      parsedStatements: {},
      ssl: false,
      _ending: true,
      _emitMessage: false,
      _connecting: true,
      [Symbol(kCapture)]: false
    },
    queryQueue: [],
    binary: false,
    processID: -1631365830,
    secretKey: -1498717443,
    ssl: false,
    _connectionTimeoutMillis: 0,
    _connectionCallback: null,
    release: [Function (anonymous)],
    activeQuery: null,
    readyForQuery: true,
    hasExecuted: true,
    _poolUseCount: 1,
    [Symbol(kCapture)]: false
  }
}
[nodemon] app crashed - waiting for file changes before starting...
 

Тот же запрос непосредственно в консоли бд (который выдает успешный ответ):

 select json_object_data from "jason-elwood/authors"."authors" where lower(json_object_data ->> 'name') like lower('jason%');
 

Я предполагаю, что это как-то связано со структурой запроса, особенно с тем, как я включаю параметр запроса в строку запроса. Я попытался заключить весь запрос в одинарные кавычки и не экранировать имя таблицы таким образом:

 const getAuthors = await pool.query('select json_object_data from "jason-elwood/authors"."authors" where lower(json_object_data ->> "name") like lower($1%)', [author_name]);
 

но это все равно приводит к ошибке.

Любая помощь в этом вопросе будет очень признательна.

Спасибо!

Ответ №1:

По крайней % мере, должно быть добавлено к переданному значению, поскольку вы используете параметры запроса:

 const getAuthors = await pool
  .query("select json_object_data from "jason-elwood/authors"."authors" where json_object_data ->> 'name' ilike $1", [`${author_name}%`]);
res.json(getAuthors);
 

И вы можете использовать ilike вместо like того, чтобы избегать использования lower для обеих сторон операции.

Комментарии:

1. О, спасибо, что предупредили об использовании ilike вместо like! И да, я наконец понял, что это была проблема с моими подстановочными знаками в моем переданном значении.

Ответ №2:

Поэтому для всех, кто придет к этому в будущем, проблема заключалась в том, как я структурировал параметры при добавлении их в строку запроса. Поскольку я использую оператор LIKE, он должен быть отформатирован следующим образом:

 ['%'   req.params.name   '%']
 

по причинам, которые мне теперь очевидны после того, как я несколько часов бился головой о стол. (Или то, что у меня было изначально, что было бы [‘%’ имя автора ‘%’]). Итак, окончательный запрос выглядит следующим образом:

 const getAuthors = await pool.query("select json_object_data from "jason-elwood/authors"."authors" where lower(json_object_data ->> 'name') like lower($1)", ['%'   req.params.name   '%']);
 

Я снова могу дышать. 🙂