Switch to using a different db cursor type for full-row lookups

The only user of full-row database lookups is the admin interface,
dumping information from various tables.

Using the default cursor type of NamedTupleCursor is fine when we're
returning one or two items, but is rubbish when we're grabbing an
entire row. On the other side of the IPC layer, the names in the named
tuple are lost and we have to track fields by hand using just
positional logic.

Instead, add a second cursor using RealDictCursor for full-row
lookups. That passes through the IPC layer cleanly and we can refer to
the dict entries by name which is much cleaner.

Change-Id: I6076d4d75dd657e804320f77e0d9ac0cc9aae9ba
diff --git a/admin.py b/admin.py
index 706d24b..e710d36 100755
--- a/admin.py
+++ b/admin.py
@@ -47,34 +47,34 @@
 
 def dump_switch(switch):
     print "switch_id:%d name:%s" % (
-        int(switch[0]),
-        switch[1])
+        int(switch['switch_id']),
+        switch['name'])
 
 def dump_port(port):
     print "port_id:%d name:%s switch_id:%d locked:%s mode:%s base_vlan_id:%d current_vlan_id:%d number:%d trunk_id:%s lock_reason:%s" % (
-        int(port[0]),
-        port[1],
-        int(port[2]),
-        ("yes" if port[3] is True else "no"),
-        ("trunk" if port[4] is True else "access"),
-        int(port[5]),
-        int(port[6]),
-        int(port[7]),
-        'None' if (TRUNK_ID_NONE == port[8]) else port[8],
-        port[9])
+        int(port['port_id']),
+        port['name'],
+        int(port['switch_id']),
+        ("yes" if port['is_locked'] is True else "no"),
+        ("trunk" if port['is_trunk'] is True else "access"),
+        int(port['base_vlan_id']),
+        int(port['current_vlan_id']),
+        int(port['number']),
+        'None' if (TRUNK_ID_NONE == port['trunk_id']) else port['trunk_id'],
+        port['lock_reason'])
 
 def dump_vlan(vlan):
     print "vlan_id:%d name:%s tag:%d is_base_vlan:%s, creation_time:%s" % (
-        int(vlan[0]),
-        vlan[1],
-        int(vlan[2]),
-        ("yes" if vlan[3] is True else "no"),
-        vlan[4])
+        int(vlan['vlan_id']),
+        vlan['name'],
+        int(vlan['tag']),
+        ("yes" if vlan['is_base_vlan'] is True else "no"),
+        vlan['creation_time'])
 
 def dump_trunk(trunk):
     print "trunk_id:%d creation_time:%s" % (
-        int(trunk[0]),
-        trunk[1])
+        int(trunk['trunk_id']),
+        trunk['creation_time'])
 
 def call_vland(msgtype, msg):
     ipc = VlanIpc()
diff --git a/db/db.py b/db/db.py
index 003f973..82cd633 100644
--- a/db/db.py
+++ b/db/db.py
@@ -48,7 +48,11 @@
     def __init__(self, db_name="vland", username="vland", readonly=True):
         try:
             self.connection = psycopg2.connect(database=db_name, user=username)
+            # Create first cursor for normal usage - returns tuples
             self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor)
+            # Create second cursor for full-row lookups - returns a dict
+            # instead, much more useful in the admin interface
+            self.dictcursor = self.connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
             if not readonly:
                 self._init_state()
         except Exception as e:
@@ -57,6 +61,7 @@
 
     def __del__(self):
         self.cursor.close()
+        self.dictcursor.close()
         self.connection.close()
 
     # Create the state table (if needed) and add its only record
@@ -637,8 +642,8 @@
         # for its own substitution code. We force this with the
         # trailing comma here
         data = (value, )
-        self.cursor.execute(sql, data)
-        return self.cursor.fetchone()
+        self.dictcursor.execute(sql, data)
+        return self.dictcursor.fetchone()
 
     # (Un)Lock a port in the database. This can only be done through
     # the admin interface, and will stop API users from modifying
@@ -776,11 +781,11 @@
     # Trivial helper function to return all the rows in a given table
     def _dump_table(self, table, order):
         result = []
-        self.cursor.execute("SELECT * FROM %s ORDER by %s ASC" % (table, order))
-        record = self.cursor.fetchone()
+        self.dictcursor.execute("SELECT * FROM %s ORDER by %s ASC" % (table, order))
+        record = self.dictcursor.fetchone()
         while record != None:
             result.append(record)
-            record = self.cursor.fetchone()
+            record = self.dictcursor.fetchone()
         return result
 
     def all_switches(self):