2

I'm new to CakePHP and using version 1.3.

How can I dynamically change the 'schema' property as found in DATABASE_CONFIG prior to any database operation? What is the class where I could have the postgres-specific command "set search_path to 'schema_xyz'" executed before any database interaction?

I want to use Postgres' ability to maintain multiple distinct namespaces (aka schema in postgres parlance) within a single database to implement multi-tenancy in my application. That is, every namespace will contain the same set of tables, but evidently with different content. Here, it's important not to understand schema as meaning table metadata, but rather as the postgres-specific concept of namespace where a schema is a container for tables. The exact Postgres command isn't important. What is, is the mechanism by which it can be invoked, and steering clear of Cake's typical meaning of table description, as seen in the SchemaShell. The only place I have found where Cake exposes the concept of namespace is in the database.php file, which is then used when the DB connection is first established. See: api13.cakephp.org/view_source/dbo-postgres/#line-113 (new user link limit, sorry)

    if ($this->connection) {
      $this->connected = true;
      $this->_execute("SET search_path TO " . $config['schema']);

I want to set that search_path before ALL DB queries, not just at connection time as is currently done.

As a proof of concept, I have tried setting $useDbConfig in my models, but according to the debug output where the SQL commands are printed, this only seems to affect a subset of all queries. I've moved this up into app_model.php with the same result. As did augmenting that with creating a db_config instance on the fly and passing to the ConnectionManager through loadDataSource. Maybe I should slap that code in all flavors of before... methods.

I have seen many posts online where people discuss using one of several DB configurations in database.php to use different databases for dev, lab and production environments. But I have a single database with multiple namespaces/schemas. Also, my number of such namespaces will be too high and dynamic to make hardcoding a new variable in database.php practical.

Thus, where is the spot in CakePHP where I could insert something to set the search_path prior to any database command? I'll deal with optimizing that later. Remember that I'm new to Cake, so try to be as specific as you can. Let me know if I can clarify this question.

Thanks in advance. Here's the partially working code snippet:

class AppModel extends Model {
  function beforeFind($queryData)
  {
    App::import("ConnectionManager");
    $cm = &ConnectionManager::getInstance();

    $namespace = 'xyz_namespace';            //name of the new schema/namespace/search path 
    $new_db_config_name = 'new_config';      //name for the new DB config to be used in the ConnectionManager
    $new_db_config = $cm->config->default; //copy the 'default' DB config into an array
    $new_db_config['schema'] = $namespace; //change/add the new schema/namespace/search path

    $cm->create($new_db_config_name, $new_db_config);   //turn the array into a DbConfig object
    $cm->loadDataSource($new_db_config_name);           //load the new DbConfig into the ConnectionManager
    $this->useDbConfig = $new_db_config_name;       //tell the model to new use the Db Config


    return $queryData;
  } 
}
JP Beaudry
  • 194
  • 5
  • i think you are looking for something [like this](http://book.cakephp.org/view/1576/Models-Behaviors-and-Datasource) in the part of cakeSchema and tableParameters. Hope it works for you – api55 Oct 13 '11 at 20:40
  • @api55 - Though interesting, I think the doc snippet refers to schema as in "table metadata", whereas I meant "namespace". I've updated my post hoping to clarify. The Psql cmd I want to execute prior to any DB cmd is 'set search_path to xyz'. That should affect the whole DB connection and it's not per se a table parameter. Or maybe I just didn't understand the doc snippet, happens quite a bit. – JP Beaudry Oct 14 '11 at 02:06
  • @JPBeaudry - did you ever get this figured out - I have the same requirements as you do. I don't see much help from the already provided answers as you may want to switch during a session to another schema. I have the same need. Thanks! – Charles Harmon Jan 01 '15 at 21:24

2 Answers2

1

There is a very simple way in PostgreSQL if you want to switch schema per login role:

ALTER ROLE foo SET search_path=bar, public;
ALTER ROLE baz SET search_path=bam, public;

Thus a connection initiated by that role has that search_path set automatically.
If your login names are the same as the desired schema names, there is an even simpler way, I quote the fine manual:

If one of the list items is the special value $user, then the schema having the name returned by SESSION_USER is substituted, if there is such a schema. (If not, $user is ignored.)

But be advised that - the fine manual again:

Role-specific variable settings take effect only at login; SET ROLE and SET SESSION AUTHORIZATION do not process role-specific variable settings.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Erwin - Of course, you are correct. However, I forgot to mention that thus far I'm only using a single Postgres-level user. I'll eventually split that into a few roles, but didn't envision doing it on a per app-user basis. Signon/signoff in the web site is handled from credentials stored in a "users" DB table, as per the CakePHP tutorials. I'll potentially (cross-fingers) have 1000s of users and I'm wondering if it'd be wise to have so many Postgres-level users. Do you know if there are limits I should worry about on Roles? I wonder how that intersects with Cake's expectation of a Users table. – JP Beaudry Oct 14 '11 at 04:18
  • I use that method for small applications with just a few dozen users. For a big app like you describe you would want to use connection pooling to save overhead, connecting with a few or just one user. You probably want another solution. – Erwin Brandstetter Oct 14 '11 at 05:23
0

If I understand your question correctly, (bear with me, I know little about Postgres but basically I think you mean, reloading the schema whenever the table perspective changes?), here's how to dynamically switch schemas in your controller:

// Model::getDataSource()->configKeyName holds whichever db config you're using
if ($this->Model->getDataSource()->configKeyName != 'default') {
    // do something...
    $this->loadModel("Special")
    $this->Model->table = "extras";
    $this->Model->schema(true);
} else {
    // predictably, Model::setDataSource($configKey) changes configs
    $this->Model->setDataSource("offsite"); // this could be a string variable
}

Or from the model, $this->getDataSource()->configKeyName and $this->schema(true) and so forth. Note $this->schema(true) actually reloads the model schema and registers it with cake. app_model, a component, or config/bootstrap might be an appropriate place for this. I'm not sure where Cake would have defined the search_path the first time, but it would almost certainly be a property of the dataSource object and could be redefined there just like the table name, etc. And then reload Cake's schema to register the changed path. It is necessary to ensure Cake unloads any default it may have picked up, and load the correct schema based on the currently defined table. (It sounds like this may have been the only step you were missing.)

If this does not answer your question or if I misunderstood, let me know. HTH. :)

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
OpenSorceress
  • 2,014
  • 16
  • 21
  • I'm afraid I didn't sufficiently explained that in Postgres, the word 'schema' means two different things (very unfortunate). I've thus updated my post a bit. I do not want to reload the model schema as described above. What I want to do, is instruct Postgres to change its 'search_path' prior to the next DB query. Imagine that the same table exists multiple times in a given database, but each copy lies in a different namespace. Postgres uses the search_path to know which copy will be returned when queried. – JP Beaudry Oct 14 '11 at 01:59