ES6 Async / Await, ExpressJS и Postgres транзакции - PullRequest
4 голосов
/ 30 марта 2020

ПЕРЕСМОТРЕННЫЙ ВОПРОС

Я пересмотрел вопрос в надежде получить более четкий ответ.


Я пытаюсь обработать данные в ExpressJS , исходя из входящих req.body и существующих данных в таблице.

Я получаю req.body, который содержит JSON список обновленных полей. Некоторые из этих полей хранятся как JSONB в Postgres. Если входящим полем является JSONB, то форма (внешний код), который делает запрос, уже запустила jsonpatch.compare() для генерации списка патчей, и именно эти патчи, а не полные значения, передаются. любые не-JSONB-значения, входящие значения просто необходимо передать в запрос UPDATE.

У меня есть рабочая версия, как показано ниже, которая делает вид, что существующие значения JSONB в таблице равны NULL. Понятно, что это НЕ то, что нужно. Мне нужно вытащить значения из БД. Версия без запроса текущих значений и минимальный маршрутизатор выглядят так:

const express = require('express')
const bodyParser = require('body-parser')
const SQL = require('sql-template-strings')
const { Client } = require('pg')
const dbConfig = require('../db')
const jsonpatch = require('fast-json-patch')

const FormRouter = express.Router()

I have some update code:

````javascript
const patchFormsRoute = (req, res) => {
  const client = new Client(dbConfig)
  const { id } = req.body
  const parts = []
  const params = [id]

  // list of JSONB fields for the 'forms' table
  const jsonFields = [
    'sections',
    'editors',
    'descriptions',
  ]

  // list of all fields, including JSONB fields in the 'forms' table
  const possibleFields = [
    'status',
    'version',
    'detail',
    'materials',
    ...jsonFields,
  ]

  // this is a DUMMY RECORD instead of the result of a client.query 
  let currentRecord = { 'sections':[], 'editors':[], 'descriptions':[] }

  possibleFields.forEach(myProp => {
    if (req.body[myProp] != undefined) {
      parts.push(`${myProp} = $${params.length + 1}`)
      if (jsonFields.indexOf(myProp) > -1) {
        val = currentRecord[myProp]
        jsonpatch.applyPatch(val, req.body[myProp])
        params.push(JSON.stringify(val))
      } else {
        params.push(req.body[myProp])
      }
    }
  })

  const updateQuery = 'UPDATE forms SET ' + parts.join(', ') + ' WHERE id = $1'

  client.connect()
  return client
    .query(updateQuery, params)
    .then(result => res.status(200).json(result.rowCount))
    .catch(err => res.status(400).json(err.severity))
    .then(() => client.end())
}

FormRouter.route('/')
  .patch(bodyParser.json({ limit: '50mb' }), patchFormsRoute)

exports.FormRouter = FormRouter

Я обещаю, что это рабочий код, который выполняет почти то, что я нужно. Однако я хочу заменить фиктивную запись данными, уже находящимися в таблице, взятыми одновременно. Моя проблема заключается в том, что несколько клиентов могут обновлять строку одновременно (но, глядя на ортогональные элементы значений JSONB), мне нужны выборка, cal c и обновление, чтобы она выполнялась как ОДИН ТРАНЗАКЦИЯ. Мой план:

  1. НАЧАТЬ транзакцию

  2. Запрос Postgres для текущего значения строки на основе входящего id

  3. Для любых полей JSONB примените исправление, чтобы сгенерировать правильное значение для этого поля в инструкции UPDATE.

  4. Запустите инструкцию UPDATE с соответствующие значения параметров (либо из req.body, либо из исправленной строки, в зависимости от того, является ли поле JSONB или нет)

  5. COMMIT транзакции или ROLLBACK при ошибке.

Я пытался реализовать ответ от @midrizi; может быть, это только я, но комбинация ожидания и простого тестирования res отправляет сервер в гиперпространство ... и заканчивается таймаутом.

Ответы [ 2 ]

5 голосов
/ 02 апреля 2020

Контроллер:

...
...
...

    client.connect();

    // not all are needed here you can remove some of them
    let cl = getClient(client, id, jsonFields, possibleFields, patchList);

};

Бизнес логи c:

const getClient = async (client, id, jsonFields, possibleFields, patchList) => {

  const parts = [];
  const params = [id];
  let begin = await client.query('BEGIN');

  if (begin) {
    let res = await client.query(SQL`SELECT * FROM forms WHERE id = ${id}`);

    if (res) {
      // this needs to change also depending on what you get from res
      const currentRecord = fetchResult.rows[0];
      patchList.forEach(e => {
        jsonpatch.applyPatch(currentRecord[e], req.body[e]) // patches in place
      });

      possibleFields.forEach(myProp => {
        if (body[myProp] !== undefined) {
          parts.push(`${myProp} = $${params.length + 1}`)
          if (jsonFields.indexOf(myProp) > -1) {
            params.push(JSON.stringify(req.body[myProp]))
          } else {
            params.push(req.body[myProp])
          }
        }
      });


      const updateQuery =
        'UPDATE forms SET ' + parts.join(', ') + ' WHERE id = $1'

      let res = await client.query(updateQuery, params);

      if (res) {
        await client.query('COMMIT');
        return res.rowCount;
      } else {
        await client.query('ROLLBACK');
        throw res;
      }
    } else {
      throw 'no results found';
    }
  }
};

0 голосов
/ 11 апреля 2020

Если кто-то еще не спит, вот рабочее решение моей проблемы.

TLDR; RTFM: Клиент в пуле с async / await минус пул (пока).

const patchFormsRoute = (req, res) => {
  const { id } = req.body
  // list of JSONB fields for the 'forms' table
  const jsonFields = [
    'sections',
    'editors',
    'descriptions',
  ]

  // list of all fields, including JSONB fields in the 'forms' table
  const possibleFields = [
    'status',
    'version',
    'detail',
    'materials',
    ...jsonFields,
  ]
  const parts = []
  const params = [id]

  ;(async () => {
    const client = await new Client(dbConfig)
    await client.connect()
    try {
      // begin a transaction
      await client.query('BEGIN')

      // get the current form data from DB
      const fetchResult = await client.query(
        SQL`SELECT * FROM forms WHERE id = ${id}`,
      )

      if (fetchResult.rowCount === 0) {
        res.status(400).json(0)
        await client.query('ROLLBACK')
      } else {
        const currentRecord = fetchResult.rows[0]

        // patch JSONB values or update non-JSONB values
        let val = []

        possibleFields.forEach(myProp => {
          if (req.body[myProp] != undefined) {
            parts.push(`${myProp} = $${params.length + 1}`)
            if (jsonFields.indexOf(myProp) > -1) {
              val = currentRecord[myProp]
              jsonpatch.applyPatch(val, req.body[myProp])
              params.push(JSON.stringify(val))
            } else {
              params.push(req.body[myProp])
            }
          }
        })

        const updateQuery =
          'UPDATE forms SET ' + parts.join(', ') + ' WHERE id = $1'

        // update record in DB
        const result = await client.query(updateQuery, params)

        // commit transaction
        await client.query('COMMIT')

        res.status(200).json(result.rowCount)
      }
    } catch (err) {
      await client.query('ROLLBACK')
      res.status(400).json(err.severity)
      throw err
    } finally {
      client.end()
    }
  })().catch(err => console.error(err.stack))
}
...