Support for DB2 databases?

Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sat, 2005-11-05 03:56

Hmmm... I just noticed that by specifying cursor mode 0 (SQL_CUR_USE_IF_NEEDED), it seems to have gone back to the DB2 CLI/ODBC driver.

Interesting, but not a problem. Probably good news.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sat, 2005-11-05 05:35

Well, the one-line fix might be to change adodb-db2.php.inc as follows:

Quote:
function ADODB_DB2()
{
// if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_ODBC;
if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_IF_NEEDED;
$this->ADODB_odbc();
}

This seems to get past the immediate problem. Unfortunately there are more after that. :-(

Does that change to adodb-db2.php.inc sound reasonable to you folks?

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Sat, 2005-11-05 13:15

sure, we can hack our adodb version.

but have you already read the explanation of the whole problem?
from
lib/adodb/drivers/adodb-db2.inc.php

Quote:
DB2 data driver. Requires ODBC.

From phpdb list:

Hi Andrew,

thanks a lot for your help. Today we discovered what
our real problem was:

After "playing" a little bit with the php-scripts that try
to connect to the IBM DB2, we set the optional parameter
Cursortype when calling odbc_pconnect(....).

And the exciting thing: When we set the cursor type
to SQL_CUR_USE_ODBC Cursor Type, then
the whole query speed up from 1 till 10 seconds
to 0.2 till 0.3 seconds for 100 records. Amazing!!!

Therfore, PHP is just almost fast as calling the DB2
from Servlets using JDBC (don't take too much care
about the speed at whole: the database was on a
completely other location, so the whole connection
was made over a slow network connection).

I hope this helps when other encounter the same
problem when trying to connect to DB2 from
PHP.

Kind regards,
Christian Szardenings

2 Oct 2001
Mark Newnham has discovered that the SQL_CUR_USE_ODBC is not supported by
IBM's DB2 ODBC driver, so this must be a 3rd party ODBC driver.

From the IBM CLI Reference:

SQL_ATTR_ODBC_CURSORS (DB2 CLI v5)
This connection attribute is defined by ODBC, but is not supported by DB2
CLI. Any attempt to set or get this attribute will result in an SQLSTATE of
HYC00 (Driver not capable).

A 32-bit option specifying how the Driver Manager uses the ODBC cursor
library.

So I guess this means the message [above] was related to using a 3rd party
odbc driver.

Setting SQL_CUR_USE_ODBC
========================
To set SQL_CUR_USE_ODBC for drivers that require it, do this:

$db = NewADOConnection('db2');
$db->curMode = SQL_CUR_USE_ODBC;
$db->Connect($dsn, $userid, $pwd);

USING CLI INTERFACE
===================

I have had reports that the $host and $database params have to be reversed in
Connect() when using the CLI interface. From Halmai Csongor csongor.halmai#nexum.hu:

> The symptom is that if I change the database engine from postgres or any other to DB2 then the following
> connection command becomes wrong despite being described this version to be correct in the docs.
>
> $connection_object->Connect( $DATABASE_HOST, $DATABASE_AUTH_USER_NAME, $DATABASE_AUTH_PASSWORD, $DATABASE_NAME )
>
> In case of DB2 I had to swap the first and last arguments in order to connect properly.

*/

and the constructor of the class:

	function ADODB_DB2()
	{
		if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_ODBC;
		$this->ADODB_odbc();
	}

so they're using the cursor mode on windows because it's an order of magnitude faster.
but it doesn't work for you.
so you could change the constructor, remove the if line and just set
$this->curmode = false; instead of the if line.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sat, 2005-11-05 16:21

Yes I did see the comments. But as you said, it doesn't work for me, and it's 10 times faster... than what? It doesn't discuss SQL_CUR_USE_IF_NEEDED.

I suspect the performance issue is moot anyway... we're talking about a single row result set, I suspect the case described in the file comments was considerably more than that.

Yes, the setting of $this->curmode in the constructor is the exact line I'm talking about. So there are two ways to make this change:

1) Set it to SQL_CUR_USE_IF_NEEDED regardless of platform
2) Set it to SQL_CUR_USE_IF_NEEDED only on Windows

For now, I'm going to set it to SQL_CUR_USE_IF_NEEDED only on Windows, unless you have any violent objections about it. :-)

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Sun, 2005-11-06 01:26

you're the only db2 user for now, so it's needed for 100% of all g2/db2 users :). so i'm +1 for that change.

please post the edited constructor and i'll commit it to cvs.

if that works, please get back to check if you need to change anything else.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sun, 2005-11-06 02:46

The new constructor I'm using is:

Quote:
function ADODB_DB2()
{
if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_IF_NEEDED;
$this->ADODB_odbc();
}

Until such time as I know if it's required for other platforms, I'd rather leave it as Windows-only for now.

The next problem I hit looks like a BIT AND issue.

Quote:
Error
An error has occurred.

Back to the Gallery

