I'm developing an e-commerce site using Ubercart and wanted to integrate the affiliate program using Affiliate2. My set up is a multisite configuration in a single Postgresql database using prefixes "site1_", "site2_" with the following tables shared using the prefix "shared_": users, authmap, role, sessions, profile_fields, profile_values. Ubercart is working correctly as are all other modules I have enabled with this config. However, when I enabled the Affiliate2 module clicked on the management tab I got the following errors:
# warning: pg_query() [function.pg-query]: Query failed: ERROR: column "u.name" must appear in the GROUP BY clause or be used in an aggregate function in /drupal/includes/database.pgsql.inc on line 139.
# user warning: query: SELECT u.uid, u.name, u.status, u.created, u.access FROM shared_users u INNER JOIN store_users_roles ur ON u.uid = ur.uid WHERE ur.rid IN (SELECT r.rid from shared_role r INNER JOIN store_permission p ON r.rid = p.rid WHERE p.perm LIKE '%act as affiliate%') GROUP BY u.uid ORDER BY u.created DESC in /drupal/sites/all/modules/uc_affiliate2/uc_affiliate2.module on line 661.When I went to my users page and clicked on a user and then the Affiliate Center tab, I got the following errors:
* warning: pg_query() [function.pg-query]: Query failed: ERROR: function from_unixtime(int_unsigned, unknown) does not exist LINE 1: SELECT unix_timestamp(from_unixtime(time, '%Y-%m-%d')) as cl... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. in /drupal/includes/database.pgsql.inc on line 139.
* user warning: query: SELECT unix_timestamp(from_unixtime(time, '%Y-%m-%d')) as click_date, count(click_id) as clicks from store_uc_affiliate2_clicks WHERE aid = 8 AND time between 1245049199 AND 1245135599 GROUP BY click_date in /drupal/sites/all/modules/uc_affiliate2/uc_affiliate2.module on line 995.
* warning: pg_query() [function.pg-query]: Query failed: ERROR: function from_unixtime(int_unsigned, unknown) does not exist LINE 1: SELECT unix_timestamp(from_unixtime(time, '%Y-%m-%d')) as cl... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. in /drupal/includes/database.pgsql.inc on line 139.
* user warning: query: SELECT unix_timestamp(from_unixtime(time, '%Y-%m-%d')) as click_date, count(click_id) as clicks from store_uc_affiliate2_clicks WHERE aid = 8 AND time between 1244530799 AND 1245135599 GROUP BY click_date in /drupal/sites/all/modules/uc_affiliate2/uc_affiliate2.module on line 1003.
* warning: pg_query() [function.pg-query]: Query failed: ERROR: function from_unixtime(int_unsigned, unknown) does not exist LINE 1: SELECT unix_timestamp(from_unixtime(time, '%Y-%m-%d')) as cl... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. in /drupal/includes/database.pgsql.inc on line 139.
* user warning: query: SELECT unix_timestamp(from_unixtime(time, '%Y-%m-%d')) as click_date, count(click_id) as clicks from store_uc_affiliate2_clicks WHERE aid = 8 AND time between 1213599599 AND 1245135599 GROUP BY click_date in /drupal/sites/all/modules/uc_affiliate2/uc_affiliate2.module on line 1011.
Does this module support Postgresql?
| Comment | File | Size | Author |
|---|---|---|---|
| #13 | uc_affiliate2-200906patch.tar_.gz | 50.15 KB | cjscullyca |
Comments
Comment #1
bojanz commentedAt the moment no, the dashboard has mysql specific queries. This will be improved for future releases.
Comment #2
cjscullyca commentedOK. I set up a MySQL box for testing and I've taken care of the first error above by modifying the query in lines 653-658 of uc_affiliate2.module:
// define the query
$sql = "SELECT u.uid, u.name, u.status, u.created, u.access
FROM {users} u
INNER JOIN {users_roles} ur ON u.uid = ur.uid
WHERE ur.rid IN (SELECT r.rid from {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%act as affiliate%')
GROUP BY u.uid, u.name, u.status, u.created, u.access";
This syntax is valid in both Postresql and MySQL.
I'll work on the others tomorrow and will post my changes once I have those solved.
Comment #3
bojanz commentedGreat, thanks.
Post your changes here and I'll have them commited right away.
Comment #4
cjscullyca commentedI've looked over the code. The main issue with Postgres is the use of the MySQL unix_timestamp function. I didn't see any other queries that looked like they would cause any issues. In order to make this database agnostic this function will have to be performed by PHP instead of the DBMS.
Looking further at the PHP it appears that the time information for events like clicks etc. are put through the PHP mktime function before being stored in the database. Am I reading that right? Are all dates being stored that way?
The reason this seems relevant to me is twofold. (A) Since the PHP mktime function converts the date/time into a unix timestamp, that conversion would mean that the result of a query like "SELECT 'time' FROM 'uc_affiliate2_clicks'" would return a unix timestamp directly since that is the result of the PHP mktime function. The time calculations could be performed directly on the stored value, thus obviating the need for the MySQL unix_timestamp function or any similar function in PHP prior to performing the calculations. (B) According to the MySQL documentation the conversion to unix timestamp and then back using the unix_timestamp and from_unixtime functions is lossy. The example on the MySQL website shows a difference of about an hour or so. Not huge and for this application may not be of concern at all.
If I am correct in the above assessment then all of the queries using the unix_timestamp function can be simplified to simple select statements using standard SQL that will work with any SQL database engine.
While awaiting a reply I'm going to run some tests along these lines and see what happens...
Comment #5
cjscullyca commentedOK. Here's what I did:
Edited uc_affiliate2.module so all queries involving the MySQL unix_timestamp function were changed from "SELECT unix_timestamp(from_unixtime(time, '%%Y-%%m-%%d')) as ..." to "SELECT time as..."
This eliminated all remaining pgsql errors.
I compared the results to those in a default MySQL installation on a test box and they matched. All affiliate pages displayed properly with no errors and the Postgres machine matched the MySQL machine using identical data.
I then copied the revised module file over to the MySQL test machine and checked the results again. They were the same as with the original file. It would be beneficial to double check the results on different sets of data to ensure that my tests were not too limited in scope.
Chris
Comment #6
cjscullyca commentedI just found another one. Partially easy to fix and partially not. I'm going to have to dig into the php documentation for the solution to this one.
Line 250 of uc_affiliate2.module has the following query:
db_query('INSERT {uc_affiliate2_product_commission} (nid, commission_structure) VALUES (%d, "%s")', $nid, serialize($commission_structure));for compatibility with both Mysql and Postgresql, the query needs to read:
db_query('INSERT INTO{uc_affiliate2_product_commission} (nid, commission_structure) VALUES (%d, "%s")', $nid, serialize($commission_structure));That change revealed another problem with this query and pgsql as seen in the resulting error message after the above change:
I ran the query directly against the database and it succeeded when I changed the quotes around the serialized string value from double quotes to single quotes. As I was reviewing the documentation for the serialize function on php.net I noticed that some people were giving example output for string values as follows:
There were also examples showing double quoted output from the serialize function.
Taking it a step further I checked to ensure the single quoted query would work on MySQL. It did. The only problem left to solve is hot to coerce a single quoted result out of the PHP serialize function. The query will definitely work in both databases if serialize($commission_structure) returns 's:2:"50";' instead of "s:2:"50";" as in the error message above.
I will keep searching around for an answer ...
Comment #7
bojanz commentedYou misunderstood what the "SELECT unix_timestamp(from_unixtime(time, '%%Y-%%m-%%d'))" query is for.
Right now, when we add a click, the current date AND time is saved as a unix timestamp to the database.
To get the number of clicks, we need to grup results by date only.
1) from_unixtime(time, '%%Y-%%m-%%d') -> this part only takes the date, discarding the time
2) the unixtimestamp then converts the date to a unixtimestamp again
Without the code above you get wrong counters. Your data set maybe didn't show it, but another certanly would.
I'm looking into how we can solve this issue.
Comment #8
bojanz commentedThe curious thing about the INSERT INTO postgres error is that the serialized string is not escaped by the DB layer for some reason.
Anyway, the php serialize function doesn't include quotes around the whole serialization (eg: a:1:{s:1:"a";s:1:"b";} ), so the only needed fix would be to change the offending part to:
db_query("INSERT INTO {uc_affiliate2_product_commission} (nid, commission_structure) VALUES (%d, '%s')"
Comment #9
bojanz commented// define the query
$sql = "SELECT u.uid, u.name, u.status, u.created, u.access
FROM {users} u
INNER JOIN {users_roles} ur ON u.uid = ur.uid
WHERE ur.rid IN (SELECT r.rid from {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%act as affiliate%')
GROUP BY u.uid, u.name, u.status, u.created, u.access";
Check if it works by reverting the GROUP BY caluse to just "GROUP BY u.uid", and changing the first line to "SELECT u.*". I'm weary of modifying the grouping because it changes how the query works, which could lead to bugs ( although it seems to be working nicely on my test case).
If it doesn't work, we'll go with the GROUP BY solution.
Bojan
P.S. Thank you for your efforts, they are appreciated.
Comment #10
bojanz commentedSeems like your unixtime changes for the dashboard work after all, we don't need to group by date, since we limit it by aid and datetime.
You don't need time at all, just use SELECT COUNT(click_id) as clicks FROM...
Also it's a good idea to GROUP BY aid to reduce the number of rows returned, less work for php.
P.S. Commited the dashboard and serialization fixes to CVS.
Comment #11
bojanz commentedBtw, we also have a datetime problem with uc_affiliate2_clicks, but the previous approach won't work there since we really need to GROUP BY date there...
EDIT: Reimplemented the clicks function, should work with postgres now. Also added the proposed u* change for the query in admin_manage. Download the CVS version and test. If any further changes are needed, make them there.
Comment #12
cjscullyca commentedDownloaded the CVS and upon hitting the manage affiliates tab I got the following errors:
Here's the relevant section from the PostgrSQL documentation:
Including both u.uid and u.name in the group by statement should never cause a problem because uid 1 will always be "John Smith" while uid 2 will always be "Mary Jones", etc. The other columns I'm not as sure about (not yet familiar enough with the module) .
Also with the CVS, logging in as a test affiliate and clicking on the affiliate center tab produced the following errors:
Comment #13
cjscullyca commentedInterestingly the CVS version eliminated the most recent error I found so I combined the CVS change to the product commission structure functions with the changes I'd made as outlined above. With this combination I am no longer getting any SQL errors.
The attached file includes the current release version of the module renamed uc_affiliate2.original, the current CVS version renamed uc_affiliate2.CVS and the above changes are incorporated in the enclosed uc_affiliate2.module file. I've updated the changelog.txt file enclosed with the changes I've made which are also described above.
As you've noted already some of my changes may introduce unanticipated logic errors. I am going to do several days of testing with different data sets to try to determine if this is the case or not. If we're lucky the attached file works well. At the very least it clears the hurdle of PostreSQL errors and allows further testing to determine whether or not additional changes are needed.
Comment #14
cjscullyca commentedI see. One question is how critical is the time for the functionality? Since the reports are breaking clicks down by day, week, etc, does the time actually need to be stored?
Comment #15
bojanz commentedOkay, use the latest CVS version. I commited the GROUP BY change. As for the dashboard error, clear your (menu) cache and it should go away.
I think ( hope) we're done :)
Comment #16
cjscullyca commentedLooks good so far. No SQL errors. Thanks!
Comment #17
bojanz commentedI am marking this as fixed. If you find anything else, you know what to do :)
Bojan