fiatjaf / pgjson

use Postgres as a zero-config NoSQL database.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Refactoring + suggestions

vitaly-t opened this issue · comments

Your library is fresh, but got some good attention already. There is a bit of code refactoring you're likely to do later on. And as far as the database goes, I'd like to help you ;)

You are using wait everywhere, but it doesn't do anything useful.

this.wait = Promise.resolve()

Some of the code I'm not sure how it is expected to work on your side, like this example:

db.many('SELECT doc FROM unnest(ARRAY[$1]) WITH ORDINALITY AS u(id, ord) LEFT JOIN pgjson.main AS m ON m.id = u.id ORDER BY u.ord', [ids])

When your parameter ids is an array, it will be automatically formatted as array[va1,val2,...], so when you are inserting it as ARRY[$1], it will produce ARRAY[array[val1, val2,...]]. I'm not sure if that's the expected behaviour, so I just wanted to point out at it.

Another example for refactoring, method manyOrNone has alias any, in case you want to use it ;)

If you want to get a good sense of what and how is being executed exactly against the database, I would really suggest to use pg-monitor, and here's an example.

Strange, in my tests this query had the correct results, so maybe in this query there's actually no problem with having ARRAY[array[val1, val2,...]] instead of array[val1, val2,...]. Anyway, thank you for informing me. I need a tool like pg-monitor, I just didn't know it existed.

this.wait is there to wait for the initial setup of tables:

pgjson/index.js

Line 175 in f085c3d

this.wait = this.wait.then(function () {

Thanks for getting back to me. I surely could find a lot more, but you have time to polish this thing ;)

var r = opts.descending ? 'DESC' : 'ASC' - declared but not used variable ;)

It is being used here:

pgjson/index.js

Line 139 in f085c3d

return db.manyOrNone("SELECT doc FROM pgjson.main ORDER BY doc->" + criteria + ' ' + r + ", doc->'_id' " + r + " LIMIT $1 OFFSET $2", [l, f])

It sure needs a lot of polishing, but please keep finding bugs whenever you have time!

Ok, here's then the method refactored, to show better use of the query formatting:

pgjson.prototype.query = function (opts) {
    opts = opts || {}
    var o = opts.orderby
    var db = this.db
    return this.wait.then(function () {
            var params = {
                limit: opts.limit || 1000,
                offset: opts.offset || 0,
                order: opts.descending ? 'DESC' : 'ASC'
            };
            if (opts.orderby) {
                var terms = o.split(/[\.[]/).map(function (t) {
                    if (t.slice(-1)[0] == ']') {
                        t = t.slice(0, -1)
                        if (!isNaN(parseInt(t))) {
                            return t
                        }
                    }
                    return "'" + t + "'";
                });
                params.criteria = terms.join('->');
                return db.any("SELECT doc FROM pgjson.main ORDER BY doc->${criteria^} ${order^}, doc->'_id' ${order^} LIMIT ${limit} OFFSET ${offset}", params);
            }
            return db.any('SELECT doc FROM pgjson.main ORDER BY id LIMIT ${limit} OFFSET ${offset}', params);
        })
        .then(function (rows) {
            return rows.map(function (r) {
                return r.doc;
            })
        })
}

Query formatting in pg-promise is powerful, you do not need to use manual string concatenation ever ;)

thank you, @vitaly-t!

maybe you could also help me with

pgjson/index.js

Lines 41 to 64 in fa83fcc

pgjson.prototype.postBulk = function (docs) {
var db = this.db
var ids = []
var valuesq = []
var valuesv = []
for (var i = 0; i < docs.length; i++) {
var doc = docs[i]
var j = 1 + i*2
var id = cuid()
doc._id = id
ids.push(id)
valuesv.push(id)
valuesv.push(doc)
valuesq.push('($' + j + ',' + '$' + (j+1) + ')')
}
return this.wait.then(function () {
sql = "INSERT INTO pgjson.main (id, doc) VALUES " + valuesq.join(',')
return db.none(sql, valuesv)
}).then(function () {
return {ok: true, ids: ids}
}).catch(handle('problem posting docs'))
}
?

I'm not quite sure about the expected output there, so wouldn't be wise refactoring without understanding the formatting logic well. If you ask me a more specific formatting question, I'd be able to help you there.

One thing for sure, introducing global variables like sql:

sql = "INSERT INTO pgjson.main (id, doc) VALUES " + valuesq.join(',')

is advised against.

Yeah, that's a Coffeescript vice. Coffeescript, that language that is exactly equal to Javascript, but where you don't have to write "var" before variables.

@vitaly-t the goal there is to insert a lot of rows at the same time. How am I supposed to do that without all that string concatenation I'm doing?

Something like this:

pgjson.prototype.postBulk = function (docs) {
    var db = this.db;
    var ids = [];
    return this.wait.then(function () {
            return db.none("INSERT INTO pgjson.main (id, doc) VALUES $1^", docs.map(function (d) {
                var id = cuid();
                d._id = id;
                ids.push(id);
                return pgp.as.format("($1, $2)", [id, d]);
            }).join(','));
        })
        .then(function () {
            return {ok: true, ids: ids}
        })
        .catch(handle('problem posting docs'))
};

@vitaly-t I searched for documentation on this kind of formatting on pg-promise docs, but couldn't find it. what does ^ mean?

again, thank you very much.

@fiatjaf Symbol ^ - that's Raw Text Injection, quite useful in your case, and in general, when formatting gets complex or multi-part.

@fiatjaf version 2.3.0 just became friendlier when it comes to JSON object formatting.

For example, the same code we changed the last time can become:

pgjson.prototype.postBulk = function (docs) {
    var db = this.db

    var ids = []
    var values = docs.map(function (doc) {
        var id = cuid()
        ids.push(id)
        doc._id = id
        return pgp.as.format("(${_id}, ${this})", doc)
    }).join(',')
    return this.wait.then(function () {
        return db.none("INSERT INTO pgjson.main (id, doc) VALUES $1^", values)
    }).then(function () {
        return {ok: true, ids: ids}
    }).catch(handle('problem posting docs'))
}

i.e. the change is in the line:

        return pgp.as.format("(${_id}, ${this})", doc)

I presume that you deliberately inject doc as a text string formatted as JSON instead of JSON object.