1

Let's say I have the following code:

db.task(t => {
    return t.none('set search_path to myschema').then(() => {
        return t.any('select * from mytable').then(results => {
            return t.none('set search_path to originalschema').then(() => {
                return results
      })
    })
  })
})

Could a query outside of db.task(), that happened to run in between of the two search_path changes inside db.task(), actually access the data in 'myschema' instead of 'originalschema'?

C. Rib
  • 340
  • 3
  • 19

1 Answers1

1

Could a query outside of db.task(), that happened to run in between of the two search_path changes inside db.task(), actually access the data in 'myschema' instead of 'originalschema'?

No.

SET search_path is a session-based operation, i.e. it applies only to the current connection, which the task allocates exclusively for the entire duration of its execution.

Once the task has finished, it releases the connection back to the pool. At that point, any query that gets that same connection will be working with the alternative schema, unless it is another task that sets the schema again. This gets tricky, if you are setting schema in just one task, and generally not recommended.

Here's how it should be instead:

  • If you want to access a special-case schema inside just one task, best is to specify the schema name explicitly in the query.
  • If you want to set custom schema(s) dynamically, for the entire app, best is to use option schema, of the Initialization Options. This will propagate the schema automatically through all new connections.
  • If you want to set schema statically, there are queries for setting schema permanently.

Addition:

And if you have a very special case, whereby you have a task that needs to run reusable queries inside an alternative schema, then you would set the schema in the beginning of the task, and then restore it to the default schema at the end, so any other query that picks up that connection later won't try to use the wrong schema.

Extra:

Example below creates your own task method (I called it taskEx), consistent across the entire protocol, which accepts new option schema, to set the optional schema inside the task:

const initOptions = {
    extend(obj) {
        obj.taskEx = function () {
            const args = pgp.utils.taskArgs(arguments); // parse arguments
            const {schema} = args.options;
            delete args.options.schema; // to avoid error thrown
            if (schema) {
                return obj.task.call(this, args.options, t => {
                    return t.none('SET search_path to $1:name', [schema])
                        .then(args.cb.bind(t, t));
                });
            }
            return obj.task.apply(this, args);
        }
    }
});

const pgp = require('pg-promise')(initOptions);

So you can use anywhere in your code:

const schema = 'public';
// or as an array: ['public', 'my_schema'];

db.taskEx({schema}, t => {
    // schema set inside task already;
});

Note that taskEx implementation assumes that the schema is fully dynamic. If it is static, then there is no point re-issuing SET search_path on every task execution, and you would want to do it only for fresh connections, based on the following check:

const isFreshConnection = t.ctx.useCount === 0;

However, in that case you would be better off using initialization option schema instead, as explained earlier.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • Thank you. Would it be a problem, if for example I opened a new task for every query? For example every time I do db.any, I do db.task instead to make use of the schema-deciding callback on the schema prop of the init options? – C. Rib Feb 24 '21 at 22:35
  • It won't be a problem, technically, but I fail to see how this can be beneficial either, for an app to do such things, or why. There is no point in creating a new task for every single query. – vitaly-t Feb 24 '21 at 22:39
  • I don't know which schema a query might need to use until the moment I call it, and db.task seems like the easiest way of doing things considering I wouldn't need to refactor the whole project like I would have to if I used the extend property – C. Rib Feb 24 '21 at 22:43
  • 1
    Ok. By the way, you do not need to refactor a project to start using `extend`, it lets you do this in small steps, whereby you would start with just one table only. Also, you can create your own `task` that controls schema internally, using `extend` ;) – vitaly-t Feb 24 '21 at 23:06
  • 1
    @C.Rib See added example of creating your own `task` method, which extends predefined task options to support `schema`. – vitaly-t Feb 24 '21 at 23:48
  • 1
    @C.Rib You are welcome! Please note that there was an issue in the original example for `taskEx`, passing the wrong context with `bind` call, which I now have rectified ;) – vitaly-t Feb 25 '21 at 18:13