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'?

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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?
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.
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 ;)
@C.Rib See added example of creating your own task method, which extends predefined task options to support schema.
@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 ;)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.