r1871 + implement IPv4 VS tag filtering
[racktables] / inc / database.php
index 32acf83ec85422c45dd4e119e1774d4fff0424f0..aff37045ed37a3812269ae853454c9eceffd5261 100644 (file)
@@ -5,17 +5,21 @@
 *
 */
 
-function escapeString ($value)
+function escapeString ($value, $do_db_escape = TRUE)
 {
-       global $dbxlink;
-       return substr ($dbxlink->quote (htmlentities ($value)), 1, -1);
+       $ret = htmlentities ($value, ENT_QUOTES, 'UTF-8');
+       if ($do_db_escape)
+       {
+               global $dbxlink;
+               $ret = substr ($dbxlink->quote ($ret), 1, -1);
+       }
+       return $ret;
 }
 
 // This function returns detailed information about either all or one
 // rack row depending on its argument.
 function getRackRowInfo ($rackrow_id = 0)
 {
-       global $dbxlink;
        $query =
                "select dict_key, dict_value, count(Rack.id) as count, " .
                "if(isnull(sum(Rack.height)),0,sum(Rack.height)) as sum " .
@@ -23,12 +27,7 @@ function getRackRowInfo ($rackrow_id = 0)
                "where chapter_name = 'RackRow' " .
                ($rackrow_id > 0 ? "and dict_key = ${rackrow_id} " : '') .
                "group by dict_key order by dict_value";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getRackRowInfo()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $clist = array ('dict_key', 'dict_value', 'count', 'sum');
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -48,9 +47,22 @@ function getObjectTypeList ()
        return readChapter ('RackObjectType');
 }
 