Error Detail +
Error (ERROR_STORAGE_FAILURE) :
in modules\core\classes\GalleryStorage\DatabaseStorage.class at line 306 (GalleryStatus::error)
in modules\core\classes\GalleryStorage.class at line 250 (DatabaseStorage::search)
in modules\core\classes\Gallery.class at line 223 (GalleryStorage::search)
in modules\core\classes\helpers\GalleryPermissionHelper_simple.class at line 89 (Gallery::search)
in modules\core\classes\GalleryCoreApi.class at line 413 (GalleryPermissionHelper_simple::fetchAccessListIds)
in modules\core\classes\helpers\GalleryUserHelper_simple.class at line 92 (GalleryCoreApi::fetchAccessListIds)
in modules\core\classes\helpers\GalleryUserHelper_simple.class at line 55 (GalleryUserHelper_simple::hasItemPermission)
in modules\core\classes\GalleryCoreApi.class at line 448 (GalleryUserHelper_simple::assertHasItemPermission)
in modules\core\ShowItem.inc at line 76 (GalleryCoreApi::assertHasItemPermission)
in modules\core\classes\GalleryTheme.class at line 689 (ShowItemView::loadTemplate)
in modules\core\classes\GalleryView.class at line 312 (GalleryTheme::loadTemplate)
in main.php at line 335 (GalleryView::doLoadTemplate)
in main.php at line 88
in main.php at line 81
Debug Output
getParameter permissions.file for core plugin file_exists(C:\g2data\cache\module/core/0/0/0.inc) file_exists(C:\g2data\cache\module/_all/0/0/GalleryFactoryHelper_loadRegistry.inc) Unimplemented: GalleryUrlGenerator file_exists(C:\g2data\sessions\bb4ec10f520abafdab91a48114a98617) file_exists(C:\g2data\sessions\bb4ec10f520abafdab91a48114a98617) getParameter cookie.domain for core plugin getParameter cookie.path for core plugin getParameter default.language for core plugin controller , view Loading plugin core Class not defined, trying to include it. file_exists(C:\My Server\gallery2\modules\core\classes\helpers/../../../../modules/core/module.inc) core plugin successfully instantiated file_exists(C:\g2data\versions.dat) file(C:\g2data\versions.dat, ) getParameter id.anonymousUser for core plugin [1131170007] can't guarantee 5 -- extending! file_exists(C:\g2data\cache\entity/0/0/5.inc) Loading plugin core core plugin successfully instantiated Check the version of the core plugin file_exists(C:\g2data\cache\module/_all/0/0/GalleryPluginHelper_fetchPluginStatus.inc) The version of the core plugin is ok file_exists(C:\My Server\gallery2\modules\core\classes\helpers/../../../../modules/core/ShowItem.inc) getParameter id.rootAlbum for core plugin file_exists(C:\g2data\cache\entity/0/0/7.inc) getParameter default.theme for core plugin Loading plugin matrix Class not defined, trying to include it. file_exists(C:\My Server\gallery2\modules\core\classes\helpers/../../../../themes/matrix/theme.inc) matrix plugin successfully instantiated Check the version of the matrix plugin file_exists(C:\g2data\cache\theme/_all/0/0/GalleryPluginHelper_fetchPluginStatus.inc) The version of the matrix plugin is ok file_exists(C:\g2data\cache\theme/matrix/0/0/0.inc) file_exists(C:\g2data\cache\theme/matrix/0/0/7.inc) getParameter id.anonymousUser for core plugin getParameter id.adminGroup for core plugin For odbc Connect(), gallery2 is not used. Place dsn in 1st parameter.

--------------------------------------------------------------------------------
(db2): SELECT g2_UserGroupMap.g_userId FROM g2_UserGroupMap WHERE g2_UserGroupMap.g_userId = ? AND g2_UserGroupMap.g_groupId = ? FETCH FIRST 1 ROWS ONLY [ (0=>'5') (1=>'3') ]
--------------------------------------------------------------------------------
getParameter id.rootAlbum for core plugin
--------------------------------------------------------------------------------
(db2): SELECT g2_Group.g_id, g2_Group.g_groupName FROM g2_UserGroupMap, g2_Group WHERE g2_Group.g_id = g2_UserGroupMap.g_groupId AND g2_UserGroupMap.g_userId = ? ORDER BY g2_Group.g_groupName [ (0=>'5') ]
--------------------------------------------------------------------------------

Warning: odbc_prepare() [function.odbc-prepare]: SQL error: [IBM][CLI Driver] CLI0005W Option value changed. SQLSTATE=01S02, SQL state 01S02 in SQLPrepare in C:\My Server\gallery2\lib\adodb\drivers\adodb-odbc.inc.php on line 502

Warning: odbc_execute() [function.odbc-execute]: SQL error: [IBM][CLI Driver] CLI0005W Option value changed. SQLSTATE=01S02, SQL state 01S02 in SQLExecute in C:\My Server\gallery2\lib\adodb\drivers\adodb-odbc.inc.php on line 510

--------------------------------------------------------------------------------
(db2): SELECT g2_PermissionSetMap.g_module, g2_PermissionSetMap.g_permission, g2_PermissionSetMap.g_description, g2_PermissionSetMap.g_bits, g2_PermissionSetMap.g_flags FROM g2_PermissionSetMap
--------------------------------------------------------------------------------
db2 error: [0: ] in EXECUTE(" SELECT DISTINCT g2_AccessMap.g_accessListId FROM g2_AccessMap WHERE (g2_AccessMap.g_userId = ? OR g2_AccessMap.g_groupId IN (?)) AND g2_AccessMap.g_permission & B? = ? ")
--------------------------------------------------------------------------------
(db2): SELECT DISTINCT g2_AccessMap.g_accessListId FROM g2_AccessMap WHERE (g2_AccessMap.g_userId = ? OR g2_AccessMap.g_groupId IN (?)) AND g2_AccessMap.g_permission & B? = ? [ (0=>'5') (1=>'4') (2=>'00000000000000000000000000000001') (3=>'00000000000000000000000000000001') ]
--------------------------------------------------------------------------------

