使用pg-promise的嵌套事务

时间:2017-01-15 14:07:43

标签: node.js postgresql transactions pg-promise

我正在使用NodeJS,PostgreSQL和令人惊叹的pg-promise库。就我而言,我想执行三个主要查询:

  1. 在表格'tweets'中插入一条推文。
  2. 如果推文中有主题标签,请将它们插入另一个表'hashtags'
  3. 他们将tweet和hashtag链接到第三个表'hashtagmap'(多对多关系表)
  4. 以下是请求正文(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"
        }
    }
    

    除了主题标签之外,上面的所有字段都应该包含在表格“tweets”中,而这些标签应该包含在表格“hashtags”中。

    以下是我使用的代码基于NodeJS模块中pg-promise文档的嵌套事务。我想我需要嵌套事务,因为我需要知道tweet_idhashtag_id,以便在hashtagmap表中链接它们。

    // 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);
                });
        })
    }
    

    问题在于这段代码我能够成功插入推文但是没有任何反应。我无法插入主题标签,也无法将主题标签链接到推文。

    很抱歉,我不熟悉编码,所以我想我不明白如何从事务中正确返回以及如何执行这个简单的任务。希望您能够帮助我。

    提前谢谢。

2 个答案:

答案 0 :(得分:1)

改进Jean Phelippe自己的答案:

// 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中的工作方式(read this,特别是Limitations部分)。
  • 避免手动查询格式化,不安全,始终依赖于库的查询格式。
  • 除非您在其他地方传递交易结果,否则至少应该提供.catch处理程序。

P.S。对于+tweet.id这样的语法,它与parseInt(tweet.id)相同,只是更短,以防它们是字符串;)

答案 1 :(得分:0)

对于那些将面临类似问题的人,我会发布答案。

首先,我的错误:

  1. 在for循环中:body.hashtag.length不存在,因为我正在处理一个对象(这里是非常基本的错误)。已更改为Object.keys(body.hashtags).length
  2. 为什么要使用这么多交易?按照vitaly-t的回答:Interdependent Transactions with pg-promise我删除了额外的交易。目前尚不清楚如何在同一笔交易中打开一个交易并将一个查询的结果用于另一个交易。
  3. 以下是最终代码:

        // 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);
                    });
            }),