-function getObjectList ($type_id = 0)
+function getObjectList ($type_id = 0, $tagfilter = array())
 {
-       global $dbxlink;
+       $whereclause = '';
+       if ($type_id != 0)
+               $whereclause = " and objtype_id = '${type_id}' ";
+       if (count ($tagfilter))
+       {
+               $whereclause .= ' and (';
+               $orclause = '';
+               foreach ($tagfilter as $tag_id)
+               {
+                       $whereclause .= $orclause . 'tag_id = ' . $tag_id;
+                       $orclause = ' or ';
+               }
+               $whereclause .= ') ';
+       }
        $query =
                "select distinct RackObject.id as id , RackObject.name as name, dict_value as objtype_name, " .
                "RackObject.label as label, RackObject.barcode as barcode, " .
@@ -58,14 +70,11 @@ function getObjectList ($type_id = 0)
                "((RackObject inner join Dictionary on objtype_id=dict_key natural join Chapter) " .
                "left join RackSpace on RackObject.id = object_id) " .
                "left join Rack on rack_id = Rack.id " .
-               "where objtype_id = '${type_id}' and RackObject.deleted = 'no' " .
-               "and chapter_name = 'RackObjectType' order by name";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getObjectList()');
-               return;
-       }
+               "left join TagStorage on RackObject.id = TagStorage.target_id and target_realm = 'object' " .
+               "where RackObject.deleted = 'no' and chapter_name = 'RackObjectType' " .
+               $whereclause .
+               "order by name";
+       $result = useSelectBlade ($query);
        $ret = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -89,7 +98,6 @@ function getObjectList ($type_id = 0)
 
 function getRacksForRow ($row_id = 0)
 {
-       global $dbxlink;
        $query =
                "select Rack.id, Rack.name, height, Rack.comment, row_id, " .
                "'yes' as left_is_front, 'yes' as bottom_is_unit1, dict_value as row_name " .
@@ -97,12 +105,7 @@ function getRacksForRow ($row_id = 0)
                "where chapter_name = 'RackRow' and Rack.deleted = 'no' " .
                (($row_id == 0) ? "" : "and row_id = ${row_id} ") .
                "order by row_name, Rack.id";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getRacksForRow()');
-               return;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $clist = array
        (
@@ -131,26 +134,19 @@ function getRackData ($rack_id = 0, $silent = FALSE)
        if ($rack_id == 0)
        {
                if ($silent == FALSE)
-                       showError ('Invalid rack_id in getRackData()');
+                       showError ('Invalid rack_id', __FUNCTION__);
                return NULL;
        }
-       global $dbxlink;
        $query =
                "select Rack.id, Rack.name, row_id, height, Rack.comment, " .
                "'yes' as left_is_front, 'yes' as bottom_is_unit1, dict_value as row_name from " .
                "Rack left join Dictionary on Rack.row_id = dict_key natural join Chapter " .
                "where chapter_name = 'RackRow' and Rack.id='${rack_id}' and Rack.deleted = 'no' limit 1";
-       $result1 = $dbxlink->query ($query);
-       if ($result1 == NULL)
-       {
-               if ($silent == FALSE)
-                       showError ("SQL query #1 failed in getRackData()");
-               return NULL;
-       }
-       if (($row = $result1->fetch (PDO::FETCH_ASSOC)) == NULL)
+       $result = useSelectBlade ($query);
+       if (($row = $result->fetch (PDO::FETCH_ASSOC)) == NULL)
        {
                if ($silent == FALSE)
-                       showError ('Query #1 succeded, but returned no data in getRackData()');
+                       showError ('Query #1 succeded, but returned no data', __FUNCTION__);
                return NULL;
        }
 
@@ -168,7 +164,8 @@ function getRackData ($rack_id = 0, $silent = FALSE)
        );
        foreach ($clist as $cname)
                $rack[$cname] = $row[$cname];
-       $result1->closeCursor();
+       $result->closeCursor();
+       unset ($result);
 
        // start with default rackspace
        for ($i = $rack['height']; $i > 0; $i--)
@@ -180,20 +177,14 @@ function getRackData ($rack_id = 0, $silent = FALSE)
                "select unit_no, atom, state, object_id " .
                "from RackSpace where rack_id = ${rack_id} and " .
                "unit_no between 1 and " . $rack['height'] . " order by unit_no";
-       $result2 = $dbxlink->query ($query);
-       if ($result2 == NULL)
-       {
-               if ($silent == FALSE)
-                       showError ('SQL query failure #2 in getRackData()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        global $loclist;
-       while ($row = $result2->fetch (PDO::FETCH_ASSOC))
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $rack[$row['unit_no']][$loclist[$row['atom']]]['state'] = $row['state'];
                $rack[$row['unit_no']][$loclist[$row['atom']]]['object_id'] = $row['object_id'];
        }
-       $result2->closeCursor();
+       $result->closeCursor();
        return $rack;
 }
 
@@ -202,24 +193,17 @@ function getObjectInfo ($object_id = 0)
 {
        if ($object_id == 0)
        {
-               showError ('Invalid object_id in getObjectInfo()');
+               showError ('Invalid object_id', __FUNCTION__);
                return;
        }
-       global $dbxlink;
        $query =
                "select id, name, label, barcode, dict_value as objtype_name, asset_no, dict_key as objtype_id, has_problems, comment from " .
                "RackObject inner join Dictionary on objtype_id = dict_key natural join Chapter " .
                "where id = '${object_id}' and deleted = 'no' and chapter_name = 'RackObjectType' limit 1";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               $ei = $dbxlink->errorInfo();
-               showError ("SQL query failed in getObjectInfo (${object_id}) with error ${ei[1]} (${ei[2]})");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        if (($row = $result->fetch (PDO::FETCH_ASSOC)) == NULL)
        {
-               showError ('Query succeded, but returned no data in getObjectInfo()');
+               showError ('Query succeded, but returned no data', __FUNCTION__);
                $ret = NULL;
        }
        else
@@ -248,10 +232,9 @@ function getObjectPortsAndLinks ($object_id = 0)
 {
        if ($object_id == 0)
        {
-               showError ('Invalid object_id in getObjectPorts()');
+               showError ('Invalid object_id', __FUNCTION__);
                return;
        }
-       global $dbxlink;
        $query =
                "select Port.id as Port_id, ".
                "Port.name as Port_name, ".
@@ -277,36 +260,29 @@ function getObjectPortsAndLinks ($object_id = 0)
                "where chapter_name = 'PortType' and Port.object_id=${object_id} ".
                "and (Port.id != RemotePort.id or RemotePort.id is null) ".
                "order by Port_name";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               return NULL;
-       }
-       else
+       $result = useSelectBlade ($query);
+       $ret=array();
+       $count=0;
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               $ret=array();
-               $count=0;
-               while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[$count]['id'] = $row['Port_id'];
+               $ret[$count]['name'] = $row['Port_name'];
+               $ret[$count]['l2address'] = l2addressFromDatabase ($row['Port_l2address']);
+               $ret[$count]['label'] = $row['Port_label'];
+               $ret[$count]['type_id'] = $row['Port_type'];
+               $ret[$count]['type'] = $row['PortType_name'];
+               $ret[$count]['reservation_comment'] = $row['Port_reservation_comment'];
+               $ret[$count]['remote_id'] = $row['RemotePort_id'];
+               $ret[$count]['remote_name'] = htmlentities ($row['RemotePort_name'], ENT_QUOTES);
+               $ret[$count]['remote_object_id'] = $row['RemotePort_object_id'];
+               $ret[$count]['remote_object_name'] = $row['RackObject_name'];
+               // Save on displayedName() calls.
+               if (empty ($row['RackObject_name']) and !empty ($row['RemotePort_object_id']))
                {
-                       $ret[$count]['id'] = $row['Port_id'];
-                       $ret[$count]['name'] = $row['Port_name'];
-                       $ret[$count]['l2address'] = l2addressFromDatabase ($row['Port_l2address']);
-                       $ret[$count]['label'] = $row['Port_label'];
-                       $ret[$count]['type_id'] = $row['Port_type'];
-                       $ret[$count]['type'] = $row['PortType_name'];
-                       $ret[$count]['reservation_comment'] = $row['Port_reservation_comment'];
-                       $ret[$count]['remote_id'] = $row['RemotePort_id'];
-                       $ret[$count]['remote_name'] = htmlentities ($row['RemotePort_name'], ENT_QUOTES);
-                       $ret[$count]['remote_object_id'] = $row['RemotePort_object_id'];
-                       $ret[$count]['remote_object_name'] = $row['RackObject_name'];
-                       // Save on displayedName() calls.
-                       if (empty ($row['RackObject_name']) and !empty ($row['RemotePort_object_id']))
-                       {
-                               $oi = getObjectInfo ($row['RemotePort_object_id']);
-                               $ret[$count]['remote_object_name'] = displayedName ($oi);
-                       }
-                       $count++;
+                       $oi = getObjectInfo ($row['RemotePort_object_id']);
+                       $ret[$count]['remote_object_name'] = displayedName ($oi);
                }
+               $count++;
        }
        $result->closeCursor();
        return $ret;
@@ -319,7 +295,7 @@ function commitAddRack ($name, $height, $row_id, $comment)
        $result1 = $dbxlink->query ($query);
        if ($result1 == NULL)
        {
-               showError ('SQL query failed in commitAddRack()');
+               showError ('SQL query failed', __FUNCTION__);
                return FALSE;
        }
        // last_insert_id() is MySQL-specific
@@ -327,7 +303,7 @@ function commitAddRack ($name, $height, $row_id, $comment)
        $result2 = $dbxlink->query ($query);
        if ($result2 == NULL)
        {
-               showError ('Cannot get last ID in commitAddRack()');
+               showError ('Cannot get last ID', __FUNCTION__);
                return FALSE;
        }
        // we always have a row
@@ -351,12 +327,12 @@ function commitAddObject ($new_name, $new_label, $new_barcode, $new_type_id, $ne
        if ($result1 == NULL)
        {
                $errorInfo = $dbxlink->errorInfo();
-               showError ("SQL query '${query}' failed in commitAddObject(): " . $errorInfo[2]);
+               showError ("SQL query '${query}' failed: ${errorInfo[2]}", __FUNCTION__);
                die;
        }
        if ($result1->rowCount() != 1)
        {
-               showError ('Adding new object failed in commitAddObject()');
+               showError ('Adding new object failed', __FUNCTION__);
                return FALSE;
        }
        $query = 'select last_insert_id()';
@@ -364,13 +340,15 @@ function commitAddObject ($new_name, $new_label, $new_barcode, $new_type_id, $ne
        if ($result2 == NULL)
        {
                $errorInfo = $dbxlink->errorInfo();
-               showError ("SQL query '${query}' failed in commitAddObject(): " . $errorInfo[2]);
+               showError ("SQL query '${query}' failed: ${errorInfo[2]}", __FUNCTION__);
                die;
        }
        // we always have a row
        $row = $result2->fetch (PDO::FETCH_NUM);
        $last_insert_id = $row[0];
        $result2->closeCursor();
+       // Do AutoPorts magic
+       executeAutoPorts ($last_insert_id, $new_type_id);
        return recordHistory ('RackObject', "id = ${last_insert_id}");
 }
 
@@ -378,7 +356,7 @@ function commitUpdateObject ($object_id = 0, $new_name = '', $new_label = '', $n
 {
        if ($object_id == 0 || $new_type_id == 0)
        {
-               showError ('Not all required args to commitUpdateObject() are present.');
+               showError ('Not all required args are present.', __FUNCTION__);
                return FALSE;
        }
        global $dbxlink;
@@ -391,12 +369,7 @@ function commitUpdateObject ($object_id = 0, $new_name = '', $new_label = '', $n
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ("SQL query '${query}' failed in commitUpdateObject");
-               return FALSE;
-       }
-       if ($result->rowCount() != 1)
-       {
-               showError ('Error updating object information in commitUpdateObject()');
+               showError ("SQL query '${query}' failed", __FUNCTION__);
                return FALSE;
        }
        $result->closeCursor();
@@ -407,7 +380,7 @@ function commitUpdateRack ($rack_id, $new_name, $new_height, $new_row_id, $new_c
 {
        if (empty ($rack_id) || empty ($new_name) || empty ($new_height))
        {
-               showError ('Not all required args to commitUpdateRack() are present.');
+               showError ('Not all required args are present.', __FUNCTION__);
                return FALSE;
        }
        global $dbxlink;
@@ -416,7 +389,7 @@ function commitUpdateRack ($rack_id, $new_name, $new_height, $new_row_id, $new_c
        $result1 = $dbxlink->query ($query);
        if ($result1->rowCount() != 1)
        {
-               showError ('Error updating rack information in commitUpdateRack()');
+               showError ('Error updating rack information', __FUNCTION__);
                return FALSE;
        }
        return recordHistory ('Rack', "id = ${rack_id}");
@@ -465,7 +438,7 @@ function processGridForm (&$rackData, $unchecked_state, $checked_state, $object_
                                "unit_no = ${unit_no} and atom = '${atom}' limit 1";
                        $r = $dbxlink->query ($query);
                        if ($r == NULL)
-                               return array ('code' => 500, 'message' => "${rack_name}: SQL DELETE query failed in processGridForm()");
+                               return array ('code' => 500, 'message' => __FUNCTION__ . ": ${rack_name}: SQL DELETE query failed");
                        if ($newstate != 'F')
                        {
                                $query =
@@ -473,7 +446,7 @@ function processGridForm (&$rackData, $unchecked_state, $checked_state, $object_
                                        "values(${rack_id}, ${unit_no}, '${atom}', '${newstate}') ";
                                $r = $dbxlink->query ($query);
                                if ($r == NULL)
-                                       return array ('code' => 500, 'message' => "${rack_name}: SQL INSERT query failed in processGridForm()");
+                                       return array ('code' => 500, 'message' => __FUNCTION__ . ": ${rack_name}: SQL INSERT query failed");
                        }
                        if ($newstate == 'T' and $object_id != 0)
                        {
@@ -490,7 +463,10 @@ function processGridForm (&$rackData, $unchecked_state, $checked_state, $object_
                }
        }
        if ($rackchanged)
+       {
+               resetThumbCache ($rack_id);
                return array ('code' => 200, 'message' => "${rack_name}: All changes were successfully saved.");
+       }
        else
                return array ('code' => 300, 'message' => "${rack_name}: No changes.");
 }
@@ -501,19 +477,13 @@ function getMoleculeForObject ($object_id = 0)
 {
        if ($object_id == 0)
        {
-               showError ("object_id == 0 in getMoleculeForObject()");
+               showError ("object_id == 0", __FUNCTION__);
                return NULL;
        }
-       global $dbxlink;
        $query =
                "select rack_id, unit_no, atom from RackSpace " .
                "where state = 'T' and object_id = ${object_id} order by rack_id, unit_no, atom";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed in getMoleculeForObject()");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = $result->fetchAll (PDO::FETCH_ASSOC);
        $result->closeCursor();
        return $ret;
@@ -524,19 +494,13 @@ function getMolecule ($mid = 0)
 {
        if ($mid == 0)
        {
-               showError ("mid == 0 in getMolecule()");
+               showError ("mid == 0", __FUNCTION__);
                return NULL;
        }
-       global $dbxlink;
        $query =
                "select rack_id, unit_no, atom from Atom " .
                "where molecule_id=${mid}";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed in getMolecule()");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = $result->fetchAll (PDO::FETCH_ASSOC);
        $result->closeCursor();
        return $ret;
@@ -551,14 +515,14 @@ function createMolecule ($molData)
        $result1 = $dbxlink->query ($query);
        if ($result1->rowCount() != 1)
        {
-               showError ('Error inserting into Molecule in createMolecule()');
+               showError ('Error inserting into Molecule', __FUNCTION__);
                return NULL;
        }
        $query = 'select last_insert_id()';
        $result2 = $dbxlink->query ($query);
        if ($result2 == NULL)
        {
-               showError ('Cannot get last ID in createMolecule().');
+               showError ('Cannot get last ID.', __FUNCTION__);
                return NULL;
        }
        $row = $result2->fetch (PDO::FETCH_NUM);
@@ -575,7 +539,7 @@ function createMolecule ($molData)
                $result3 = $dbxlink->query ($query);
                if ($result3 == NULL or $result3->rowCount() != 1)
                {
-                       showError ('Error inserting into Atom in createMolecule()');
+                       showError ('Error inserting into Atom', __FUNCTION__);
                        return NULL;
                }
        }
@@ -594,7 +558,7 @@ function recordHistory ($tableName, $whereClause)
        $result = $dbxlink->query ($query);
        if ($result == NULL or $result->rowCount() != 1)
        {
-               showError ("SQL query failed in recordHistory() for table ${tableName}");
+               showError ("SQL query '${query}' failed for table ${tableName}", __FUNCTION__);
                return FALSE;
        }
        return TRUE;
@@ -602,18 +566,12 @@ function recordHistory ($tableName, $whereClause)
 
 function getRackspaceHistory ()
 {
-       global $dbxlink;
        $query =
                "select mo.id as mo_id, ro.id as ro_id, ro.name, mo.ctime, mo.comment, dict_value as objtype_name, user_name from " .
                "MountOperation as mo inner join RackObject as ro on mo.object_id = ro.id " .
                "inner join Dictionary on objtype_id = dict_key natural join Chapter " .
                "where chapter_name = 'RackObjectType' order by ctime desc";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getRackspaceHistory()');
-               return;
-       }
+       $result = useSelectBlade ($query);
        $ret = $result->fetchAll(PDO::FETCH_ASSOC);
        $result->closeCursor();
        return $ret;
@@ -624,22 +582,16 @@ function getOperationMolecules ($op_id = 0)
 {
        if ($op_id <= 0)
        {
-               showError ("Missing argument to getOperationMolecules()");
+               showError ("Missing argument", __FUNCTION__);
                return;
        }
-       global $dbxlink;
        $query = "select old_molecule_id, new_molecule_id from MountOperation where id = ${op_id}";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed in getOperationMolecules()");
-               return;
-       }
+       $result = useSelectBlade ($query);
        // We expect one row.
        $row = $result->fetch (PDO::FETCH_ASSOC);
        if ($row == NULL)
        {
-               showError ("SQL query succeded, but returned no results in getOperationMolecules().");
+               showError ("SQL query succeded, but returned no results.", __FUNCTION__);
                return;
        }
        $omid = $row['old_molecule_id'];
@@ -648,30 +600,29 @@ function getOperationMolecules ($op_id = 0)
        return array ($omid, $nmid);
 }
 
-function getResidentRacksData ($object_id = 0)
+function getResidentRacksData ($object_id = 0, $fetch_rackdata = TRUE)
 {
        if ($object_id <= 0)
        {
-               showError ('Invalid object_id in getResidentRacksData()');
+               showError ('Invalid object_id', __FUNCTION__);
                return;
        }
        $query = "select distinct rack_id from RackSpace where object_id = ${object_id} order by rack_id";
-       global $dbxlink;
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed in getResidentRacksData()");
-               return;
-       }
+       $result = useSelectBlade ($query);
        $rows = $result->fetchAll (PDO::FETCH_NUM);
        $result->closeCursor();
        $ret = array();
        foreach ($rows as $row)
        {
+               if (!$fetch_rackdata)
+               {
+                       $ret[$row[0]] = $row[0];
+                       continue;
+               }
                $rackData = getRackData ($row[0]);
                if ($rackData == NULL)
                {
-                       showError ('getRackData() failed in getResidentRacksData()');
+                       showError ('getRackData() failed', __FUNCTION__);
                        return NULL;
                }
                $ret[$row[0]] = $rackData;
@@ -680,31 +631,28 @@ function getResidentRacksData ($object_id = 0)
        return $ret;
 }
 
-function getObjectGroupInfo ($group_id = 0)
+function getObjectGroupInfo ()
 {
        $query =
                'select dict_key as id, dict_value as name, count(id) as count from ' .
                'Dictionary natural join Chapter left join RackObject on dict_key = objtype_id ' .
                'where chapter_name = "RackObjectType" ' .
-               (($group_id > 0) ? "and dict_key = ${group_id} " : '') .
-               'group by dict_key';
-       global $dbxlink;
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getObjectGroupSummary');
-               return NULL;
-       }
+               'group by dict_key order by dict_value';
+       $result = useSelectBlade ($query);
        $ret = array();
+       $ret[0] = array ('id' => 0, 'name' => 'ALL types');
        $clist = array ('id', 'name', 'count');
+       $total = 0;
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
-               foreach ($clist as $dummy => $cname)
-                       $ret[$row['id']][$cname] = $row[$cname];
+               if ($row['count'] > 0)
+               {
+                       $total += $row['count'];
+                       foreach ($clist as $dummy => $cname)
+                               $ret[$row['id']][$cname] = $row[$cname];
+               }
        $result->closeCursor();
-       if ($group_id > 0)
-               return current ($ret);
-       else
-               return $ret;
+       $ret[0]['count'] = $total;
+       return $ret;
 }
 
 // This function returns objects, which have no rackspace assigned to them.
@@ -712,18 +660,12 @@ function getObjectGroupInfo ($group_id = 0)
 // the rack required.
 function getUnmountedObjects ()
 {
-       global $dbxlink;
        $query =
                'select dict_value as objtype_name, dict_key as objtype_id, name, label, barcode, id, asset_no from ' .
                'RackObject inner join Dictionary on objtype_id = dict_key natural join Chapter ' .
                'left join RackSpace on id = object_id '.
                'where rack_id is null and chapter_name = "RackObjectType" order by dict_value, name, label, asset_no, barcode';
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failure in getUnmountedObjects()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $clist = array ('id', 'name', 'label', 'barcode', 'objtype_name', 'objtype_id', 'asset_no');
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -738,17 +680,11 @@ function getUnmountedObjects ()
 
 function getProblematicObjects ()
 {
-       global $dbxlink;
        $query =
                'select dict_value as objtype_name, dict_key as objtype_id, name, id, asset_no from ' .
                'RackObject inner join Dictionary on objtype_id = dict_key natural join Chapter '.
                'where has_problems = "yes" and chapter_name = "RackObjectType" order by objtype_name, name';
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failure in getProblematicObjects()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $clist = array ('id', 'name', 'objtype_name', 'objtype_id', 'asset_no');
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -765,7 +701,7 @@ function commitAddPort ($object_id = 0, $port_name, $port_type_id, $port_label,
 {
        if ($object_id <= 0)
        {
-               showError ('Invalid object_id in commitAddPort()');
+               showError ('Invalid object_id', __FUNCTION__);
                return;
        }
        $port_l2address = l2addressForDatabase ($port_l2address);
@@ -808,27 +744,21 @@ function commitUpdatePort ($port_id, $port_name, $port_label, $port_l2address, $
 function delObjectPort ($port_id)
 {
        if (unlinkPort ($port_id) != '')
-               return 'unlinkPort() failed in delObjectPort()';
+               return __FUNCTION__ . ': unlinkPort() failed';
        if (useDeleteBlade ('Port', 'id', $port_id) != TRUE)
-               return 'useDeleteBlade() failed in delObjectPort()';
+               return __FUNCTION__ . ': useDeleteBlade() failed';
        return '';
 }
 
 function getObjectAddressesAndNames ()
 {
-       global $dbxlink;
        $query =
                "select object_id as object_id, ".
                "RackObject.name as object_name, ".
                "IPBonds.name as name, ".
                "INET_NTOA(ip) as ip ".
                "from IPBonds join RackObject on id=object_id ";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failure in getObjectAddressesAndNames()");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $count=0;
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -846,7 +776,6 @@ function getObjectAddressesAndNames ()
 
 function getEmptyPortsOfType ($type_id)
 {
-       global $dbxlink;
        $query =
                "select distinct Port.id as Port_id, ".
                "Port.object_id as Port_object_id, ".
@@ -864,12 +793,7 @@ function getEmptyPortsOfType ($type_id)
                "inner join PortCompat on Port.type = PortCompat.type2 ".
                "where chapter_name = 'PortType' and PortCompat.type1 = '$type_id' and Link.porta is NULL ".
                "and Port.reservation_comment is null order by Object_name, Port_name";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failure in getEmptyPortsOfType($type_id)");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $count=0;
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -922,10 +846,9 @@ function getObjectAddresses ($object_id = 0)
 {
        if ($object_id == 0)
        {
-               showError ('Invalid object_id in getObjectAddresses()');
+               showError ('Invalid object_id', __FUNCTION__);
                return;
        }
-       global $dbxlink;
        $query =
                "select ".
                "IPAddress.name as IPAddress_name, ".
@@ -944,88 +867,85 @@ function getObjectAddresses ($object_id = 0)
                "where ".
                "IPBonds.object_id = ${object_id} ".
                "order by IPBonds.ip, RemoteObject.name";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed in getObjectAddresses()");
-               return NULL;
-       }
-       else
+       $result = useSelectBlade ($query);
+       $ret=array();
+       $count=0;
+       $refcount=0;
+       $prev_ip = 0;
+       // We are going to call getObjectInfo() for some rows,
+       // hence the connector must be unloaded from the
+       // current data.
+       $rows = $result->fetchAll (PDO::FETCH_ASSOC);
+       $result->closeCursor();
+       foreach ($rows as $row)
        {
-               $ret=array();
-               $count=0;
-               $refcount=0;
-               $prev_ip = 0;
-               // We are going to call getObjectInfo() for some rows,
-               // hence the connector must be unloaded from the
-               // current data.
-               $rows = $result->fetchAll (PDO::FETCH_ASSOC);
-               $result->closeCursor();
-               foreach ($rows as $row)
+               if ($prev_ip != $row['IPBonds_ip'])
                {
-                       if ($prev_ip != $row['IPBonds_ip'])
-                       {
-                               $count++;
-                               $refcount=0;
-                               $prev_ip = $row['IPBonds_ip'];
-                               $ret[$count]['address_name'] = $row['IPAddress_name'];
-                               $ret[$count]['address_reserved'] = $row['IPAddress_reserved'];
-                               $ret[$count]['ip'] = $row['IPBonds_ip'];
-                               $ret[$count]['name'] = $row['IPBonds_name'];
-                               $ret[$count]['type'] = $row['IPBonds_type'];
-                               $ret[$count]['references'] = array();
-                       }
+                       $count++;
+                       $refcount=0;
+                       $prev_ip = $row['IPBonds_ip'];
+                       $ret[$count]['address_name'] = $row['IPAddress_name'];
+                       $ret[$count]['address_reserved'] = $row['IPAddress_reserved'];
+                       $ret[$count]['ip'] = $row['IPBonds_ip'];
+                       $ret[$count]['name'] = $row['IPBonds_name'];
+                       $ret[$count]['type'] = $row['IPBonds_type'];
+                       $ret[$count]['references'] = array();
+               }
 
-                       if ($row['RemoteBonds_type'])
+               if ($row['RemoteBonds_type'])
+               {
+                       $ret[$count]['references'][$refcount]['type'] = $row['RemoteBonds_type'];
+                       $ret[$count]['references'][$refcount]['name'] = $row['RemoteBonds_name'];
+                       $ret[$count]['references'][$refcount]['object_id'] = $row['RemoteBonds_object_id'];
+                       if (empty ($row['RemoteBonds_object_id']))
+                               $ret[$count]['references'][$refcount]['object_name'] = $row['RemoteObject_name'];
+                       else
                        {
-                               $ret[$count]['references'][$refcount]['type'] = $row['RemoteBonds_type'];
-                               $ret[$count]['references'][$refcount]['name'] = $row['RemoteBonds_name'];
-                               $ret[$count]['references'][$refcount]['object_id'] = $row['RemoteBonds_object_id'];
-                               if (empty ($row['RemoteBonds_object_id']))
-                                       $ret[$count]['references'][$refcount]['object_name'] = $row['RemoteObject_name'];
-                               else
-                               {
-                                       $oi = getObjectInfo ($row['RemoteBonds_object_id']);
-                                       $ret[$count]['references'][$refcount]['object_name'] = displayedName ($oi);
-                               }
-                               $refcount++;
+                               $oi = getObjectInfo ($row['RemoteBonds_object_id']);
+                               $ret[$count]['references'][$refcount]['object_name'] = displayedName ($oi);
                        }
+                       $refcount++;
                }
        }
        return $ret;
 }
 
-function getAddressspaceList ()
+function getAddressspaceList ($tagfilter = array())
 {
-       global $dbxlink;
+       if (!count ($tagfilter))
+               $whereclause = '';
+       else
+       {
+               $whereclause = 'where ';
+               $orclause = '';
+               foreach ($tagfilter as $tag_id)
+               {
+                       $whereclause .= $orclause . 'tag_id = ' . $tag_id;
+                       $orclause = ' or ';
+               }
+       }
        $query =
-               "select ".
+               "select distinct ".
                "id as IPRanges_id, ".
                "INET_NTOA(ip) as IPRanges_ip, ".
                "mask as IPRanges_mask, ".
                "name as IPRanges_name ".
-               "from IPRanges ".
-               "order by ip";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               return NULL;
-       }
-       else
+               "from IPRanges left join TagStorage on IPRanges.id = TagStorage.target_id and target_realm = 'ipv4net' " .
+               $whereclause .
+               " order by ip";
+       $result = useSelectBlade ($query);
+       $ret=array();
+       $count=0;
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               $ret=array();
-               $count=0;
-               while ($row = $result->fetch (PDO::FETCH_ASSOC))
-               {
-                       $ret[$count]['id'] = $row['IPRanges_id'];
-                       $ret[$count]['ip'] = $row['IPRanges_ip'];
-                       $ret[$count]['ip_bin'] = ip2long($row['IPRanges_ip']);
-                       $ret[$count]['name'] = $row['IPRanges_name'];
-                       $ret[$count]['mask'] = $row['IPRanges_mask'];
-                       $ret[$count]['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
-                       $ret[$count]['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
-                       $count++;
-               }
+               $ret[$count]['id'] = $row['IPRanges_id'];
+               $ret[$count]['ip'] = $row['IPRanges_ip'];
+               $ret[$count]['ip_bin'] = ip2long($row['IPRanges_ip']);
+               $ret[$count]['name'] = $row['IPRanges_name'];
+               $ret[$count]['mask'] = $row['IPRanges_mask'];
+               $ret[$count]['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
+               $ret[$count]['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
+               $count++;
        }
        $result->closeCursor();
        return $ret;
@@ -1034,7 +954,6 @@ function getAddressspaceList ()
 
 function getRangeByIp ($ip = '', $id = 0)
 {
-       global $dbxlink;
        if ($id == 0)
                $query =
                        "select ".
@@ -1046,27 +965,20 @@ function getRangeByIp ($ip = '', $id = 0)
                        "id, INET_NTOA(ip) as ip, mask, name ".
                        "from IPRanges where id='$id'";
                
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               return NULL;
-       }
-       else
+       $result = useSelectBlade ($query);
+       $ret=array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               $ret=array();
-               while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $binmask=binMaskFromDec($row['mask']);
+               if ((ip2long($ip) & $binmask) == ip2long($row['ip']))
                {
-                       $binmask=binMaskFromDec($row['mask']);
-                       if ((ip2long($ip) & $binmask) == ip2long($row['ip']))
-                       {
-                               $ret['id'] = $row['id'];
-                               $ret['ip'] = $row['ip'];
-                               $ret['ip_bin'] = ip2long($row['ip']);
-                               $ret['name'] = $row['name'];
-                               $ret['mask'] = $row['mask'];
-                               $result->closeCursor();
-                               return $ret;
-                       }
+                       $ret['id'] = $row['id'];
+                       $ret['ip'] = $row['ip'];
+                       $ret['ip_bin'] = ip2long($row['ip']);
+                       $ret['name'] = $row['name'];
+                       $ret['mask'] = $row['mask'];
+                       $result->closeCursor();
+                       return $ret;
                }
        }
        $result->closeCursor();
@@ -1103,11 +1015,11 @@ function updateAddress ($ip=0, $name='', $reserved='no')
 function commitDeleteRange ($id = 0)
 {
        if ($id <= 0)
-               return 'Invalid range ID in commitDeleteRange()';
+               return __FUNCTION__ . ': Invalid range ID';
        if (useDeleteBlade ('IPRanges', 'id', $id))
                return '';
        else
-               return 'SQL query failed in commitDeleteRange';
+               return __FUNCTION__ . ': SQL query failed';
 }
 
 function updateBond ($ip='', $object_id=0, $name='', $type='')
@@ -1133,16 +1045,10 @@ function unbindIpFromObject ($ip='', $object_id=0)
 // This function returns either all or one user account. Array key is user name.
 function getUserAccounts ()
 {
-       global $dbxlink;
        $query =
                'select user_id, user_name, user_password_hash, user_realname, user_enabled ' .
                'from UserAccount order by user_name';
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getUserAccounts()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        $clist = array ('user_id', 'user_name', 'user_realname', 'user_password_hash', 'user_enabled');
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -1155,17 +1061,11 @@ function getUserAccounts ()
 // This function returns permission array for all user accounts. Array key is user name.
 function getUserPermissions ()
 {
-       global $dbxlink;
        $query =
                "select UserPermission.user_id, user_name, page, tab, access from " .
                "UserPermission natural left join UserAccount where (user_name is not null) or " .
                "(user_name is null and UserPermission.user_id = 0) order by user_name, page, tab";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getUserPermissions()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -1179,37 +1079,57 @@ function getUserPermissions ()
 
 function searchByl2address ($l2addr)
 {
-       global $dbxlink;
        $l2addr = l2addressForDatabase ($l2addr);
        $query = "select object_id, Port.id as port_id from RackObject as ro inner join Port on ro.id = Port.object_id " .
                "where l2address = ${l2addr}";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in objectIDbyl2address()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $rows = $result->fetchAll (PDO::FETCH_ASSOC);
        $result->closeCursor();
        if (count ($rows) == 0) // No results.
                return NULL;
        if (count ($rows) == 1) // Target found.
                return $rows[0];
-       showError ('More than one results found in objectIDbyl2address(). This is probably a broken unique key.');
+       showError ('More than one results was found. This is probably a broken unique key.', __FUNCTION__);
        return NULL;
 }
 
+function getIPv4PrefixSearchResult ($terms)
+{
+       $query = "select id, inet_ntoa(ip) as ip, mask, name from IPRanges where ";
+       $or = '';
+       foreach (explode (' ', $terms) as $term)
+       {
+               $query .= $or . "name like '%${term}%'";
+               $or = ' or ';
+       }
+       $result = useSelectBlade ($query);
+       $ret = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[] = $row;
+       return $ret;
+}
+
+function getIPv4AddressSearchResult ($terms)
+{
+       $query = "select inet_ntoa(ip) as ip, name from IPAddress where ";
+       $or = '';
+       foreach (explode (' ', $terms) as $term)
+       {
+               $query .= $or . "name like '%${term}%'";
+               $or = ' or ';
+       }
+       $result = useSelectBlade ($query);
+       $ret = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[] = $row;
+       return $ret;
+}
+
 // This function returns either port ID or NULL for specified arguments.
 function getPortID ($object_id, $port_name)
 {
-       global $dbxlink;
        $query = "select id from Port where object_id=${object_id} and name='${port_name}' limit 2";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getPortID()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $rows = $result->fetchAll (PDO::FETCH_NUM);
        if (count ($rows) != 1)
                return NULL;
@@ -1241,7 +1161,7 @@ function commitUpdateUserAccount ($id, $new_username, $new_realname, $new_passwo
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitUpdateUserAccount()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1256,7 +1176,7 @@ function commitEnableUserAccount ($id, $new_enabled_value)
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitEnableUserAccount()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1286,7 +1206,7 @@ function commitRevokePermission ($userid, $page, $tab)
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitRevokePermission()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1295,7 +1215,6 @@ function commitRevokePermission ($userid, $page, $tab)
 // This function returns an array of all port type pairs from PortCompat table.
 function getPortCompat ()
 {
-       global $dbxlink;
        $query =
                "select type1, type2, d1.dict_value as type1name, d2.dict_value as type2name from " .
                "PortCompat as pc inner join Dictionary as d1 on pc.type1 = d1.dict_key " .
@@ -1303,12 +1222,7 @@ function getPortCompat ()
                "inner join Chapter as c1 on d1.chapter_no = c1.chapter_no " .
                "inner join Chapter as c2 on d2.chapter_no = c2.chapter_no " .
                "where c1.chapter_name = 'PortType' and c2.chapter_name = 'PortType'";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getPortCompat()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = $result->fetchAll (PDO::FETCH_ASSOC);
        $result->closeCursor();
        return $ret;
@@ -1319,14 +1233,14 @@ function removePortCompat ($type1 = 0, $type2 = 0)
        global $dbxlink;
        if ($type1 == 0 or $type2 == 0)
        {
-               showError ('Invalid arguments to removePortCompat');
+               showError ('Invalid arguments', __FUNCTION__);
                die;
        }
        $query = "delete from PortCompat where type1 = ${type1} and type2 = ${type2} limit 1";
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in removePortCompat()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1336,7 +1250,7 @@ function addPortCompat ($type1 = 0, $type2 = 0)
 {
        if ($type1 <= 0 or $type2 <= 0)
        {
-               showError ('Invalid arguments to addPortCompat');
+               showError ('Invalid arguments', __FUNCTION__);
                die;
        }
        return useInsertBlade
@@ -1349,18 +1263,12 @@ function addPortCompat ($type1 = 0, $type2 = 0)
 // This function returns the dictionary as an array of trees, each tree
 // representing a single chapter. Each element has 'id', 'name', 'sticky'
 // and 'word' keys with the latter holding all the words within the chapter.
-function getDict ()
+function getDict ($parse_links = FALSE)
 {
-       global $dbxlink;
-       $query =
+       $query1 =
                "select chapter_name, Chapter.chapter_no, dict_key, dict_value, sticky from " .
                "Chapter natural left join Dictionary order by chapter_name, dict_value";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('SQL query failed in getDict()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query1);
        $dict = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -1373,27 +1281,40 @@ function getDict ()
                        $dict[$chapter_no]['word'] = array();
                }
                if ($row['dict_key'] != NULL)
-                       $dict[$chapter_no]['word'][$row['dict_key']] = $row['dict_value'];
+               {
+                       $dict[$chapter_no]['word'][$row['dict_key']] = $parse_links ?
+                               parseWikiLink ($row['dict_value'], 'a') : $row['dict_value'];
+                       $dict[$chapter_no]['refcnt'][$row['dict_key']] = 0;
+               }
        }
        $result->closeCursor();
+       unset ($result);
+// Find the list of all assigned values of dictionary-addressed attributes, each with
+// chapter/word keyed reference counters. Use the structure to adjust reference counters
+// of the returned disctionary words.
+       $query2 = "select a.attr_id, am.chapter_no, uint_value, count(object_id) as refcnt " .
+               "from Attribute as a inner join AttributeMap as am on a.attr_id = am.attr_id " .
+               "inner join AttributeValue as av on a.attr_id = av.attr_id " .
+               "inner join Dictionary as d on am.chapter_no = d.chapter_no and av.uint_value = d.dict_key " .
+               "where attr_type = 'dict' group by a.attr_id, am.chapter_no, uint_value " .
+               "order by a.attr_id, am.chapter_no, uint_value";
+       $result = useSelectBlade ($query2);
+       $refcnt = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $dict[$row['chapter_no']]['refcnt'][$row['uint_value']] = $row['refcnt'];
+       $result->closeCursor();
        return $dict;
 }
 
 function getDictStats ()
 {
-       $stock_chapters = array (1, 2, 3, 11, 12, 13, 14, 16, 17, 18, 19, 20);
-       global $dbxlink;
+       $stock_chapters = array (1, 2, 3, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23);
        $query =
                "select Chapter.chapter_no, chapter_name, count(dict_key) as wc from " .
                "Chapter natural left join Dictionary group by Chapter.chapter_no";
-       $result1 = $dbxlink->query ($query);
-       if ($result1 == NULL)
-       {
-               showError ('SQL query #1 failed in getDictStats()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $tc = $tw = $uc = $uw = 0;
-       while ($row = $result1->fetch (PDO::FETCH_ASSOC))
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $tc++;
                $tw += $row['wc'];;
@@ -1402,17 +1323,13 @@ function getDictStats ()
                $uc++;
                $uw += $row['wc'];;
        }
-       $result1->closeCursor();
+       $result->closeCursor();
+       unset ($result);
        $query = "select count(attr_id) as attrc from RackObject as ro left join " .
                "AttributeValue as av on ro.id = av.object_id group by ro.id";
-       $result2 = $dbxlink->query ($query);
-       if ($result2 == NULL)
-       {
-               showError ('SQL query #2 failed in getDictStats()');
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $to = $ta = $so = 0;
-       while ($row = $result2->fetch (PDO::FETCH_ASSOC))
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $to++;
                if ($row['attrc'] != 0)
@@ -1421,7 +1338,7 @@ function getDictStats ()
                        $ta += $row['attrc'];
                }
        }
-       $result2->closeCursor();
+       $result->closeCursor();
        $ret = array();
        $ret['Total chapters in dictionary'] = $tc;
        $ret['Total words in dictionary'] = $tw;
@@ -1433,11 +1350,55 @@ function getDictStats ()
        return $ret;
 }
 
+function getIPv4Stats()
+{
+       $ret = array();
+       $subject = array();
+       $subject[] = array ('q' => 'select count(id) from IPRanges', 'txt' => 'Networks');
+       $subject[] = array ('q' => 'select count(ip) from IPAddress', 'txt' => 'Addresses commented/reserved');
+       $subject[] = array ('q' => 'select count(ip) from IPBonds', 'txt' => 'Addresses allocated');
+       $subject[] = array ('q' => 'select count(*) from PortForwarding', 'txt' => 'NAT rules');
+       $subject[] = array ('q' => 'select count(id) from IPVirtualService', 'txt' => 'Virtual services');
+       $subject[] = array ('q' => 'select count(id) from IPRSPool', 'txt' => 'Real server pools');
+       $subject[] = array ('q' => 'select count(id) from IPRealServer', 'txt' => 'Real servers');
+       $subject[] = array ('q' => 'select count(distinct object_id) from IPLoadBalancer', 'txt' => 'Load balancers');
+
+       foreach ($subject as $item)
+       {
+               $result = useSelectBlade ($item['q']);
+               $row = $result->fetch (PDO::FETCH_NUM);
+               $ret[$item['txt']] = $row[0];
+               $result->closeCursor();
+               unset ($result);
+       }
+       return $ret;
+}
+
+function getRackspaceStats()
+{
+       $ret = array();
+       $subject = array();
+       $subject[] = array ('q' => 'select count(*) from Dictionary where chapter_no = 3', 'txt' => 'Rack rows');
+       $subject[] = array ('q' => 'select count(*) from Rack', 'txt' => 'Racks');
+       $subject[] = array ('q' => 'select avg(height) from Rack', 'txt' => 'Average rack height');
+       $subject[] = array ('q' => 'select sum(height) from Rack', 'txt' => 'Total rack units in field');
+
+       foreach ($subject as $item)
+       {
+               $result = useSelectBlade ($item['q']);
+               $row = $result->fetch (PDO::FETCH_NUM);
+               $ret[$item['txt']] = empty ($row[0]) ? 0 : $row[0];
+               $result->closeCursor();
+               unset ($result);
+       }
+       return $ret;
+}
+
 function commitUpdateDictionary ($chapter_no = 0, $dict_key = 0, $dict_value = '')
 {
        if ($chapter_no <= 0 or $dict_key <= 0 or empty ($dict_value))
        {
-               showError ('Invalid args to commitUpdateDictionary()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1447,7 +1408,7 @@ function commitUpdateDictionary ($chapter_no = 0, $dict_key = 0, $dict_value = '
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitUpdateDictionary()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1457,7 +1418,7 @@ function commitSupplementDictionary ($chapter_no = 0, $dict_value = '')
 {
        if ($chapter_no <= 0 or empty ($dict_value))
        {
-               showError ('Invalid args to commitSupplementDictionary()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        return useInsertBlade
@@ -1471,7 +1432,7 @@ function commitReduceDictionary ($chapter_no = 0, $dict_key = 0)
 {
        if ($chapter_no <= 0 or $dict_key <= 0)
        {
-               showError ('Invalid args to commitReduceDictionary()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1481,7 +1442,7 @@ function commitReduceDictionary ($chapter_no = 0, $dict_key = 0)
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitReduceDictionary()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1491,7 +1452,7 @@ function commitAddChapter ($chapter_name = '')
 {
        if (empty ($chapter_name))
        {
-               showError ('Invalid args to commitAddChapter()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        return useInsertBlade
@@ -1505,7 +1466,7 @@ function commitUpdateChapter ($chapter_no = 0, $chapter_name = '')
 {
        if ($chapter_no <= 0 or empty ($chapter_name))
        {
-               showError ('Invalid args to commitUpdateChapter()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1515,7 +1476,7 @@ function commitUpdateChapter ($chapter_no = 0, $chapter_name = '')
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitUpdateChapter()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1525,7 +1486,7 @@ function commitDeleteChapter ($chapter_no = 0)
 {
        if ($chapter_no <= 0)
        {
-               showError ('Invalid args to commitDeleteChapter()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1534,7 +1495,7 @@ function commitDeleteChapter ($chapter_no = 0)
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitDeleteChapter()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1546,20 +1507,13 @@ function readChapter ($chapter_name = '')
 {
        if (empty ($chapter_name))
        {
-               showError ('invalid argument to readChapter()');
+               showError ('invalid argument', __FUNCTION__);
                return NULL;
        }
-       global $dbxlink;
        $query =
                "select dict_key, dict_value from Dictionary natural join Chapter " .
                "where chapter_name = '${chapter_name}'";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               $errorInfo = $dbxlink->errorInfo();
-               showError ("SQL query '${query}'\nwith message '${errorInfo[2]}'\nfailed in readChapter('${chapter_name}')");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $chapter = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                $chapter[$row['dict_key']] = parseWikiLink ($row['dict_value'], 'o');
@@ -1571,7 +1525,6 @@ function readChapter ($chapter_name = '')
 
 function getAttrMap ()
 {
-       global $dbxlink;
        $query =
                "select a.attr_id, a.attr_type, a.attr_name, am.objtype_id, " .
                "d.dict_value as objtype_name, am.chapter_no, c2.chapter_name from " .
@@ -1581,13 +1534,7 @@ function getAttrMap ()
                "left join Chapter as c2 on am.chapter_no = c2.chapter_no " .
                "where c1.chapter_name = 'RackObjectType' or c1.chapter_name is null " .
                "order by attr_name";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               $errorInfo = $dbxlink->errorInfo();
-               showError ("SQL query '${query}'\nwith message '${errorInfo[2]}'\nfailed in getAttrMap()");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
        $ret = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -1618,7 +1565,7 @@ function commitUpdateAttribute ($attr_id = 0, $attr_name = '')
 {
        if ($attr_id <= 0 or empty ($attr_name))
        {
-               showError ('Invalid args to commitUpdateAttribute()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1628,7 +1575,7 @@ function commitUpdateAttribute ($attr_id = 0, $attr_name = '')
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ("SQL query '${query}' failed in commitUpdateAttribute()");
+               showError ("SQL query '${query}' failed", __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1638,7 +1585,7 @@ function commitAddAttribute ($attr_name = '', $attr_type = '')
 {
        if (empty ($attr_name))
        {
-               showError ('Invalid args to commitAddAttribute()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        switch ($attr_type)
@@ -1649,7 +1596,7 @@ function commitAddAttribute ($attr_name = '', $attr_type = '')
                case 'dict':
                        break;
                default:
-                       showError ('Invalid args to commitAddAttribute()');
+                       showError ('Invalid args', __FUNCTION__);
                        die;
        }
        return useInsertBlade
@@ -1663,7 +1610,7 @@ function commitDeleteAttribute ($attr_id = 0)
 {
        if ($attr_id <= 0)
        {
-               showError ('Invalid args to commitDeleteAttribute()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        return useDeleteBlade ('Attribute', 'attr_id', $attr_id);
@@ -1674,7 +1621,7 @@ function commitSupplementAttrMap ($attr_id = 0, $objtype_id = 0, $chapter_no = 0
 {
        if ($attr_id <= 0 or $objtype_id <= 0 or $chapter_no <= 0)
        {
-               showError ('Invalid args to commitSupplementAttrMap()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        return useInsertBlade
@@ -1693,7 +1640,7 @@ function commitReduceAttrMap ($attr_id = 0, $objtype_id)
 {
        if ($attr_id <= 0 or $objtype_id <= 0)
        {
-               showError ('Invalid args to commitReduceAttrMap()');
+               showError ('Invalid args', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1703,7 +1650,7 @@ function commitReduceAttrMap ($attr_id = 0, $objtype_id)
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitReduceAttrMap()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1712,14 +1659,13 @@ function commitReduceAttrMap ($attr_id = 0, $objtype_id)
 // This function returns all optional attributes for requested object
 // as an array of records. NULL is returned on error and empty array
 // is returned, if there are no attributes found.
-function getAttrValues ($object_id)
+function getAttrValues ($object_id, $strip_optgroup = FALSE)
 {
        if ($object_id <= 0)
        {
-               showError ('Invalid argument to getAttrValues()');
+               showError ('Invalid argument', __FUNCTION__);
                return NULL;
        }
-       global $dbxlink;
        $ret = array();
        $query =
                "select A.attr_id, A.attr_name, A.attr_type, C.chapter_name, " .
@@ -1729,14 +1675,8 @@ function getAttrValues ($object_id)
                "left join AttributeValue as AV on AV.attr_id = AM.attr_id and AV.object_id = RO.id " .
                "left join Dictionary as D on D.dict_key = AV.uint_value and AM.chapter_no = D.chapter_no " .
                "left join Chapter as C on AM.chapter_no = C.chapter_no " .
-               "where RO.id = ${object_id} order by A.attr_type";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               $errorInfo = $dbxlink->errorInfo();
-               showError ("SQL query '${query}'\nwith message '${errorInfo[2]}'\nfailed in getAttrValues()");
-               return NULL;
-       }
+               "where RO.id = ${object_id} order by A.attr_type, A.attr_name";
+       $result = useSelectBlade ($query);
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $record = array();
@@ -1748,9 +1688,12 @@ function getAttrValues ($object_id)
                        case 'uint':
                        case 'float':
                        case 'string':
+                               $record['value'] = $row[$row['attr_type'] . '_value'];
+                               $record['a_value'] = parseWikiLink ($record['value'], 'a');
+                               break;
                        case 'dict':
-                               $record['value'] = parseWikiLink ($row[$row['attr_type'] . '_value'], 'o');
-                               $record['a_value'] = parseWikiLink ($row[$row['attr_type'] . '_value'], 'a');
+                               $record['value'] = parseWikiLink ($row[$row['attr_type'] . '_value'], 'o', $strip_optgroup);
+                               $record['a_value'] = parseWikiLink ($row[$row['attr_type'] . '_value'], 'a', $strip_optgroup);
                                $record['chapter_name'] = $row['chapter_name'];
                                $record['key'] = $row['uint_value'];
                                break;
@@ -1768,7 +1711,7 @@ function commitResetAttrValue ($object_id = 0, $attr_id = 0)
 {
        if ($object_id <= 0 or $attr_id <= 0)
        {
-               showError ('Invalid arguments to commitResetAttrValue()');
+               showError ('Invalid arguments', __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1776,7 +1719,7 @@ function commitResetAttrValue ($object_id = 0, $attr_id = 0)
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ('SQL query failed in commitResetAttrValue()');
+               showError ('SQL query failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1787,7 +1730,7 @@ function commitUpdateAttrValue ($object_id = 0, $attr_id = 0, $value = '')
 {
        if ($object_id <= 0 or $attr_id <= 0)
        {
-               showError ('Invalid arguments to commitUpdateAttrValue()');
+               showError ('Invalid arguments', __FUNCTION__);
                die;
        }
        if (empty ($value))
@@ -1797,13 +1740,13 @@ function commitUpdateAttrValue ($object_id = 0, $attr_id = 0, $value = '')
        $result = $dbxlink->query ($query1);
        if ($result == NULL)
        {
-               showError ('SQL query #1 failed in commitUpdateAttrValue()');
+               showError ('SQL query #1 failed', __FUNCTION__);
                die;
        }
        $row = $result->fetch (PDO::FETCH_NUM);
        if ($row == NULL)
        {
-               showError ('SQL query #1 returned no results in commitUpdateAttrValue()');
+               showError ('SQL query #1 returned no results', __FUNCTION__);
                die;
        }
        $attr_type = $row[0];
@@ -1819,7 +1762,7 @@ function commitUpdateAttrValue ($object_id = 0, $attr_id = 0, $value = '')
                        $column = 'uint_value';
                        break;
                default:
-                       showError ("Unknown attribute type '${attr_type}' met in commitUpdateAttrValue()");
+                       showError ("Unknown attribute type '${attr_type}' met", __FUNCTION__);
                        die;
        }
        $query2 =
@@ -1828,7 +1771,7 @@ function commitUpdateAttrValue ($object_id = 0, $attr_id = 0, $value = '')
        $result = $dbxlink->query ($query2);
        if ($result == NULL)
        {
-               showError ('SQL query #2 failed in commitUpdateAttrValue()');
+               showError ('SQL query #2 failed', __FUNCTION__);
                die;
        }
        // We know $value isn't empty here.
@@ -1838,7 +1781,7 @@ function commitUpdateAttrValue ($object_id = 0, $attr_id = 0, $value = '')
        $result = $dbxlink->query ($query3);
        if ($result == NULL)
        {
-               showError ('SQL query #3 failed in commitUpdateAttrValue()');
+               showError ('SQL query #3 failed', __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1848,7 +1791,7 @@ function commitUseupPort ($port_id = 0)
 {
        if ($port_id <= 0)
        {
-               showError ("Invalid argument to commitUseupPort()");
+               showError ("Invalid argument", __FUNCTION__);
                die;
        }
        global $dbxlink;
@@ -1856,7 +1799,7 @@ function commitUseupPort ($port_id = 0)
        $result = $dbxlink->exec ($query);
        if ($result == NULL)
        {
-               showError ("SQL query failed in commitUseupPort()");
+               showError ("SQL query failed", __FUNCTION__);
                die;
        }
        return TRUE;
@@ -1886,13 +1829,14 @@ function useInsertBlade ($tablename, $values)
 
 // This swiss-knife blade deletes one record from the specified table
 // using the specified key name and value.
-function useDeleteBlade ($tablename, $keyname, $keyvalue, $quotekey = TRUE)
+function useDeleteBlade ($tablename, $keyname, $keyvalue, $quotekey = TRUE, $deleteall = FALSE)
 {
        global $dbxlink;
        if ($quotekey == TRUE)
-               $query = "delete from ${tablename} where ${keyname}='$keyvalue' limit 1";
-       else
-               $query = "delete from ${tablename} where ${keyname}=$keyvalue limit 1";
+               $keyvalue = "'${keyvalue}'";
+       $query = "delete from ${tablename} where ${keyname}=$keyvalue";
+       if (!$deleteall)
+               $query .= ' limit 1';
        $result = $dbxlink->exec ($query);
        if ($result === NULL)
                return FALSE;
@@ -1902,17 +1846,23 @@ function useDeleteBlade ($tablename, $keyname, $keyvalue, $quotekey = TRUE)
                return TRUE;
 }
 
-function loadConfigCache ()
+function useSelectBlade ($query, $caller = 'N/A')
 {
        global $dbxlink;
-       $query = 'SELECT varname, varvalue, vartype, is_hidden, emptyok, description FROM Config ORDER BY varname';
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               $errorInfo = $dbxlink->errorInfo();
-               showError ("SQL query '${query}'\nwith message '${errorInfo[2]}'\nfailed in loadConfigCache()");
+               $ei = $dbxlink->errorInfo();
+               showError ("SQL query '${query}' failed in useSelectBlade with error ${ei[1]} (${ei[2]})", $caller);
                return NULL;
        }
+       return $result;
+}
+
+function loadConfigCache ()
+{
+       $query = 'SELECT varname, varvalue, vartype, is_hidden, emptyok, description FROM Config ORDER BY varname';
+       $result = useSelectBlade ($query);
        $cache = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                $cache[$row['varname']] = $row;
@@ -1926,21 +1876,21 @@ function storeConfigVar ($varname = NULL, $varvalue = NULL)
        global $dbxlink;
        if (empty ($varname) || $varvalue === NULL)
        {
-               showError ('Invalid arguments to storeConfigVar()');
+               showError ('Invalid arguments', __FUNCTION__);
                return FALSE;
        }
        $query = "update Config set varvalue='${varvalue}' where varname='${varname}' limit 1";
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
-               showError ("SQL query '${query}' failed in storeConfigVar()");
+               showError ("SQL query '${query}' failed", __FUNCTION__);
                return FALSE;
        }
        $rc = $result->rowCount();
        $result->closeCursor();
        if ($rc == 0 or $rc == 1)
                return TRUE;
-       showError ("Something went wrong in storeConfigVar('${varname}', '${varvalue}')");
+       showError ("Something went wrong for args '${varname}', '${varvalue}'", __FUNCTION__);
        return FALSE;
 }
 
@@ -1956,27 +1906,659 @@ function getDatabaseVersion ()
                $errorInfo = $dbxlink->errorInfo();
                if ($errorInfo[0] == '42S02') // ER_NO_SUCH_TABLE
                        return '0.14.4';
-               die ('SQL query #1 failed in getDatabaseVersion() with error ' . $errorInfo[2]);
+               die (__FUNCTION__ . ': SQL query #1 failed with error ' . $errorInfo[2]);
        }
        $rows = $result->fetchAll (PDO::FETCH_NUM);
        if (count ($rows) != 1 || empty ($rows[0][0]))
        {
                $result->closeCursor();
-               die ('Cannot guess database version. Config table is present, but DB_VERSION is missing or invalid. Giving up.');
+               die (__FUNCTION__ . ': Cannot guess database version. Config table is present, but DB_VERSION is missing or invalid. Giving up.');
        }
        $ret = $rows[0][0];
        $result->closeCursor();
        return $ret;
 }
 
+// Return an array of virtual services. For each of them list real server pools
+// with their load balancers and other stats.
 function getSLBSummary ()
 {
-       $query = 'select IPRealServer.vsid, IPVirtualService.vip as vip_bin, ' .
-               'inet_ntoa(IPVirtualService.vip) as vip, IPVirtualService.vport, ro.id as object_id, ' .
-               'count(rsid) as rscount from IPLBConfig inner join RackObject as ro on ro.id = object_id ' .
-               'inner join IPRealServer on IPLBConfig.rsid = IPRealServer.id ' .
-               'inner join IPVirtualService on IPRealServer.vsid = IPVirtualService.vsid ' .
-               'group by ro.id, IPVirtualService.vsid order by vip_bin, object_id';
+       $query = 'select vs.id as vsid, inet_ntoa(vip) as vip, vport, proto, vs.name, object_id, ' .
+               'lb.rspool_id, pool.name as pool_name, count(rs.id) as rscount ' .
+               'from IPVirtualService as vs inner join IPLoadBalancer as lb on vs.id = lb.vs_id ' .
+               'inner join IPRSPool as pool on lb.rspool_id = pool.id ' .
+               'left join IPRealServer as rs on rs.rspool_id = lb.rspool_id ' .
+               'group by vs.id, object_id order by vs.vip, object_id';
+       $result = useSelectBlade ($query);
+       $ret = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $vsid = $row['vsid'];
+               $object_id = $row['object_id'];
+               if (!isset ($ret[$vsid]))
+               {
+                       $ret[$vsid] = array();
+                       foreach (array ('vip', 'vport', 'proto', 'name') as $cname)
+                               $ret[$vsid][$cname] = $row[$cname];
+                       $ret[$vsid]['lblist'] = array();
+               }
+               // There's only one assigned RS pool possible for each LB-VS combination.
+               $ret[$vsid]['lblist'][$row['object_id']] = array
+               (
+                       'id' => $row['rspool_id'],
+                       'size' => $row['rscount'],
+                       'name' => $row['pool_name']
+               );
+       }
+       $result->closeCursor();
+       return $ret;
+}
+
+// Get the detailed composition of a particular virtual service, namely the list
+// of all pools, each shown with the list of objects servicing it. VS/RS configs
+// will be returned as well.
+function getVServiceInfo ($vsid = 0)
+{
+       $query1 = "select inet_ntoa(vip) as vip, vport, proto, name, vsconfig, rsconfig " .
+               "from IPVirtualService where id = ${vsid}";
+       $result = useSelectBlade ($query1);
+       $vsinfo = array ();
+       $row = $result->fetch (PDO::FETCH_ASSOC);
+       if (!$row)
+               return NULL;
+       foreach (array ('vip', 'vport', 'proto', 'name', 'vsconfig', 'rsconfig') as $cname)
+               $vsinfo[$cname] = $row[$cname];
+       $vsinfo['rspool'] = array();
+       $result->closeCursor();
+       unset ($result);
+       $query2 = "select pool.id, name, pool.vsconfig, pool.rsconfig, object_id, " .
+               "lb.vsconfig as lb_vsconfig, lb.rsconfig as lb_rsconfig from " .
+               "IPRSPool as pool left join IPLoadBalancer as lb on pool.id = lb.rspool_id " .
+               "where vs_id = ${vsid} order by pool.name, object_id";
+       $result = useSelectBlade ($query2);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               if (!isset ($vsinfo['rspool'][$row['id']]))
+               {
+                       $vsinfo['rspool'][$row['id']]['name'] = $row['name'];
+                       $vsinfo['rspool'][$row['id']]['vsconfig'] = $row['vsconfig'];
+                       $vsinfo['rspool'][$row['id']]['rsconfig'] = $row['rsconfig'];
+                       $vsinfo['rspool'][$row['id']]['lblist'] = array();
+               }
+               if ($row['object_id'] == NULL)
+                       continue;
+               $vsinfo['rspool'][$row['id']]['lblist'][$row['object_id']] = array
+               (
+                       'vsconfig' => $row['lb_vsconfig'],
+                       'rsconfig' => $row['lb_rsconfig']
+               );
+       }
+       $result->closeCursor();
+       return $vsinfo;
+}
+
+// Collect and return the following info about the given real server pool:
+// basic information
+// parent virtual service information
+// load balancers list (each with a list of VSes)
+// real servers list
+
+function getRSPoolInfo ($id = 0)
+{
+       $query1 = "select id, name, vsconfig, rsconfig from " .
+               "IPRSPool where id = ${id}";
+       $result = useSelectBlade ($query1);
+       $ret = array();
+       $row = $result->fetch (PDO::FETCH_ASSOC);
+       if (!$row)
+               return NULL;
+       foreach (array ('id', 'name', 'vsconfig', 'rsconfig') as $c)
+               $ret[$c] = $row[$c];
+       $result->closeCursor();
+       unset ($result);
+       $ret['lblist'] = array();
+       $ret['rslist'] = array();
+       $query2 = "select object_id, vs_id, vsconfig, rsconfig from IPLoadBalancer " .
+               "where rspool_id = ${id} order by object_id, vs_id";
+       $result = useSelectBlade ($query2);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               foreach (array ('vsconfig', 'rsconfig') as $c)
+                       $ret['lblist'][$row['object_id']][$row['vs_id']][$c] = $row[$c];
+       $result->closeCursor();
+       unset ($result);
+       $query3 = "select id, inservice, inet_ntoa(rsip) as rsip, rsport, rsconfig from " .
+               "IPRealServer where rspool_id = ${id} order by IPRealServer.rsip, rsport";
+       $result = useSelectBlade ($query3);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               foreach (array ('inservice', 'rsip', 'rsport', 'rsconfig') as $c)
+                       $ret['rslist'][$row['id']][$c] = $row[$c];
+       $result->closeCursor();
+       return $ret;
+}
+
+function addRStoRSPool ($pool_id = 0, $rsip = '', $rsport = 0, $inservice = 'no', $rsconfig = '')
+{
+       if ($pool_id <= 0 or $rsport <= 0)
+       {
+               showError ('Invalid arguments', __FUNCTION__);
+               die;
+       }
+       return useInsertBlade
+       (
+               'IPRealServer',
+               array
+               (
+                       'rsip' => "inet_aton('${rsip}')",
+                       'rsport' => $rsport,
+                       'rspool_id' => $pool_id,
+                       'inservice' => ($inservice == 'yes' ? "'yes'" : "'no'"),
+                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+               )
+       );
+}
+
+function commitCreateVS ($vip = '', $vport = 0, $proto = '', $name = '', $vsconfig, $rsconfig)
+{
+       if (empty ($vip) or $vport <= 0 or empty ($proto))
+       {
+               showError ('Invalid arguments', __FUNCTION__);
+               die;
+       }
+       return useInsertBlade
+       (
+               'IPVirtualService',
+               array
+               (
+                       'vip' => "inet_aton('${vip}')",
+                       'vport' => $vport,
+                       'proto' => "'${proto}'",
+                       'name' => (empty ($name) ? 'NULL' : "'${name}'"),
+                       'vsconfig' => (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
+                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+               )
+       );
+}
+
+function addLBtoRSPool ($pool_id = 0, $object_id = 0, $vs_id = 0, $vsconfig = '', $rsconfig = '')
+{
+       if ($pool_id <= 0 or $object_id <= 0 or $vs_id <= 0)
+       {
+               showError ('Invalid arguments', __FUNCTION__);
+               die;
+       }
+       return useInsertBlade
+       (
+               'IPLoadBalancer',
+               array
+               (
+                       'object_id' => $object_id,
+                       'rspool_id' => $pool_id,
+                       'vs_id' => $vs_id,
+                       'vsconfig' => (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
+                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+               )
+       );
+}
+
+function commitDeleteRS ($id = 0)
+{
+       if ($id <= 0)
+               return FALSE;
+       return useDeleteBlade ('IPRealServer', 'id', $id);
+}
+
+function commitDeleteVS ($id = 0)
+{
+       if ($id <= 0)
+               return FALSE;
+       return useDeleteBlade ('IPVirtualService', 'id', $id);
+}
+
+function commitDeleteLB ($object_id = 0, $pool_id = 0, $vs_id = 0)
+{
+       global $dbxlink;
+       if ($object_id <= 0 or $pool_id <= 0 or $vs_id <= 0)
+               return FALSE;
+       $query = "delete from IPLoadBalancer where object_id = ${object_id} and " .
+               "rspool_id = ${pool_id} and vs_id = ${vs_id} limit 1";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       elseif ($result != 1)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+function commitUpdateRS ($rsid = 0, $rsip = '', $rsport = 0, $rsconfig = '')
+{
+       if ($rsid <= 0 or $rsport <= 0)
+       {
+               showError ('Invalid args', __FUNCTION__);
+               die;
+       }
+       if (long2ip (ip2long ($rsip)) !== $rsip)
+       {
+               showError ("Invalid IP address '${rsip}'", __FUNCTION__);
+               die;
+       }
+       global $dbxlink;
+       $query =
+               "update IPRealServer set rsip = inet_aton('${rsip}'), rsport = ${rsport}, rsconfig = " .
+               (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               " where id = ${rsid} limit 1";
+       $result = $dbxlink->query ($query);
+       if ($result == NULL)
+       {
+               showError ("SQL query '${query}' failed", __FUNCTION__);
+               die;
+       }
+       return TRUE;
+}
+
+function commitUpdateLB ($object_id = 0, $pool_id = 0, $vs_id = 0, $vsconfig = '', $rsconfig = '')
+{
+       if ($object_id <= 0 or $pool_id <= 0 or $vs_id <= 0)
+       {
+               showError ('Invalid args', __FUNCTION__);
+               die;
+       }
+       global $dbxlink;
+       $query =
+               "update IPLoadBalancer set vsconfig = " .
+               (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'") .
+               ', rsconfig = ' .
+               (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               " where object_id = ${object_id} and rspool_id = ${pool_id} " .
+               "and vs_id = ${vs_id} limit 1";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+function commitUpdateVS ($vsid = 0, $vip = '', $vport = 0, $proto = '', $name = '', $vsconfig = '', $rsconfig = '')
+{
+       if ($vsid <= 0 or empty ($vip) or $vport <= 0 or empty ($proto))
+       {
+               showError ('Invalid args', __FUNCTION__);
+               die;
+       }
+       global $dbxlink;
+       $query = "update IPVirtualService set " .
+               "vip = inet_aton('${vip}'), " .
+               "vport = ${vport}, " .
+               "proto = '${proto}', " .
+               'name = ' . (empty ($name) ? 'NULL,' : "'${name}', ") .
+               'vsconfig = ' . (empty ($vsconfig) ? 'NULL,' : "'${vsconfig}', ") .
+               'rsconfig = ' . (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               " where id = ${vsid} limit 1";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+// Return the list of virtual services, indexed by vs_id.
+// Each record will be shown with its basic info plus RS pools counter.
+function getVSList ($tagfilter = array())
+{
+       if (!count ($tagfilter))
+               $whereclause = '';
+       else
+       {
+               $whereclause = 'where ';
+               $orclause = '';
+               foreach ($tagfilter as $tag_id)
+               {
+                       $whereclause .= $orclause . 'tag_id = ' . $tag_id;
+                       $orclause = ' or ';
+               }
+       }
+       $query = "select vs.id, inet_ntoa(vip) as vip, vport, proto, vs.name, vs.vsconfig, vs.rsconfig, count(rspool_id) as poolcount " .
+               "from IPVirtualService as vs left join IPLoadBalancer as lb on vs.id = lb.vs_id " .
+               "left join TagStorage on vs.id = TagStorage.target_id and target_realm = 'ipv4vs' " . 
+               "${whereclause} group by vs.id order by vs.vip, proto, vport";
+       $result = useSelectBlade ($query);
+       $ret = array ();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               foreach (array ('vip', 'vport', 'proto', 'name', 'vsconfig', 'rsconfig', 'poolcount') as $cname)
+                       $ret[$row['id']][$cname] = $row[$cname];
+       $result->closeCursor();
+       return $ret;
+}
+
+// Return the list of RS pool, indexed by pool id.
+function getRSPoolList ()
+{
+       $query = "select pool.id, pool.name, count(rspool_id) as refcnt, pool.vsconfig, pool.rsconfig " .
+               "from IPRSPool as pool left join IPLoadBalancer as lb on pool.id = lb.rspool_id " .
+               "group by pool.id order by pool.id, name";
+       $result = useSelectBlade ($query);
+       $ret = array ();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               foreach (array ('name', 'refcnt', 'vsconfig', 'rsconfig') as $cname)
+                       $ret[$row['id']][$cname] = $row[$cname];
+       $result->closeCursor();
+       return $ret;
+}
+
+function loadThumbCache ($rack_id = 0)
+{
+       $ret = NULL;
+       $query = "select thumb_data from Rack where id = ${rack_id} and thumb_data is not null limit 1";
+       $result = useSelectBlade ($query);
+       $row = $result->fetch (PDO::FETCH_ASSOC);
+       if ($row)
+               $ret = base64_decode ($row['thumb_data']);
+       $result->closeCursor();
+       return $ret;
+}
+
+function saveThumbCache ($rack_id = 0, $cache = NULL)
+{
+       global $dbxlink;
+       if ($rack_id == 0 or $cache == NULL)
+       {
+               showError ('Invalid arguments', __FUNCTION__);
+               return;
+       }
+       $data = base64_encode ($cache);
+       $query = "update Rack set thumb_data = '${data}' where id = ${rack_id} limit 1";
+       $result = $dbxlink->exec ($query);
+}
+
+function resetThumbCache ($rack_id = 0)
+{
+       global $dbxlink;
+       if ($rack_id == 0)
+       {
+               showError ('Invalid argument', __FUNCTION__);
+               return;
+       }
+       $query = "update Rack set thumb_data = NULL where id = ${rack_id} limit 1";
+       $result = $dbxlink->exec ($query);
+}
+
+// Return the list of attached RS pools for the given object. As long as we have
+// the LB-VS UNIQUE in IPLoadBalancer table, it is Ok to key returned records
+// by vs_id, because there will be only one RS pool listed for each VS of the
+// current object.
+function getRSPoolsForObject ($object_id = 0)
+{
+       if ($object_id <= 0)
+       {
+               showError ('Invalid object_id', __FUNCTION__);
+               return NULL;
+       }
+       $query = 'select vs_id, inet_ntoa(vip) as vip, vport, proto, vs.name, pool.id as pool_id, ' .
+               'pool.name as pool_name, count(rsip) as rscount, lb.vsconfig, lb.rsconfig from ' .
+               'IPLoadBalancer as lb inner join IPRSPool as pool on lb.rspool_id = pool.id ' .
+               'inner join IPVirtualService as vs on lb.vs_id = vs.id ' .
+               'left join IPRealServer as rs on lb.rspool_id = rs.rspool_id ' .
+               "where lb.object_id = ${object_id} " .
+               'group by lb.rspool_id, lb.vs_id order by vs.vip, vport, proto, pool.name';
+       $result = useSelectBlade ($query);
+       $ret = array ();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               foreach (array ('vip', 'vport', 'proto', 'name', 'pool_id', 'pool_name', 'rscount', 'vsconfig', 'rsconfig') as $cname)
+                       $ret[$row['vs_id']][$cname] = $row[$cname];
+       $result->closeCursor();
+       return $ret;
+}
+
+function commitCreateRSPool ($name = '', $vsconfig = '', $rsconfig = '')
+{
+       return useInsertBlade
+       (
+               'IPRSPool',
+               array
+               (
+                       'name' => (empty ($name) ? 'NULL' : "'${name}'"),
+                       'vsconfig' => (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
+                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+               )
+       );
+}
+
+function commitDeleteRSPool ($pool_id = 0)
+{
+       global $dbxlink;
+       if ($pool_id <= 0)
+               return FALSE;
+       $query = "delete from IPRSPool where id = ${pool_id} limit 1";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       elseif ($result != 1)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+function commitUpdateRSPool ($pool_id = 0, $name = '', $vsconfig = '', $rsconfig = '')
+{
+       if ($pool_id <= 0)
+       {
+               showError ('Invalid arg', __FUNCTION__);
+               die;
+       }
+       global $dbxlink;
+       $query = "update IPRSPool set " .
+               'name = ' . (empty ($name) ? 'NULL,' : "'${name}', ") .
+               'vsconfig = ' . (empty ($vsconfig) ? 'NULL,' : "'${vsconfig}', ") .
+               'rsconfig = ' . (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               " where id = ${pool_id} limit 1";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       elseif ($result != 1)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+function getRSList ()
+{
+       $query = "select id, inservice, inet_ntoa(rsip) as rsip, rsport, rspool_id, rsconfig " .
+               "from IPRealServer order by rspool_id, IPRealServer.rsip, rsport";
+       $result = useSelectBlade ($query);
+       $ret = array ();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               foreach (array ('inservice', 'rsip', 'rsport', 'rspool_id', 'rsconfig') as $cname)
+                       $ret[$row['id']][$cname] = $row[$cname];
+       $result->closeCursor();
+       return $ret;
+}
+
+// Return the list of all currently configured load balancers with their pool count.
+function getLBList ()
+{
+       $query = "select object_id, count(rspool_id) as poolcount " .
+               "from IPLoadBalancer group by object_id order by object_id";
+       $result = useSelectBlade ($query);
+       $ret = array ();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[$row['object_id']] = $row['poolcount'];
+       $result->closeCursor();
+       return $ret;
+}
+
+// For the given object return: it vsconfig/rsconfig; the list of RS pools
+// attached (each with vsconfig/rsconfig in turn), each with the list of
+// virtual services terminating the pool. Each pool also lists all real
+// servers with rsconfig.
+function buildLBConfig ($object_id)
+{
+       if ($object_id <= 0)
+       {
+               showError ('Invalid arg', __FUNCTION__);
+               return NULL;
+       }
+       $ret = array();
+       $query = 'select vs_id, inet_ntoa(vip) as vip, vport, proto, vs.name as vs_name, ' .
+               'vs.vsconfig as vs_vsconfig, vs.rsconfig as vs_rsconfig, ' .
+               'lb.vsconfig as lb_vsconfig, lb.rsconfig as lb_rsconfig, pool.id as pool_id, pool.name as pool_name, ' .
+               'pool.vsconfig as pool_vsconfig, pool.rsconfig as pool_rsconfig, ' .
+               'rs.id as rs_id, inet_ntoa(rsip) as rsip, rsport, rs.rsconfig as rs_rsconfig from ' .
+               'IPLoadBalancer as lb inner join IPRSPool as pool on lb.rspool_id = pool.id ' .
+               'inner join IPVirtualService as vs on lb.vs_id = vs.id ' .
+               'inner join IPRealServer as rs on lb.rspool_id = rs.rspool_id ' .
+               "where lb.object_id = ${object_id} and rs.inservice = 'yes' " .
+               "order by vs.vip, vport, proto, pool.name, rs.rsip, rs.rsport";
+       $result = useSelectBlade ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $vs_id = $row['vs_id'];
+               if (!isset ($ret[$vs_id]))
+               {
+                       foreach (array ('vip', 'vport', 'proto', 'vs_name', 'vs_vsconfig', 'vs_rsconfig', 'lb_vsconfig', 'lb_rsconfig', 'pool_vsconfig', 'pool_rsconfig', 'pool_id', 'pool_name') as $c)
+                               $ret[$vs_id][$c] = $row[$c];
+                       $ret[$vs_id]['rslist'] = array();
+               }
+               foreach (array ('rsip', 'rsport', 'rs_rsconfig') as $c)
+                       $ret[$vs_id]['rslist'][$row['rs_id']][$c] = $row[$c];
+       }
+       $result->closeCursor();
+       return $ret;
+}
+
+function commitSetInService ($rs_id = 0, $inservice = '')
+{
+       if ($rs_id <= 0 or empty ($inservice))
+       {
+               showError ('Invalid args', __FUNCTION__);
+               return NULL;
+       }
+       global $dbxlink;
+       $query = "update IPRealServer set inservice = '${inservice}' where id = ${rs_id} limit 1";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       elseif ($result != 1)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+function executeAutoPorts ($object_id = 0, $type_id = 0)
+{
+       if ($object_id == 0 or $type_id == 0)
+       {
+               showError ('Invalid arguments', __FUNCTION__);
+               die;
+       }
+       $ret = TRUE;
+       foreach (getAutoPorts ($type_id) as $autoport)
+               $ret = $ret and '' == commitAddPort ($object_id, $autoport['name'], $autoport['type'], '', '');
+       return $ret;
+}
+
+// Return only implicitly listed tags, the rest of the trail will be
+// generated/deducted later at higher levels.
+function loadEntityTags ($entity_realm = '', $entity_id = 0)
+{
+       if ($entity_realm == '' or $entity_id <= 0)
+       {
+               showError ('Invalid or missing arguments', __FUNCTION__);
+               return NULL;
+       }
+       $ret = array();
+       $result = useSelectBlade ("select tt.id, tag from TagStorage as ts inner join TagTree as tt on ts.tag_id = tt.id where target_realm = '${entity_realm}' and target_id = ${entity_id}");
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[$row['id']] = $row;
+       $result->closeCursor();
+       return $ret;
+}
+
+function loadRackObjectTags ($id)
+{
+       return loadEntityTags ('object', $id);
+}
+
+function loadIPv4PrefixTags ($id)
+{
+       return loadEntityTags ('ipv4net', $id);
+}
+
+function loadRackTags ($id)
+{
+       return loadEntityTags ('rack', $id);
+}
+
+function loadIPv4VSTags ($id)
+{
+       return loadEntityTags ('ipv4vs', $id);
+}
+
+function loadIPv4RSPoolTags ($id)
+{
+       return loadEntityTags ('ipv4rspool', $id);
+}
+
+function getTagList ()
+{
+       $ret = array();
+       $query = "select id, parent_id, tag, target_realm as realm, count(target_id) as refcnt " .
+               "from TagTree left join TagStorage on id = tag_id " .
+               "group by id, target_realm order by tag";
+       $result = useSelectBlade ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               if (!isset ($ret[$row['id']]))
+                       $ret[$row['id']] = array
+                       (
+                               'id' => $row['id'],
+                               'tag' => $row['tag'],
+                               'parent_id' => $row['parent_id'],
+                               'refcnt' => array()
+                       );
+               if ($row['realm'])
+                       $ret[$row['id']]['refcnt'][$row['realm']] = $row['refcnt'];
+       }
+       $result->closeCursor();
+       return $ret;
+}
+
+function commitCreateTag ($tagname = '', $parent_id = 0)
+{
+       if ($tagname == '' or $parent_id === 0)
+               return "Invalid args to " . __FUNCTION__;
+       $result = useInsertBlade
+       (
+               'TagTree',
+               array
+               (
+                       'tag' => "'${tagname}'",
+                       'parent_id' => $parent_id
+               )
+       );
+       if ($result)
+               return '';
+       else
+               return "SQL query failed in " . __FUNCTION__;
+}
+
+function commitDestroyTag ($tagid = 0)
+{
+       if ($tagid == 0)
+               return 'Invalid arg to ' . __FUNCTION__;
+       if (useDeleteBlade ('TagTree', 'id', $tagid, FALSE))
+               return '';
+       else
+               return 'useDeleteBlade() failed in ' . __FUNCTION__;
+}
+
+function wipeTags ($realm, $id)
+{
+       global $dbxlink;
+       $query = "delete from TagStorage where target_realm = '${realm}' and target_id = ${id}";
+       $result = $dbxlink->exec ($query);
+       if ($result === NULL)
+               return FALSE;
+       return TRUE;
 }
 
 ?>