Вложенные транзакции с pg-promise

Я использую NodeJS, PostgreSQL и замечательную библиотеку pg-promise. В моем случае я хочу выполнить три основных запроса:

  1. Вставьте один твит в таблицу «tweets».
  2. Если в твите есть хэштеги, вставьте их в другую таблицу «хэштеги».
  3. Они связывают твит и хэштег в третьей таблице «hashtagmap» (реляционная таблица «многие ко многим»).

Вот пример тела запроса (JSON):

{
"id":"12344444",
"created_at":"1999-01-08 04:05:06 -8:00",
"userid":"@postman",
"tweet":"This is the first test from postman!",
"coordinates":"",
"favorite_count":"0",
"retweet_count":"2",
"hashtags":{
    "0":{
        "name":"test",
        "relevancetraffic":"f",
        "relevancedisaster":"f"
    },
    "1":{
        "name":"postman",
        "relevancetraffic":"f",
        "relevancedisaster":"f"
    },
    "2":{
        "name":"bestApp",
        "relevancetraffic":"f",
        "relevancedisaster":"f"
    }
}

Все вышеперечисленные поля должны быть включены в таблицу «твиты» кроме хэштегов, которые, в свою очередь, должны быть включены в таблицу «хэштеги».

Вот код, который я использую на основе вложенных транзакций из документации pg-promise внутри модуля NodeJS. Я предполагаю, что мне нужны вложенные транзакции, потому что мне нужно знать как tweet_id, так и hashtag_id, чтобы связать их в таблице хэштегов.

// Columns
var tweetCols = ['id','created_at','userid','tweet','coordinates','favorite_count','retweet_count'];

var hashtagCols = ['name','relevancetraffic','relevancedisaster'];

//pgp Column Sets
var cs_tweets = new pgp.helpers.ColumnSet(tweetCols, {table: 'tweets'});

var cs_hashtags = new pgp.helpers.ColumnSet(hashtagCols, {table:'hashtags'});
return{
// Transactions
add: body =>
    rep.tx(t => {
        return t.one(pgp.helpers.insert(body,cs_tweets)+" ON CONFLICT(id) DO UPDATE SET coordinates = "+body.coordinates+" RETURNING id")
            .then(tweet => {
                var queries = [];
                for(var i = 0; i < body.hashtags.length; i++){
                    queries.push(
                        t.tx(t1 => {
                            return t1.one(pgp.helpers.insert(body.hashtags[i],cs_hashtags) + "ON CONFLICT(name) DO UPDATE SET fool ='f' RETURNING id")
                                .then(hash =>{
                                    t1.tx(t2 =>{
                                        return t2.none("INSERT INTO hashtagmap(tweetid,hashtagid) VALUES("+tweet.id+","+hash.id+") ON CONFLICT DO NOTHING");
                                    });
                                });
                        }));
                }
                return t.batch(queries);
            });
    })
}

Проблема в том, что с этим кодом я могу успешно вставить твит, но тогда ничего не происходит. Я не могу вставить хэштеги или связать хэштег с твитами.

Извините, но я новичок в кодировании, поэтому, думаю, я не понял, как правильно вернуться из транзакции и как выполнить эту простую задачу. Надеюсь, вы можете мне помочь.

Заранее спасибо.

Жан


person Jean Phelippe    schedule 15.01.2017    source источник


Ответы (2)


Улучшение собственного ответа Жана Филиппа:

// Columns
var tweetCols = ['id', 'created_at', 'userid', 'tweet', 'coordinates', 'favorite_count', 'retweet_count'];

var hashtagCols = ['name', 'relevancetraffic', 'relevancedisaster'];

//pgp Column Sets
var cs_tweets = new pgp.helpers.ColumnSet(tweetCols, {table: 'tweets'});

var cs_hashtags = new pgp.helpers.ColumnSet(hashtagCols, {table: 'hashtags'});

return {
    /* Tweets */
    // Add a new tweet and update the corresponding hash tags
    add: body =>
        db.tx(t => {
            return t.one(pgp.helpers.insert(body, cs_tweets) + ' ON CONFLICT(id) DO UPDATE SET coordinates = ' + body.coordinates + ' RETURNING id')
                .then(tweet => {
                    var queries = Object.keys(body.hashtags).map((_, idx) => {
                        return t.one(pgp.helpers.insert(body.hashtags[i], cs_hashtags) + 'ON CONFLICT(name) DO UPDATE SET fool = $1 RETURNING id', 'f')
                            .then(hash => {
                                return t.none('INSERT INTO hashtagmap(tweetid, hashtagid) VALUES($1, $2) ON CONFLICT DO NOTHING', [+tweet.id, +hash.id]);
                            });
                    });
                    return t.batch(queries);
                });
        })
            .then(data => {
                // transaction was committed;
                // data = [null, null,...] as per t.none('INSERT INTO hashtagmap...
            })
            .catch(error => {
                // transaction rolled back
            })
},

ПРИМЕЧАНИЯ:

  • Согласно моим заметкам ранее, вы должны связать все запросы, иначе вы получите свободные обещания.
  • Держитесь подальше от вложенных транзакций, если вы точно не понимаете, как они работают в PostgreSQL (прочитайте это и, в частности, раздел Limitations).
  • Избегайте ручного форматирования запросов, это небезопасно, всегда полагайтесь на форматирование запросов в библиотеке.
  • Если вы не передаете результат транзакции куда-то еще, вы должны как минимум предоставить обработчик .catch.

P.S. Для синтаксиса типа +tweet.id это то же самое, что и parseInt(tweet.id), только короче, если это строки ;)

