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;
}
}
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.
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. :)
Related
My MS SQL 2014 database table has a computed property column which uses a database function. Using SQL Server Management Studio, a query against the table lists the computed property values as expected.
The Codefluent model created via the import wizard shows the Entity with the computed column as a property. The underlying .cpf file defines the property with "d3p1:compute=" and the list of parameters that are used by the database function.
When an entity or the collection of entities is loaded, the properties which are used in the computed property have values, yet the computed property has a value of nothing/null.
How do I get Codefluent to read the computed value from the database table and have the value included in the entity's properties?
This is a bit tricky. First of all, you should declare the property like any other property. Then you must instruct the SQL producer to declare a formula on that column. You can do that with a custom 'compute' attribute in the SQL producer namespace. You can set it with the Visual Studio modeler like this:
In this example I've created an int property that is just another column value multiplied by 2.
Optionally, you can declare the property to be 'read on save' because most of the time, you want to read the computed value after a save, not only on load operations:
Once this is all done, this sample console app should display 30:
class Program
{
static void Main(string[] args)
{
var c = new Customer();
c.Name = "killroy";
c.Age = 15;
c.Save();
Console.WriteLine(c.Age2); // will display 30
}
}
If Simon Mouriers solution solves your problem than that is probably the best approach. However, there are 2 other options
RAW View Method
After you create a Codefluent Entities View click on the Edit Where button and it will allow you to create a RAW View
You can than specify the advanced property "UsedForMethods".
WARNING: Related entities will use the table instead of the view. This is by design and there is an article somewhere on the knowledge center on how to get around it. http://www.softfluent.com/product/codefluent-entities/knowledge-center/
Rename SQL Tables and Create a SQL View with the Same Name as the Original Table - This method is a hack, Softfluent discourages this approach, I love it because I know exactly what is happening under the scenes. I have used it with success in a scenario in which I needed soft deletes. I have automated the process with 2 stored procedures that handle the renaming. Using this approach requires running one of the stored procedures to undo the name changing prior to building the model. The other stored procedure handles the renaming after building the model. I'll post the stored procedures and how I use them within a couple of days.
We're currently transitioning from one database to another. A table in our legacy database has column names that are less than ideal, for example:
some_crazy_name__xyz
In our new database, we'd like to have a column name like:
someCrazyName
In the short term, we have to work with data from our legacy database. At some point in the near future, we'd like to switch over without having to refactor all of our Eloquent code to query for different column names. For example:
$model = MyModel::where('someCrazyName', '=', 1);
I'm currently extending the Model class, where all implementing models provide a map of terrible names to friendly names:
class MyModel extends BaseModel {
$columnMap = array(
'someCrazyName' => 'some_crazy_name__xyz'
);
}
This works well where I can use __get and __set in BaseModel to lookup properties in my map, for example:
$myModel = new MyModel;
// ...
echo $myModel->someCrazyName;
However, this obviously doesn't work well with queries without having to always use my map to look up column names. I'm wondering if it's possible without having to override all of the methods within Illuminate\Database\Eloquent\Model, Illuminate\Database\Query\Builder and Illuminate\Database\Eloquent\Builder that deal with columns, where the underlying query that is built always maps to the correct column? Then after we transition databases, we can remove that one piece of code rather then remove potentially thousands of column name mappings.
This is what you need: https://github.com/jarektkaczyk/eloquence/wiki/Mappable
It's not only for mapping badly_named_columns to something_useful, but also can be used for relational mappings:
// simple aliasing
User::where('cool_name', 'value') // where badName = ?
// relations, eg. User hasOne Profile
User::where('first_name', 'Jon') // search through related profiles table
// and obviously mutators:
$user->first_name == $user->profile->first_name
$user->cool_name = 'Jon' // becomes $user->badName = 'value'
$user->cool_name; // 'Jon'
One way to do it would be with accessors.
For example, in MyModel you could define an accessor for the some_crazy_name__xyz column like this:
public function getSomeCrazyNameAttribute()
{
return $this->attributes['some_crazy_name__xyz'];
}
You can then transparently refer to that column with $mymodel->someCrazyName. You can also define a mutator to set the value.
Admittedly, this may not be the best solution if you have MANY values like this. But it does have one important benefit: later on, if you refactor your database so that the column some_crazy_name__xyz is actually called someCrazyName, all you need to do is remove that function from your model. And, to my mind at least, it's simpler than trying to override a bunch of methods on the various classes involved.
And unfortunately, it doesn't adequately address the use of column names in queries. For that, you might want to look at the repository pattern. But in any event, it looks like there's going to be a lot of coding involved.
Finally, you haven't mentioned what database you're using. If it's MySQL, it is possible to create updatable and insertable views. Using a view, you could simply map old column names to new, and point your Eloquent model at the view instead of a table. Other database servers may provide similar functionality.
I have my reflection configured like so:
meta = MetaData()
meta.reflect(bind=db.engine, schema='web')
Base = automap_base(metadata=meta)
Base.prepare()
I thought the schema keyword would limit the reflection to the specified schema, but running the app I see that it reflects tables in all schemas anyway, which leads to some conflicts given I have tables of the same name in different schemas. So Base.classes will contain the wrong classes given that it uses the wrong schema.
What are my options here?
You can limit the tables reflected in MetaData.reflect using the only keyword.
meta.reflect(bind=db.engine, schema='web', only=tables)
If you don't know the tables in the particular schema ahead of time, you can use an Inspector object to get them.
insp = reflection.Inspector.from_engine(db.engine)
tables = insp.get_table_names(schema='web')
I have a legacy database with a particular table -- I will call it ItemTable -- that can have billions of rows of data. To overcome database restrictions, we have decided to split the table into "silos" whenever the number of rows reaches 100,000,000. So, ItemTable will exist, then a procedure will run in the middle of the night to check the number of rows. If numberOfRows is > 100,000,000 then silo1_ItemTable will be created. Any Items added to the database from now on will be added to silo1_ItemTable (until it grows to big, then silo2_ItemTable will exist...)
ItemTable and silo1_ItemTable can be mapped to the same Item entity because the table structures are identical, but I am not sure how to set this mapping up at runtime, or how to specify the table name for my queries. All inserts should be added to the latest siloX_ItemTable, and all Reads should be from a specified siloX_ItemTable.
I have a separate siloTracker table that will give me the table name to insert/read the data from, but I am not sure how I can use this with entity framework...
Thoughts?
You could try to use the Entity Inheritance to get this. So you have a base class which has all the fields mapped to ItemTable and then you have descendant classes that inherit from ItemTable entity and is mapped to the silo tables in the db. Every time you create a new silo you create a new entity mapped to that silo table.
[Table("ItemTable")]
public class Item
{
//All the fields in the table goes here
}
[Table("silo1_ItemTable")]
public class Silo1Item : Item
{
}
[Table("silo2_ItemTable")]
public class Silo2Item : Item
{
}
You can find more information on this here
Other option is to create a view that creates a union of all those table and map your entity to that view.
As mentioned in my comment, to solve this problem I am using the SQLQuery method that is exposed by DBSet. Since all my item tables have the exact same schema, I can use the SQLQuery to define my own query and I can pass in the name of the table to the query. Tested on my system and it is working well.
See this link for an explanation of running raw queries with entity framework:
EF raw query documentation
If anyone has a better way to solve my question, please leave a comment.
[UPDATE]
I agree that stored procedures are also a great option, but for some reason my management is very resistant to make any changes to our database. It is easier for me (and our customers) to put the sql in code and acknowledge the fact that there is raw sql. At least I can hide it from the other layers rather easily.
[/UPDATE]
Possible solution for this problem may be using context initialization with DbCompiledModel param:
var builder = new DbModelBuilder(DbModelBuilderVersion.V6_0);
builder.Configurations.Add(new EntityTypeConfiguration<EntityName>());
builder.Entity<EntityName>().ToTable("TableNameDefinedInRuntime");
var dynamicContext = new MyDbContext(builder.Build(context.Database.Connection).Compile());
For some reason in EF6 it fails on second table request, but mapping inside context looks correct on the moment of execution.
I have a public synonym on my server for transactions: However I'd like to be able to work with my own local version of the table, so as not to disturb other users.
Does Oracle's SQL resolve naming conflicts like this in a predictable fashion?
In other words, if another user creates a public synonym called TRANSACTION and I do this:
CREATE TABLE TRANSACTION (
ID NUMBER
);
When I write
select * from TRANSACTION
Do I have any guarantee that Oracle will always resolve the synonym or my local table?
(I know I could technically specify schema.TRANSACTION to force the issue, but in my case that would require me to modify/rebuild an application and I'm hoping to save some work.)
Your understanding of name resolution is correct. Oracle will first look in the current schema to find an object with that name. So, In case of a conflict, It will choose an object in the current schema instead of the object referred to by the public synonym.
http://docs.oracle.com/cd/B28359_01/server.111/b28310/general008.htm
Oracle Database attempts to qualify the first piece of the name
referenced in the SQL statement. For example, in scott.emp, scott is
the first piece. If there is only one piece, the one piece is
considered the first piece.
In the current schema, the database searches for an object whose name
matches the first piece of the object name. If it does not find such
an object, it continues with step b.
The database searches for a public synonym that matches the first
piece of the name. If it does not find one, it continues with step c.
The database searches for a schema whose name matches the first piece
of the object name. If it finds one, it returns to step b, now using
the second piece of the name as the object to find in the qualified
schema. If the second piece does not correspond to an object in the
previously qualified schema or there is not a second piece, the
database returns an error.
If no schema is found in step c, the object cannot be qualified and
the database returns an error.
Having said that, this is one of the problems with public synonyms. Having objects like this will lead to confusion down the road, both for development and support. You are better off referencing the object by owner and name in both cases