When I originally coded the multiple database support I noted the caveat that only one database type was useable at a time due to the conflicting function names, so including both database.pgsql.inc and database.mysql.inc would cause a crash.
This was also brought up again when someone reopened the multi-db functionality issue, and I am attaching the patch here.
I would love for this patch to get into 4.6 if possible. I don't believe it's very invasive, and it fixes a long standing bug in the db api. I have been using some form of this patch on Bryght for the last 3-4 months, and have not found any ill effects related to it.
I also clarified the db_set_active() comments a bit after I saw this issue (http://drupal.org/node/13115).
| Comment | File | Size | Author |
|---|---|---|---|
| #52 | database_9.patch | 29.99 KB | bjaspan |
| #48 | database-patch_2.txt | 24.55 KB | bjaspan |
| #44 | database-patch_1.txt | 24.24 KB | bjaspan |
| #42 | database-patch.txt | 25.07 KB | bjaspan |
| #38 | multi_db_wrappers.patch | 23.39 KB | rkerr |
Comments
Comment #1
Arto commentedWould love to have this in the core, but doesn't look like it's included yet, neither in 4.6.1 nor in the latest snapshot. Any idea yet if and when it'll be merged?
Comment #2
Arto commentedOops, didn't intend to change the title of this bug report. Hereby corrected.
Comment #3
adrian commentedHere is an updated and working patch.
We use this patch on Bryght, as our hosting infrastructure is based on postgres, but the sites themselves use mysql.
I originally wrote this patch as part of the install system however, since I found myself in the situation where you had a mysql master installation, and wanted to create a postgres site hosted 'underneath' it. (the install.php file is multisite aware).
Comment #4
drummLooks like the added constants in common.inc are leftover from something else.
Comment #5
killes@www.drop.org commentednot a bug
Comment #6
Chris Johnson commentedAdrian's code appears to be in the 4.7 core files now, so I have closed this issue. There is still a problem with throwing a watchdog message when an alternate database is selected. See issue 42000.
Comment #7
Chris Johnson commentedI was wrong, wrong, wrong. Trying to tie all db_set_active() issues together too fast.
Comment #8
hellsnail commentedI just tried using both mysql & postgres with db_set_active() and I got error:
"Fatal error: Cannot redeclare db_connect() (previously declared in /var/www/html/thirdside/drupal/includes/database.mysql.inc:23) in /var/www/html/thirdside/drupal/includes/database.pgsql.inc on line 71"
Comment #9
webchickYes, that behaviour is not currently supported. That's why this is classified as a feature request. And feature requests are only ever applied against CVS, not a release.
Comment #10
adrian commentedHere's the 4.7 (and current cvs) version of the patch.
Comment #11
adrian commentedstatus wrong
Comment #12
tarek commentedHey all!
I have applied the patch against 4.7.0, and am trying to use the module phpbb2drupal. However, I get the following:
repeatedly with some slight variation.
Is this because the module is badly formed? or does it represent a bug in the code?
tarek : )
Comment #13
tarek commentedHey again all!
I believe that I have narrowed down the problem a little more.. After excluding some non-MySQL-friendly code, I now get:
The key here being that 'mysqluser' is supposed to go with a mysql URL.
I think, then, that my problem is related to this portion of code:
Does the $db_type have to be set in some way by me? If so, perhaps it should auto-parse the db_url instead.
As it stands right now, this is the code being used to connect to the mysql:
Thanks in advance for the assistance.
tarek : )
Comment #14
adrian commentedI made a mistake with the previous patch. I was missing the escape functions.
Here is a new patch, against 4.7.2.
Also. db_url is automatically populated. it is set on line 114 of database.inc, and it is defined in global scope inside that function.
Comment #15
moshe weitzman commentedi'm doing a pgsql => mysql migration right nw and this patch is performing well. code looks sane also. RTBC.
Comment #16
chx commentedIf there would be a patch (code needs benchmark) I would have used that. This code will be executed a (few) hundred times on each and every Drupal page...
Comment #17
AmrMostafa commentedGlad there is such a patch!
I will benchmark that tomorrow and will post back the results.
Comment #18
killes@www.drop.org commentedFrankly, I think that this patch is a bit on the large side for a rarely used feature.
Comment #19
drummThere has (surprise for me too) been enough interest to seriously consider this. I do think the approach fits with Drupal. Banchmarking is a good idea.
Comment #20
beginner commentedbookmarking because this issue is relevant for my phpbb2drupal.module.
Comment #21
moshe weitzman commentedlets resubmit this for next release
Comment #22
adrian commentedHere's the DRUPAL 5 version of the patch
Comment #23
adrian commentedRegarding benchmarking, I'm almost certain that it will be slower.
My requirements were to make it all plug and play, without modifying all core calls to the db layer. Unfortunately I fear that there will be performance concerns related to this.
Basically I copied and pasted all the function definitions for the current db api, and wrote code that pointed the function calls at a new dispatcher I wrote (db_invoke). The entire process is eerliy similar to having to write phptemplate stubs to override functions, just to redirect them to the phptemplate template finder. (ie: endless 2 line functions).
If this makes it in core, I think it would be more sane to change core to call the dispatcher directly (ie: db_invoke('query', 'select * from blah')) ). Consider how often it's being called, we could even make a case for db('query', $string);. That is also nice because it's something a regular expression could make quick work of.
This dramatically reduces the amount of code that is required, and also removes an extra function call per db call (literally anything that is db_* now).
Comment #24
rkerr commentedI wrote basically the same thing for 4.6 a couple months ago. Shame I didn't find this issue until now!
I didn't make a new hook though. I simply wrapped db_{$db_type}_foo() calls with the default db_foo() and used the global $db_type variable to call the correct function depending on the active connection.
Comment #25
adrian commentedit's not really a new hook. Just a new function =)
It would remove a lot of duplication of code to just have the single :
A simple search replace could be used to change db_query and friends into db('query' and friends.
Comment #26
chx commentedI know it's a chore but if you would not use db_invoke and call_user_func_array but write $function($arg1, $arg2..) as needed (note that db_query has array syntax, so no variable number of arguments) then the speed loss would be actually small. variable function is slower but c_u_f_a is the slowest.
Comment #27
adrian commenteddb_query might handle arrays, but the rest of the db api definitely doesn't. Also, my issue is with how many additional functions need to be created for no apparent reason, optimizing the db() function wouldn't be too hard.
It could work if we made another one of those functions that do $arg1, $arg2, $arg3 as parameters all the way to 10 or something.
I haven't seen a function like that make it to core yet tho.
Comment #28
rkerr commentedI don't mind the db('query', ...) approach.. instead of 14 db_query like wrapper functions. Although with the wrapper functions it's easier see exactly what the arguments are without diging too many levels down. (func_get_args type stuff is a real pain for readability when you're trying to grok some code).
Although maybe some database-specific functions require more arguments than others (can we get the Oracle / DB2 patch guys in on this discussion)?
Properly supporting more than one type of database at once, I think is pretty important to have since we are already so close.
Maybe benchmarks of the call_user_func vs variable function approach are needed to see how we specifically end up there. There was a page somewhere about how to run a good benchmark on Drupal wasn't there?
Comment #29
chx commentedI have commented on the patch, sorry not on the further ideas of db(...).
If you want a db() then that probably would mean call_user_func_array and adding a hundred of that to a page is not a particularly good idea. You can bench this easily, change _db_query from a direct call to a call with call_user_func_array and let's see results.
Comment #30
dries commentedThis might be a case where it would be really helpful to have a database object. I eliminates a lot of ugly regex.
Comment #31
chx commentedRegex? What regex? The only regex that appeared in the issue is one that Adrian mentioned that could be used to upgrade the code to his model. Noone wanted to add any regex to core here.
Comment #32
rkerr commentedI ran some benchmarks on my laptop. Drupal core, vs vertice's db_invoke, vs using a generic wrapper with a variable function to call the db-specific implementation.
Testing environment...
Mac OS X 10.3.9
Drupal: 5
MySQL: 5.0.27
PHP: 4.4.4
Apache: 1.3.33
Vocabularies: 15
Terms: 250
Nodes: 2000
Comments: 8000
So, it looks like we could get full multi-database support by using variable functions without negatively affecting performance.
If the flexibility of Adrian's approach is desired, the impact doesn't seem to be huge, and there may be other places in the db layer to look for optimizations to offset that.
Comment #33
adrian commentedI'm not sure what you mean by the variable function call? Did you just remove the db_invoke call and replace it with $function?
Would we still have to have all those wrapper functions?
I believe if you make :
Remove the extra duplicate functions from database.inc
and do a search / replace for all calls to db_X( to become db('X',
It could be faster/ cleaner.
Comment #34
adrian commentedOh, and that last function also handles references, which is something that call_user_func_array doesn't like.
Comment #35
chx commented$funcis a variable function. Rokerr's figures support my notion, I believe that call_user_func_array is too slow.As the signature of
dbdoes not contain references it does not handle them but that's hardly a problem as we do not use references in the db layer.So, I am eagerly waiting for Adrian's
dbfunction then we bench it and decide on it.Comment #36
adrian commentedThe regex to change it is. :
s/db_([a-zA-Z_]*)\(/db('$1', /g
I am having some trouble getting it working with sed tho.
It doesn't like the escaped parenthesis before the / , and it doesn't work when i don't escape it. yay for the shell =\
Comment #37
rkerr commentedYes, I retained the wrapper calls like db_connect(...) -> $f = 'db_' . $db_type . '_connect'; return $f(...);
I don't really mind changing to db('connect' ...) instead, although the $arg1, $arg2 thing makes it really tough to figure out what arguments are useful in which context.
The only wrapper function I needed to use call_user_func_array in was db_query_range. (I think, there may have been one other).
Comment #38
rkerr commentedHere's kind of a least-disruptive implementation, renaming db.*.inc functions to db_*_foo() and having the generic db_foo() functions in database.inc. And then, using the global $db_type and variable functions to do the work.
Comment #39
ChrisKennedy commented#22: 4 out of 58 hunks fail - needs to be re-rolled if there is still interest.
#48: Your db_version generic implementation needs parentheses after the $f to return the function result rather than the string, and the coding style for string concatenations is wrong.
Comment #40
bjaspan commentedI have re-rolled this patch from #48 for HEAD and tested it (briefly) with both MySQL and PostgresQL. Works fine. I think it is better to have a variety of short functions in database.inc than to apply changes all over core *and* require every module developer to do the same.
The failed hunks were due to db_column_exists() being added after the previous patch was created.
Note that the pgsql in system.install got broken in HEAD on 4/25/07; you have to replace "int(1)" with "int" in order to test this code. I'll submit a fix for that momentarily, but it does not affect this patch.
Comment #41
ChrisKennedy commentedThere is a pgsql system.install fix posted to http://drupal.org/node/139673 - I think it's the same bug you're referring to.
You meant to attach an updated patch, right?
Comment #42
bjaspan commentedYes, I did. Sorry.
Comment #43
ChrisKennedy commentedMaybe I'm missing something, but do we really gain anything by checking if the function exists? If the application is calling a db function it seems that the desired db layer implementation should at least have a stub function.
Comment #44
bjaspan commentedI don't think you are missing something. All the db functions are required. If a .inc file does not implement one, it is a critical bug that must be fixed so just crashing Drupal by calling an undefined function is acceptable (arguably preferrable to returning null which might hide the fact that the function is missing).
New patch attached.
Comment #45
ChrisKennedy commentedI tested this on MySQL and couldn't find any bugs. Unfortunately I don't have access to mysqli/postgres to test on those.
Comment #46
bjaspan commentedI tested this with mysqli and postgres; no problems.
To review, the benefits of this patch are: 1. The ability to access multiple supported DBMS systems from within a a single Drupal process using the existing $db_url-as-array functionality. 2. A big step towards allowing DBMS backends as contrib modules; I'll be submitting a separate patch for the very small remaining step. 3. It is just ugly to define the same function names in multiple files.
Since Chris already reviewed the code for mysql and I just verified mysqli and postgres support, I'm marking RTBC.
Comment #47
chx commentedWhere are the benchmarks?
Comment #48
bjaspan commentedWhile making benchmarks I actually found a bug. system.install and system.module has code like ths:
Currently, only database.pgsql.inc defines db_check_setup(), and this works fine. With this patch, db_check_setup() is always defined as a dispatcher but database.mysql{,i}.inc did not define it. I added no-op functions.
A new patch is attached.
Benchmarks:
I confess to being surprised by these results. How can funcall+query > 102%*query?
If this is deemed unacceptable I'll look into what is causing the extra overhead (is it really one function call)? Can someone point me in the right direction for a PHP profiler?
Comment #49
chx commentedYou are calling a variable function. I would suggest rollng an --ugh-- patch which does a switch on known database types and bench that. You might be surprised.
Comment #50
moshe weitzman commentedsee xdebug.org for the profiler, and wincachegrind or kcachegrind in order to visualize the results in pretty graphs.
Comment #51
bjaspan commentedOkay, I wrote a little "call a no-op function from within an empty loop 50,000,000 times" and now see that (1) variable-function calls are about 30% slower than direct calls and (2) PHP5 is about 100% slower than PHP4.
Please remind me why we are using this POS language.
Anyway, using a switch based on known database types eliminates a primary benefit of this patch: support for db backend contrib modules. Unless, of course, I provide a 'default' case that uses the variable function call. Then contrib modules will be slower, but will work.
Comment #52
bjaspan commentedOkay, new patch, using hard-coded function calls for known db types and a variable function call for unknown db types. New benchmarks:
(The previous benchmarks were actually made with MySQL, not PostgresQL; apparently MySQL is faster at least on my system a few days ago.)
Comment #53
chx commentedI told you willbe surprised. Now, it's up to others -- does this speed penalty worth the superb capabilities? I would vote 'yes' and here is why: there is a DDL layer coming and after that comes the avalanche of DB layers and I believe there are some interesting mixes with SQLite and MySQL. But even before that, this is a very, very long outstanding request...
Comment #54
FiReaNGeL commentedI don't think it make sense to lose 1% of performance for a feature that will be used by less than 1% of users. To me its a solution in search of a problem. Can you describe a use-case for 2 db (postgre and mysql) in one site? I have to side with killes and drumm here and -1 it, but I'm open to discussion :)
Comment #55
moshe weitzman commentedI decided to exercise my new fast Macbook with some benchmarks. Unfortunately, I am seeing a 6% slowdown after this patch. I don't really believe it but there it is. I took a large sample (1000 requests) :(
Command:
ab -n 1000 -c 10 http://mm/pr/index.php(against current HEAD)BEFORE
AFTER
Comment #56
chx commentedI doubt this can be solved without going OOP -- and I am unsure of the performance implications.
Comment #57
bjaspan commentedI do not fully believe either my or Moshe's benchmarks. We are adding a single extra function call which cannot be such a large percentage increase over loading the files, compiling them, making the database queries, etc. If it really is, then we could get a substantial performance boost by using some automated techniques for inlining virtually every function call. Who knows? Maybe that would help!
Anyway, I'm not going to work on this until after the schema patch gets in.
Comment #58
chx commentedBarry, that's an interesting idea. I will experiment a bit with inling various little function.
Comment #59
moshe weitzman commentedI did a code review of our new template system and we use variable function calls very heavily in D6. FYI.
Comment #60
bjaspan commentedComment #61
antgiant commentedSubscribing
Comment #62
Crell commentedLet's continue this as part of the new system: http://drupal.org/node/225450
Comment #63
snoble commentedI am having a hard time finding a version of the original multiple database type patch that will work with with drupal 6. Has anyone maintained one? Would anyone be interested in me posting one?
Thanks,
Steven
Comment #64
freephile commentedI'd like to use a this patch in D6, but the last one in this thread fails on several hunks since it's been quite a long time since that patch was rolled. If somebody has a recent patch that would be great.
Comment #65
benjus commentedHi, I want to know if this patch works for drupal 7. Obviously, my drupal 7 crashes when I try to work with "mysql" and "postgres" in the same site. Another cuestion is: Why this problem doesn't fixed in drupal core yet? Thank's in advance.