person vitaly-t    schedule 15.01.2017
comment
Спасибо за ваш ответ! Всегда предоставляя нам полезные и проницательные комментарии / ответы. С Уважением - person Jean Phelippe; 19.01.2017

Для тех, кто столкнется с подобной проблемой, я опубликую ответ.

Во-первых, мои ошибки:

  1. В цикле for: body.hashtag.length не существует, потому что я имею дело с объектом (здесь очень простая ошибка). Изменено на Object.keys(body.hashtags).length
  2. Зачем использовать так много транзакций? После ответа vitaly-t в: Взаимозависимые транзакции с pg-promise я удалил дополнительные транзакции. Мне пока непонятно, как можно открыть одну транзакцию и использовать результат одного запроса в другой в той же транзакции.

Вот окончательный код:

    // Columns
var tweetCols = ['id','created_at','userid','tweet','coordinates','favorite_count','retweet_count'];

var hashtagCols = ['name','relevancetraffic','relevancedisaster'];

//pgp Column Sets
var cs_tweets = new pgp.helpers.ColumnSet(tweetCols, {table: 'tweets'});

var cs_hashtags = new pgp.helpers.ColumnSet(hashtagCols, {table:'hashtags'});

return {
    /* Tweets */
    // Add a new tweet and update the corresponding hashtags
    add: body =>
        rep.tx(t => {
            return t.one(pgp.helpers.insert(body,cs_tweets)+" ON CONFLICT(id) DO UPDATE SET coordinates = "+body.coordinates+" RETURNING id")
                .then(tweet => {
                    var queries = [];
                    for(var i = 0; i < Object.keys(body.hashtags).length; i++){
                        queries.push(
                            t.one(pgp.helpers.insert(body.hashtags[i],cs_hashtags) + "ON CONFLICT(name) DO UPDATE SET fool ='f' RETURNING id")
                                .then(hash =>{
                                    t.none("INSERT INTO hashtagmap(tweetid,hashtagid) VALUES("+tweet.id+","+hash.id+") ON CONFLICT DO NOTHING");
                                })
                            );
                    }
                    return t.batch(queries);
                });
        }),
person Jean Phelippe    schedule 15.01.2017
comment
У вас есть проблема с ` t.none(INSERT INTO hashtagmap...`, вы должны вернуть результат из него, то есть return t.none("INSERT INTO hashtagmap..., иначе вы создадите свободное обещание. Каждый отдельный запрос запроса должен быть цепочкой, это ключевое правило, унаследованное от обещаний. - person vitaly-t; 15.01.2017
comment
It's not yet clear for me how you can open one transaction and use the result of one query into another in the same transaction. - так же, как и с промисами в целом. - person vitaly-t; 15.01.2017