Warning: odbc_prepare() [function.odbc-prepare]: SQL error: [IBM][CLI Driver][DB2/NT] SQL0007N The character "&" following "cessMap.g_permission" is not valid. SQLSTATE=42601 , SQL state 42601 in SQLPrepare in C:\My Server\gallery2\lib\adodb\drivers\adodb-odbc.inc.php on line 502
0:

ADOConnection._Execute(
SELECT DISTINCT
g2_AccessMap.g_accessListId
FROM
g2_AccessMap
WHERE
(g2_AccessMap.g_u..., Array[4])
% line 851, file: adodb.inc.php

ADOConnection.Execute(
SELECT DISTINCT
g2_AccessMap.g_accessListId
FROM
g2_AccessMap
WHERE
(g2_AccessMap.g_u..., Array[4])
% line 298, file: DatabaseStorage.class

DatabaseStorage.search(
SELECT DISTINCT
[GalleryAccessMap::accessListId]
FROM
[GalleryAccessMap]
WHERE
([Gall..., Array[4], Array[0]) %
line 250, file: GalleryStorage.class

GalleryStorage.search(
SELECT DISTINCT
[GalleryAccessMap::accessListId]
FROM
[GalleryAccessMap]
WHERE
([Gall..., Array[4], Array[0]) %
line 223, file: Gallery.class

Gallery.search(
SELECT DISTINCT
[GalleryAccessMap::accessListId]
FROM
[GalleryAccessMap]
WHERE
([Gall..., Array[4]) % line
89, file: GalleryPermissionHelper_simple.class

getParameter default.theme for core plugin Loading plugin matrix matrix plugin successfully instantiated Check the version of the matrix plugin The version of the matrix plugin is ok Loading plugin core core plugin successfully instantiated Check the version of the core plugin The version of the core plugin is ok file_exists(C:\My Server\gallery2\modules\core\classes\helpers/../../../../modules/core/ErrorPage.inc) getParameter id.adminGroup for core plugin realpath(C:\My Server\gallery2\modules\core\classes/../../../) file_exists(C:\My Server\gallery2\modules\core\classes/../../../lib/javascript/local/BlockToggle.js) file_exists(C:\g2data\smarty\templates_c\%%4276933715) is_dir(C:\g2data\smarty\templates_c\%%4276933715) file_exists(C:\g2data\smarty\templates_c\%%4276933715) is_writeable(C:\g2data\smarty\templates_c\%%4276933715) file_exists(C:\g2data\smarty\templates_c\%%4276933715/v_8) file_exists(C:\My Server\gallery2\modules\core\classes/../../../themes/matrix/local/theme.css)

I think I've found the statement code in "modules/core/classes/helpers/GalleryPermissionHelper_simple.class":

Quote:
list ($ret, $bitAndPermission) =
$storage->getFunctionSql('BITAND', array('[GalleryAccessMap::permission]', '?'));
if ($ret->isError()) {
return array($ret->wrap(__FILE__, __LINE__), null);
}

$query = sprintf('
SELECT DISTINCT
[GalleryAccessMap::accessListId]
FROM
[GalleryAccessMap]
WHERE
([GalleryAccessMap::userId] = ?
OR
[GalleryAccessMap::groupId] IN (%s))
AND
' . $bitAndPermission . ' = ?
', $groupIdMarkers);

$storage =& $gallery->getStorage();

$data = array($userId);
$data = array_merge($data, $groupIds);
$data[] = $storage->convertIntToBits($bits);
$data[] = $storage->convertIntToBits($bits);

and in "modules/core/classes/GalleryStorage/DatabaseStorage/Db2DatabaseStorage.class":

Quote:
function getFunctionSql($functionName, $args) {
switch($functionName) {
...
case 'BITAND':
/* Cast any input values as the 'bit' type */
$args = str_replace('?', 'B?', $args);
$sql = $args[0] . ' & ' . $args[1];
break;

Obviously the "&" isn't valid in DB2, so I'll need to implement some sort of BITWISE AND functionality, not a problem. But I'm not clear on exactly what it's trying to do overall. Specifically, I don't understand:

1) The "B". Is that supposed to be a way of casting the value of the subsequent parameter marker?

2) Why the same value of bits is pushed onto the $data[] array twice? We're comparing the 'B-casted' bit string to the 'un-B-casted' verion of itself? Why?

The dump of the $data[] array is:

Quote:
array(4) { [0]=> string(1) "5" [1]=> int(4) [2]=> string(32) "00000000000000000000000000000001" [3]=> string(32) "00000000000000000000000000000001" }

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Sun, 2005-11-06 03:00

wel, that B is specific for postgres.

you need to find how to create a bit and in DB2 which will be totally different.
e.g. see
http://groups.google.com/group/comp.databases.ibm-db2/browse_thread/thread/14c4822b817b109c/be742125ac1d0f71?lnk=st&q=db2+bit+operations&rnum=1&hl=en#be742125ac1d0f71

but what about the BIT_OR function in your Db2 class? did you write it? why not also write such a bit_and function?

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sun, 2005-11-06 03:40

Yes, I wrote that BIT_OR function (actually, I shamelessly 'borrowed' it from a DB2 developerWorks article) because there was already something similar there. There was nothing similar to a BIT_AND. I can easily create one though. Like I said, that's not a problem.

I've found some info about the "B"... it's a funky way of casting a char string to PostgreSQL's BIT type. But I'm still confused as to why we're comparing the 'bit-casted' bit string to the original version of itself. What's the purpose of doing that? DB2 does not have a BIT datatype, so I need to understand what we're doing in order to come up with a DB2 equivalent.

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Sun, 2005-11-06 05:22

we are ANDing 2 operands...

an example:
table AccessMap
g_accessListId g_userId g_groupId g_permission
508 8 0 3

so this ACL has permissionBits = 408
our permission system is simple:
we have at the moment 32 bits for all permissions, each bit is for another permission.
see table permissionSetMap for the existing permissions
permission bit 1 is for core.view
permission bit 2 is for core.viewResizes
so an ACL permission entry with value 3 is 1 + 2 = core.view + core.viewResizes permissions.

it's the same concept as with unix filesystem permissions (chmod 755 etc.), just that we have not just read + write + execute = 7, we have a lot more permissions (and soon even more).

so, the bit AND:
we have to check if userId = 8 has permission core.viewResizes,
core.viewResizes = 2
our example ACL has permission 3
to check, we AND core.viewResizes with the ACL permission bits, we AND 2 with 3, the result of the AND is 2.
the result of the AND operation of a specific permission as core.viewResize and the ACL permission entry will always be either 0 or again the specific permission.
that's the property of our permission bits.
2 in binary format is
00000000000000000000000000000010
1 in binary format is
00000000000000000000000000000001
3 in binary format is
00000000000000000000000000000011

the AND operation of 2 operands 2 and 3 is
00000000000000000000000000000010 // 2
00000000000000000000000000000011 // 3
--------------------------------
00000000000000000000000000000010 // keep a 1 where both operations have a 1, else 0

which is again 2

this should explain why we need it and what it should do.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sun, 2005-11-06 05:51

I already understand what a bitwise AND is... what I don't understand is: Why is the same series of bits being pushed onto the $data[] array twice? What is the point of comparing the 'B-casted' bit string to the 'un-B-casted' version of itself?

Quote:
... & B? = ? [ (0=>'5') (1=>'4') (2=>'00000000000000000000000000000001') (3=>'00000000000000000000000000000001') ]

From what I see in the code, this will always be the case:

Quote:
$data = array($userId);
$data = array_merge($data, $groupIds);
$data[] = $storage->convertIntToBits($bits);
$data[] = $storage->convertIntToBits($bits);

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Sun, 2005-11-06 12:10

Hi.
I only just stumbled upon this topic. However, I too would like 'register' interest for DB2 support in Gallery. From what I have seen on your website it looks very nice.
Also If you would like access to a DB2 server, I can help with that, However, my DB2 programming skills are quite non-existant. I mainly work with DB2 itself, not much with the actual front-end stuff.

Anyway, if I can help let me know. otherwise, I will look forward to functioning DB2 support in Gallery!

Thank you!

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Sun, 2005-11-06 14:07

Larry Menard, I tried to explain it, I'll try it with other words.... :)
this is one of our functions that use the BITAND:

    function fetchAccessListIds($permission, $userId) {
	global $gallery;

	$cacheKey = "GalleryPermissionHelper::fetchAccessListIds($permission, $userId)";
	if (!GalleryDataCache::containsKey($cacheKey)) {
	    list ($ret, $groupIds) = GalleryCoreApi::fetchGroupsForUser($userId);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    $groupIds = array_keys($groupIds);
	    $groupIdMarkers = GalleryUtilities::makeMarkers($groupIds);

	    list ($ret, $bits) = GalleryCoreApi::convertPermissionIdsToBits($permission);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    $storage =& $gallery->getStorage();
	    list ($ret, $bitAndPermission) =
		$storage->getFunctionSql('BITAND', array('[GalleryAccessMap::permission]', '?'));
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    $query = sprintf('
	    SELECT DISTINCT
	      [GalleryAccessMap::accessListId]
	    FROM
	      [GalleryAccessMap]
	    WHERE
	      ([GalleryAccessMap::userId] = ?
	      OR
	      [GalleryAccessMap::groupId] IN (%s))
	      AND
	      ' . $bitAndPermission . ' = ?
	    ', $groupIdMarkers);

	    $storage =& $gallery->getStorage();

	    $data = array($userId);
	    $data = array_merge($data, $groupIds);
	    $data[] = $storage->convertIntToBits($bits);
	    $data[] = $storage->convertIntToBits($bits);

	    list ($ret, $results) = $gallery->search($query, $data);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    $aclIds = array();
	    while ($result = $results->nextResult()) {
		$aclIds[] = (int)$result[0];
	    }
	    GalleryDataCache::put($cacheKey, $aclIds);
	} else {
	    $aclIds = GalleryDataCache::get($cacheKey);
	}

	return array(GalleryStatus::success(), $aclIds);
    }

let's say we call it with
fetchAccessListIds('core.viewResizes', '8'),
so we want to know to which access list ids the user has permission to viewResizes (and access list ids are mapped one to many to entities in G2). so with another simple query we'll get the complete list of entityIds for which the user has core.viewResize permissions.

so first we get the id for core.viewResizes from PermissionSetMap which is 2.
we also get all the groupIds the user is a member of. let's say he's a member of group everybody and registered users, 2 and 4.
so we can build our query:

	    SELECT DISTINCT
	      g2_GalleryAccessMap.g_accessListId
	    FROM
	      g2_GalleryAccessMap
	    WHERE
	      (g2_GalleryAccessMap.g_userId = 8
	      OR
	      g2_GalleryAccessMap.g_groupId IN (2, 4))
	      AND
	      g2_GalleryAccessMap.g_permission & 2 = 2;

I guess you understand everything but the last line. We could also write it as:
BITAND(g2_GalleryAccessMap.g_permission, 2) = 2; , to note that we first execute the AND operation and then compare the result with 2.
The two 2's in the last line will always be the same number, a power of 2 (well, we also have combinations of permissions, which are the sum of other permissions).
When you AND an arbitrary number with a power of 2 then the result is always 0 or again the same power of 2.

examples:

nr in ( ) is decimal, other numbers are in binary format
g2_GalleryAccessMap.g_permission          input         result
( 3) 00000011                     ( 1) 00000001  ( 1) 00000001  -> user has permission 
( 3) 00000011                     ( 4) 00000100  ( 0) 00000000  -> user has no permission
( 3) 00000011                     (16) 00010000  ( 0) 00000000  -> user has no permission
(81) 01010001                     (64) 01000000  (64) 01000000  -> user has permission

as you see, the result of the AND operation is always equal to the one of the AND operators or 0. if it's 0, we don't have the permission for this ACL id.

this should already be enough to explain our system. just for completenesss, i'll add a more complicated case too, which is when the permission we're checking for is a permission set of other permissions. e.g. core.viewAll is the sum of core.view (1 = 0001) + core.viewResizes (2 = 0010) and core.viewSource (4 = 0100) so core.viewAll is 7 = 0111.
examples:

g2_GalleryAccessMap.g_permission          input         result
(  3) 00000011                     ( 7) 00000111  ( 3) 00000011  -> result != input, we only have permission to viewResizes and view, but not viewSource -> not what we're looking for
(  7) 00000111                     ( 7) 00000111  ( 7) 00000111  -> result == input, we have core.viewAll for this ACL id
( 38) 00100110                     ( 7) 00000111  ( 6) 00000110  -> result != input, we don't have core.view -> not what we're looking for
(247) 11110111                     ( 7) 00000111  ( 7) 00000111  -> result == input, we have core.viewAll for this ACL id

as you see, we don't get 0 or the input sequence as result, the result of the AND operation is equal to the input or equal to the sum of 1 or more members of the permission set or 0. but we're only interested if it's again the input or something else.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Sun, 2005-11-06 14:16

Hi, Sphericus.

Thanks for chiming in, and welcome aboard!

I'm not a hardcore developer either, but am somehow managing to muddle through this porting exercise with a lot of help from the actual G2 developers.

Right now the DB2 support does not exist in the released versions of G2, and even in the nightly development builds it is not complete. The G2 Installer in the nightly development build should work, but the 'using' of it is not working yet.

Out of curiosity, what platform are you on? Windows? Unix? Linux?

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Sun, 2005-11-06 15:00

Thanks!

My DB2 is mainly on Linux, Although I used to have a few on Windows as well. But recently managed to get them all over to Linux. I also hope to have a few AIX ones soon though. But that's currently only in the planning stage at this time.

My webserving is split across Linux and Windows. However, with Websphere on Linux, and Lotus Domino on Windows, we have plans to migrate Domino away, but can't decide on migrating to Linux, AIX, or OS/400 yet. (Too many choices :-p)

I also would like to commend the G2 developers for their assistance and open minds about DB2 support. As only a few open source projects currently have DB2 support, and many of the ones that do not, IMO are very entrenched in the "MySQL is the only way" mindset, while I agree that MySQL does have its uses, and some of the things it does are all well and good. I find that there is NO DBMS solution that is the absolute greatest at doing everything. As I am sure everyone can agree, that although the DB vendors try very hard, there are still areas where each DBMS pales in comparison compared to others. Combined with the impracticality of running a seperate DBMS solution for every single app, I think multi-DBMS support is something more developers need to think about, both open and closed source.

If you need help with anything let me know. I have access to both new and archive DB2 code, If for some reason you need old code, I can help with old code down until DB2 v5.x. Also if the G2 devs need access to a DB2 box, I should be able to help. However, I am in Australia, and I only have a 512/512kbps link which is fairly generous for Australia :-(.

So if you need anything just give us a yell. We would be happy to help wherever possible.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Tue, 2005-11-08 01:20

Wow, this is astronomically more time-consuming than I expected it would be.

I've just spent the last two days implementing the BIT AND support (DB2 doesn't have either a true BIT datatype or any kind of boolean AND/OR functionality... they rely on you to implement your own using User-Defined Functions.

At any rate, the BIT_AND is now implemented. Now I have to do the same for the BIT_OR. (The BIT_OR support from the PostgreSQL files is useless in DB2.)

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Tue, 2005-11-08 01:25
Quote:
At any rate, the BIT_AND is now implemented. Now I have to do the same for the BIT_OR. (The BIT_OR support from the PostgreSQL files is useless in DB2.)

cool :)

yeah. it's a lot of work mostly because db2 isn't used that often in the php world. else we wouldn't have had the adodb / ODBC issues etc.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Tue, 2005-11-08 22:47

I've hit another snag... It appears that DB2 doesn't support concatenation in a WHERE ... LIKE ... clause.

The query in question is:

Quote:
SELECT iam0.g_itemId,
COUNT(iam1.g_itemId)
FROM g2_ItemAttributesMap AS iam0, g2_ItemAttributesMap AS iam1, g2_AccessSubscriberMap
WHERE iam0.g_itemId IN (7)
AND iam1.g_parentSequence LIKE iam0.g_parentSequence || iam0.g_itemId || '/%'
AND iam1.g_itemId = g2_AccessSubscriberMap.g_itemId
AND g2_AccessSubscriberMap.g_accessListId IN (8,9)
GROUP BY iam0.g_itemId;

Which results in:

Quote:
SQL0440N No authorized routine named "||" of type "FUNCTION" having
compatible arguments was found. SQLSTATE=42884

I'm seeking confirmation that this is not supported by DB2. Assuming it is not, can you think of a way around this?

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Tue, 2005-11-08 23:00

BTW, it seems there's only one 0-byte long entry in the g2_ItemAttributesMap.g_parentSequence column. (I believe this is not a NULL, it's a 0-byte string.)

Quote:
C:\My Server\gallery2>db2 "select g_parentSequence from g2_ItemAttributesMap"

G_PARENTSEQUENCE

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
---------------

1 record(s) selected.

C:\My Server\gallery2>db2 select length(g_parentSequence) from g2_ItemAttributes
Map

1
-----------
0

1 record(s) selected.

Is that to be expected?

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Tue, 2005-11-08 23:11

well, i'd be very surprised if DB2 didn't support concatenation.
PG and oracle do it with operand1 || operand2, mysql does it with CONCAT(op1, op2)

btw: after an initial install, there's only a single entry in g2_ItemAttributesMap and it's for the root album, id 7.
it's the only item with an empty, 0 byte long parent sequence. that's perfectly fine.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Wed, 2005-11-09 01:58

DB2 does support concatenation ("||" and "CONCAT(..., ...)", but apparently its use in a "WHERE ... LIKE ..." clause is limited.

The answer I got in comp.databases.ibm-db2:

Quote:
See:
http://publib.boulder.ibm.com/infocenter/db2help/topic/com.ibm.db2.udb.doc/admin/r0000751.htm

You can't do this. The <pattern> argument of a LIKE operator is pretty
restrictive -- it must be a host variable, a constant, a special register, a
scalar function result, or a concatenation of any of these.

In your case, you're using two column values (not allowed) and a string
constant (allowed).

So we need to either find a portable way to do it, or implement a DB2-specific override here.

(I probably shouldn't say this, but... since undertaking this project, I'm now beginning to develop more of an appreciation for the criticism I've seen reported about DB2's shortcomings.)

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Wed, 2005-11-09 08:32

hmm, arg!
the first idea that comes to my mind is yet another user defined function in DB2.
function CONCATLIKE would accept 3 operands, and do the CONCAT and LIKE in multiple steps and return true or false.

AND iam1.g_parentSequence LIKE iam0.g_parentSequence || iam0.g_itemId || '/%'

would be replaced with
AND CONCATLIKE(iam1.g_parentSequence, iam0.g_parentSequence, iam0.g_itemId)

and this function would do the magic. no idea how, but maybe a DB2 guy can answer this.
and if it's not possible, maybe a DB2 guy can explain how to rewrite the whole query for DB2.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Wed, 2005-11-09 13:21

UDFs don't work in WHERE predicates either... only in SELECT clauses.

A further response to my newsgroup posting added the following:

Quote:
> Thanks, guys. I had checked the doc for CONCAT , but not LIKE.
>
> (FYI Bob, the datatype of 'iam0.g_itemId' is INTEGER.)
>
That is why the error message about concatenation is appearing; it is
not defined on integers, only character types. You would later, after
using something like char(iam0.g_itemId) in the concatenation, find the
limitation on LIKE.

I'm going to think about this for a while... will let you know if/when I think of anything.

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Wed, 2005-11-09 13:30

so concact(concact(iam0.g_parentSequence, char(iam0.g_itemId)), '/%') would work, but you can't use it in a LIKE clause. and you can't use a UDF in a WHERE clause.

but didn't you already create a UDF for BIT_OR? and BIT_OR is used in a WHERE clause too...

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Wed, 2005-11-09 14:14

Sorry, you're right. (I only got about 2 hours sleep last night, not thinking very well at the moment.)

Let me chew on the SQL for a while, but I will need your input as to how to implement the SQL difference. For example, should we implement another CASE in getFunctionSql() in Db2DatabaseStorage.class?

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Wed, 2005-11-09 14:28
Quote:
For example, should we implement another CASE in getFunctionSql() in Db2DatabaseStorage.class?

yes, do that for your Db2DbStorage class and also change the modules/core/classes/helper/ class that needs this special concat.

if everything works, we can change the other db abstraction classes as well (mysql, pg, oracle), that's not your job :)

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Wed, 2005-11-09 22:25

Hmm... it's not going to be as easy as that.

The problem is not the concatenation, and it's not the datatype of the columns involved. The problem is simply that the fact that column names cannot be specified in a LIKE predicate.

The only things that can be contained in the LIKE predicate are:

- A constant
- A special register
- A host variable
- A scalar function whose operands are any of the above
- An expression concatenating any of the above

It appears to me that the only way to do this in DB2 is to split this query into two separate queries:

- the first one putting the values of iam0.g_parentSequence and iam0.g_itemId into host variables,
- the second one doing the actual "SELECT iam0.g_itemId FROM g2_ItemAttributesMap AS iam0, g2_ItemAttributesMap AS iam1 WHERE iam1.g_parentSequence LIKE hostvar || '/%'".

Your thoughts?

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Thu, 2005-11-10 00:56

as i said, i don't know how since i didn't spend time thingking about it, but i guess a UDF could emulate a LIKE CONCACT statement / implement it somehow, maybe without the normal LIKE operator.
can you ask your DB2 contacts what they'd do and what they think about the feasability of such a UDF? show them the above query that uses the like concat and ask them how this could be done in DB2, e.g. if the whole LIKE CONCAT line or just the LIKE could be replaced with a UDF that does the trick.

splitting the query into 2 queries doesn't sound very good since we'd have to add more logic into the helper class and maybe also into the db abstraction layer just 'cause of DB2.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Thu, 2005-11-10 02:38

That's about what I thought you'd say.

I've been trying to come up with a UDF solution, but it keeps complaining about it being an invalid LIKE predicate.

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Thu, 2005-11-10 02:44

have you already consulted people that code DB2 SQL / UDF professionally? maybe in DB2 usergroups, mailinglists, forums... or something like oracle's metanet for DB2?

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Thu, 2005-11-10 02:51

I already told you above what DB2 newsgroup I'm working with.

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Thu, 2005-11-10 03:00

i'm coming from the oracle world, PL / SQL, and you can achieve pretty nice features in oracle (plus the built in stuff is also very powerful).
and there was almost always a solution or a good answer when asking / searching in oracle's metanet.

before considering a not so elegant change in G2 just 'cause of DB2, i'd rather be sure that it's really impossible to find / program a UDF or another solution in DB2 to resolve this issue. therefore i hope there's something like oracle's metanet for DB2, where experts meet and also official DB2 engineers answer.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Thu, 2005-11-10 03:08

The DB2 support in comp.databases.ibm-db2 is very good. That's where I'm getting the DB2 help I've got so far.

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Thu, 2005-11-10 03:17

so you asked there for a solution / alternative / UDF to solve this select statement in a single query.
i suggest we wait a few days, maybe someone has an idea. maybe you can reformulate the question too which might inspire someone to a solution.

you could probably even emulate the LIKE statement in a UDF with a character by character operation (i don't know how powerful DB2's UDF are, but i think that should be possible if nothing else helps).

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 00:07

I have some bad news, and some good news.

First the bad news.

It has been made very clear to me in the newsgroup that:

- column names are not allowed in LIKE predicates

- UDFs written in SQL/PL (or PL/SQL) are not allowed in LIKE predicates
(this particular little gem is undocumented :-( )

Now the hopefully good news.

How about if I replace:

Quote:
WHERE iam1.g_parentSequence LIKE iam0.g_parentSequence || iam0.g_itemId || '/%'

with:

Quote:
WHERE SUBSTR(iam1.g_parentSequence, 1, LENGTH(iam0.g_parentSequence) + 5) = iam0.g_parentSequence || CAST(iam0.g_itemId AS CHAR(4)) || '/%'

?

I haven't tried it in the actual G2 code yet, but as a standalone statement in my simplified testcase it seems to work. ;-)

Quote:
SELECT iam0.g_itemId, COUNT(iam1.g_itemId)
FROM g2_ItemAttributesMap AS iam0,
g2_ItemAttributesMap AS iam1,
g2_AccessSubscriberMap
WHERE iam0.g_itemId IN (7)
AND substr(iam1.g_parentSequence,
1,
length(iam0.g_parentSequence) + 5) =
iam0.g_parentSequence ||
cast(iam0.g_itemId as char(4)) ||
'/%'
AND iam1.g_itemId = g2_AccessSubscriberMap.g_itemId
AND g2_AccessSubscriberMap.g_accessListId IN (8,9)
GROUP BY iam0.g_itemId

G_ITEMID 2
----------- -----------

0 record(s) selected.

And a much less attractive possibility: In order to keep the LIKE predicate I could make the UDF 'external' by writing it in Java, but that could be messy because:

- that would mean either shipping a binary ".class" library file and/or building it from source as part of the G2 build step

- during the install, the ".class" library file would have to be copied to a directory in which the DB2 runtime will be able to find it (like '.../sqllib/function').

And I haven't done a quick prototype of that, so I don't even know for sure that it would work.

Could you please review my suggested change above, and let me know if you have any concerns about it?

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Fri, 2005-11-11 00:41

I have been following the newsgroup as well, and was wondering if you were indeed able to download the mtk?
If you haven't I can sent it to you, so that you can try the UDF LIKE that they say should work for you.

All the best

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Fri, 2005-11-11 00:54

phew, a solution :)
your suggestion:

WHERE iam1.g_parentSequence LIKE iam0.g_parentSequence || iam0.g_itemId || '/%'
with
WHERE SUBSTR(iam1.g_parentSequence, 1, LENGTH(iam0.g_parentSequence) + 5) = iam0.g_parentSequence || CAST(iam0.g_itemId AS CHAR(4)) || '/%'

1. are you sure strings start at 1 and not at 0 in DB2 / substr?
2. CAST(iam0.g_itemId AS CHAR(4)) ? 4 chars only? 4 chars = max 9999 items. itemId can be much larger what's the limit of TYPE integer in DB2? do you have to specify as CHAR(4) ? can't you just use CAST(iam0.g_itemId as CHAR) ?
3. this explains your +5, which i don't like. exactly because itemId isn't limited to 4 chars in the real world.
4. we can leave away the /% since the % has only a meaning in LIKE statements
5. - iam1.g_parentSequence could be shorther than LENGTH(iam0.g_parentSequence) + LENGTH(CHAR(iam0.g_itemId) + 1), do we then get a DB2 error, because we tried to get a substring which is longer than the original?

so here's what i think would be better, if it works:

WHERE LENGTH(iam1.g_parentSequence) >= (LENGTH(iam0.g_parentSequence) + LENGTH(CHAR(iam0.g_itemId) + 1) AND
SUBSTR(iam1.g_parentSequence, 1, LENGTH(iam0.g_parentSequence) + LENGTH(CHAR(iam0.g_itemId) + 1) = iam0.g_parentSequence || CHAR(iam0.g_itemId) || '/'

now, the question whether this is a correct replacement:
- yes! great job. that's really excellent!
- if this CHAR casting needs an argument / needs to know how long it should make the string, then we have to find a solution for this hopefully small problem

@java:
we'd of course ship g2 with a compiled java file. but it seems we don't need it.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 00:55

I just updated the newsgroup saying that no I am still not able to get the MTK. But given the other things I've learned about UDFs in the last few days, I don't think it would work anyway. I suspect that this is a scenario that the MTK folks overlooked (or chose not to handle).

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Fri, 2005-11-11 01:19

you may have overlooked my answer since we were posting almost at the same time. please check my answer.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 02:18

1) Yes, quite sure.

2a) CAST() needs a specific length.
2b) Sorry, I was confusing the number of digits in an INTEGER and its on-disk space size. Type INTEGER takes 4 bytes on-disk in DB2. The max integer value in DB2 is 32,767. I don't think the LENGTH() function will work against an INTEGER, but I can make it CHAR(5), or perhaps look for a way to make it something more portable.

3) Same as #2b. I can make this CHAR(6), or perhaps look for a way to make it something more portable.

4) Right, in fact I thought I already had left it off. ;-)

5) Not sure I can visualize that right now. Let me get some progress made on the above things, then come back to this one.

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Fri, 2005-11-11 03:52

Sure not a problem, but if you do want the MTK to try it I'm more than happy to put it up, or send it to you.
As if I understand what they said correctly, and they wrote a new LIKE predicate, then it could work. As it sounds like the existing LIKE is not fucntional enough, so they replaced the LIKE with one more similar to other DBMS's?

Just some guessing.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 06:14

Valiant, here are the answers to the outstanding questions from above:

2b & 3) I was incorrect... maximum INTEGER value is 2,147,483,647. (32,767 is the SMALLINT max.) I trust itemId won't exceed that? :-) And you are right, I can avoid hard-coding the lengths of strings by using CHAR(itemId) instead of CAST(itemId AS CHAR(n)).
5) I think I misled you with an error in my SQL. :-) I think a better way to do it is:

Quote:
AND LOCATE(iam0.g_parentSequence || CHAR(iam0.g_itemId) || '/',
iam1.g_parentSequence,
1) > 0

This way, we avoid the SUBSTR and hard-coded lengths entirely. If iam1.g_parentSequence is shorter than iam0.g_parentSequence LENGTH(CHAR((iam0.g_itemId) + 1, it will simply return false (0).

I think this satisfies all the requirements. The query works, and I'm now moving on to the next problem, a BIT_OR issue.

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Fri, 2005-11-11 09:44

- INTEGER: 2,147,483,647 is certainly better than 32,767. else we'd have had a problem, since our sequence for IDs in G2 reaches a few thousands quite fast.
- LOCATE is also fine. i don't know if it is faster than the other approach since it doesn't stop comparing the two strings after the LENGTH(iam0.g_parentSequence || CHAR(iam0.g_itemId) || '/'), but i guess it could still be faster...

a slight correction:
AND LOCATE(iam0.g_parentSequence || CHAR(iam0.g_itemId) || '/', iam1.g_parentSequence) = 1
i removed the 3rd argument since it defaults to 1 anyway and i require a result of 1 not just > 0 since our original statement was a LIKE statement which doesn't have % at the beginning.

so now we have a replacement for
OP1 LIKE CONCAT(OP2, OP3)
constructs. we're using concat in very few places, but we'd have to change those a little and we'd have to change the DB abstraction classes a little, i.e. add LIKECONCAT or something like that.

of course we'd prefer if we didn't have to do this.
how much work would this UDF LIKE replacement be? if you give me the choice between a UDF LIKE and the LIKE CONCAT replacement, i'd choose the UDF LIKE.
then we could just add a DB abstraction LIKE instead of a weird LIKECONCAT and we wouldn't have to deal with additional issues and it would be more flexible.

I can understand that you're quite happy with the LOCATE() solution and that you don't want to spend even more work on this.

sphericus
could you maybe create this LIKE UDF?
it sounds like the algorithm was already posted somewhere, right?

of course, if it's too complex, we'll go with the LIKE CONCAT replacement...

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 15:59

I don't mind taking a look into it if Sphericus can send or attach the MTK. I am curious about it.

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Fri, 2005-11-11 17:33

The MTK is 38MB and it exceeds the maximum size for this forum. Do you have any preference for distribution?

 
valiant

Joined: 2003-01-04
Posts: 32509
Posted: Fri, 2005-11-11 17:39

can you upload it to your website just for a few hours?
else: i could give you a temp account for the ftp server of my website and then Larry could download it from there.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 17:45

I've started up my FTP server ("ftp cpe0013102da23b-cm0f0079804905.cpe.net.cable.rogers.com"), and created userid "sphericus" with password "gallery2". You can put the file there (remember to specify binary if it's compressed).

I don't like to leave the ftp server running, so I'll shut it down when I see the file arrive.

Thanks.

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Fri, 2005-11-11 18:22

I am currently uploading straight to Larry.
Should be finished shortly.

 
Larry Menard
Larry Menard's picture

Joined: 2005-10-01
Posts: 757
Posted: Fri, 2005-11-11 18:48

Got it. I'm shutting down my server now. Thanks, Sphericus.

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Fri, 2005-11-11 18:53

No Problem.

If you guys need anything else let me know.

 
sphericus

Joined: 2005-11-06
Posts: 55
Posted: Fri, 2005-11-11 18:55

valiant: I would write a LIKE UDF if, I had the expertise, which I lack.
However, I believe that Larry may be able to come up with something, as there are LIKE UDF's in the MTK, four infact that I was able to find. Two for MS SQL migration, and two from a Sybase migration. Hopefully one of those will be functional.