Edgewall Software

Ticket #3067: session3.patch

File session3.patch, 23.3 kB (added by jonas, 2 years ago)

A new version of the patch with a more normalized table schema

  • trac/env.py

     
    273273        if not cnx: 
    274274            cnx = self.get_db_cnx() 
    275275        cursor = cnx.cursor() 
    276         cursor.execute("SELECT DISTINCT s.sid, n.var_value, e.var_value " 
     276        cursor.execute("SELECT DISTINCT s.sid, n.value, e.value " 
    277277                       "FROM session AS s " 
    278                        " LEFT JOIN session AS n ON (n.sid=s.sid " 
    279                        "  AND n.authenticated=1 AND n.var_name = 'name') " 
    280                        " LEFT JOIN session AS e ON (e.sid=s.sid " 
    281                        "  AND e.authenticated=1 AND e.var_name = 'email') " 
     278                       " LEFT JOIN session_attribute AS n ON (n.sid=s.sid " 
     279                       "  and n.authenticated=1 AND n.name = 'name') " 
     280                       " LEFT JOIN session_attribute AS e ON (e.sid=s.sid " 
     281                       "  AND e.authenticated=1 AND e.name = 'email') " 
    282282                       "WHERE s.authenticated=1 ORDER BY s.sid") 
    283283        for username,name,email in cursor: 
    284284            yield username, name, email 
  • trac/db_default.py

     
    1717from trac.db import Table, Column, Index 
    1818 
    1919# Database version identifier. Used for automatic upgrades. 
    20 db_version = 17 
     20db_version = 18 
    2121 
    2222def __mkreports(reports): 
    2323    """Utility function used to create report data in same syntax as the 
     
    4646        Column('name'), 
    4747        Column('ipnr'), 
    4848        Column('time', type='int')], 
    49     Table('session', key=('sid', 'authenticated', 'var_name'))[ 
     49    Table('session', key=('sid', 'authenticated'))[ 
    5050        Column('sid'), 
    5151        Column('authenticated', type='int'), 
    52         Column('var_name'), 
    53         Column('var_value')], 
     52        Column('last_visit', type='int'), 
     53        Index(['last_visit']), 
     54        Index(['authenticated'])], 
     55    Table('session_attribute', key=('sid', 'authenticated', 'name'))[ 
     56        Column('sid'), 
     57        Column('authenticated', type='int'), 
     58        Column('name'), 
     59        Column('value')], 
    5460 
    5561    # Attachments 
    5662    Table('attachment', key=('type', 'id', 'filename'))[ 
     
    119125        Column('field'), 
    120126        Column('oldvalue'), 
    121127        Column('newvalue'), 
    122         Index(['ticket', 'time'])], 
     128        Index(['ticket']), 
     129        Index(['time'])], 
    123130    Table('ticket_custom', key=('ticket', 'name'))[ 
    124131        Column('ticket', type='int'), 
    125132        Column('name'), 
  • trac/tests/env.py

     
    2626    def test_get_known_users(self): 
    2727        """Testing env.get_known_users""" 
    2828        cursor = self.db.cursor() 
    29         cursor.execute("INSERT INTO session " 
    30                        "VALUES ('123',0,'email','a@example.com')") 
    31         cursor.executemany("INSERT INTO session VALUES (%s,1,%s,%s)", 
    32                            [('tom', 'name', 'Tom'), 
    33                             ('tom', 'email', 'tom@example.com'), 
    34                             ('joe', 'email', 'joe@example.com'), 
    35                             ('jane', 'name', 'Jane')]) 
     29        cursor.executemany("INSERT INTO session VALUES (%s,%s,0)", 
     30                           [('123', 0),('tom', 1), ('joe', 1), ('jane', 1)]) 
     31        cursor.executemany("INSERT INTO session_attribute VALUES (%s,%s,%s,%s)", 
     32                           [('123', 0, 'email', 'a@example.com'), 
     33                            ('tom', 1, 'name', 'Tom'), 
     34                            ('tom', 1, 'email', 'tom@example.com'), 
     35                            ('joe', 1, 'email', 'joe@example.com'), 
     36                            ('jane', 1, 'name', 'Jane')]) 
    3637        users = {} 
    3738        for username,name,email in self.env.get_known_users(self.db): 
    3839            users[username] = (name, email) 
  • trac/upgrades/db18.py

     
     1from trac.db import Table, Column, Index, DatabaseManager 
     2 
     3def do_upgrade(env, ver, cursor): 
     4    cursor.execute("CREATE TEMP TABLE session_old AS SELECT * FROM session") 
     5    cursor.execute("DROP TABLE session") 
     6    cursor.execute("CREATE TEMP TABLE ticket_change_old AS SELECT * FROM ticket_change") 
     7    cursor.execute("DROP TABLE ticket_change") 
     8 
     9    # A slightly more denormalized session schema where the 'last_visit' values are 
     10    # stored in a column for performance reasons 
     11    tables = [Table('session', key=('sid', 'authenticated'))[ 
     12                Column('sid'), 
     13                Column('authenticated', type='int'), 
     14                Column('last_visit', type='int'), 
     15                Index(['last_visit']), 
     16                Index(['authenticated'])], 
     17              Table('session_attribute', key=('sid', 'authenticated', 'name'))[ 
     18                Column('sid'), 
     19                Column('authenticated', type='int'), 
     20                Column('name'), 
     21                Column('value')], 
     22              Table('ticket_change', key=('ticket', 'time', 'field'))[ 
     23                Column('ticket', type='int'), 
     24                Column('time', type='int'), 
     25                Column('author'), 
     26                Column('field'), 
     27                Column('oldvalue'), 
     28                Column('newvalue'), 
     29                Index(['ticket']), 
     30                Index(['time'])]] 
     31     
     32    db_connector, _ = DatabaseManager(env)._get_connector() 
     33    for table in tables: 
     34        for stmt in db_connector.to_sql(table): 
     35            cursor.execute(stmt) 
     36 
     37    # Add an index to the temporary table to speed up the conversion 
     38    cursor.execute("CREATE INDEX session_old_sid_idx ON session_old(sid)") 
     39    # Insert the sessions into the new table 
     40    db = env.get_db_cnx() 
     41    cursor.execute("INSERT INTO session (sid, last_visit, authenticated) " 
     42                   "SELECT distinct s.sid,COALESCE(%s,0),s.authenticated " 
     43                   "FROM session_old AS s LEFT JOIN session_old AS s2 " 
     44                   "ON (s.sid=s2.sid AND s2.var_name='last_visit') " 
     45                   "WHERE s.sid IS NOT NULL" 
     46                   % db.cast('s2.var_value', 'int')) 
     47    cursor.execute("INSERT INTO session_attribute " 
     48                   "(sid, authenticated, name, value) " 
     49                   "SELECT s.sid, s.authenticated, s.var_name, s.var_value " 
     50                   "FROM session_old s " 
     51                   "WHERE s.var_name <> 'last_visit' AND s.sid IS NOT NULL") 
     52 
     53    # Insert ticket change data into the new table 
     54    cursor.execute("INSERT INTO ticket_change " 
     55                   "(ticket, time, author, field, oldvalue, newvalue) " 
     56                   "SELECT ticket, time, author, field, oldvalue, newvalue " 
     57                   "FROM ticket_change_old") 
     58 
     59 
  • trac/web/tests/session.py

     
    6565        authenticated session when the user logs in. 
    6666        """ 
    6767        cursor = self.db.cursor() 
    68         cursor.execute("INSERT INTO session VALUES ('123456', 0, 'foo', 'bar')") 
    69  
     68        cursor.execute("INSERT INTO session VALUES ('123456', 0, 0)") 
    7069        incookie = Cookie() 
    7170        incookie['trac_session'] = '123456' 
    7271        outcookie = Cookie() 
     
    9392        session['foo'] = 'bar' 
    9493        session.save() 
    9594        cursor = self.db.cursor() 
    96         cursor.execute("SELECT var_value FROM session WHERE sid='123456' AND " 
    97                        "authenticated=0 AND var_name='foo'")  
     95        cursor.execute("SELECT value FROM session_attribute WHERE sid='123456'") 
    9896        self.assertEqual('bar', cursor.fetchone()[0]) 
    9997 
    10098    def test_modify_anonymous_session_var(self): 
     
    103101        accordingly for an anonymous session. 
    104102        """ 
    105103        cursor = self.db.cursor() 
    106         cursor.execute("INSERT INTO session VALUES ('123456', 0, 'foo', 'bar')") 
    107  
     104        cursor.execute("INSERT INTO session VALUES ('123456', 0, 0)") 
     105        cursor.execute("INSERT INTO session_attribute VALUES " 
     106                       "('123456', 0, 'foo', 'bar')") 
    108107        incookie = Cookie() 
    109108        incookie['trac_session'] = '123456' 
    110109        req = Mock(authname='anonymous', base_path='/', incookie=incookie, 
     
    113112        self.assertEqual('bar', session['foo']) 
    114113        session['foo'] = 'baz' 
    115114        session.save() 
    116         cursor.execute("SELECT var_value FROM session WHERE sid='123456' AND " 
    117                        "authenticated=0 AND var_name='foo'")  
     115        cursor.execute("SELECT value FROM session_attribute WHERE sid='123456'") 
    118116        self.assertEqual('baz', cursor.fetchone()[0]) 
    119117 
    120118    def test_delete_anonymous_session_var(self): 
     
    123121        for an anonymous session. 
    124122        """ 
    125123        cursor = self.db.cursor() 
    126         cursor.execute("INSERT INTO session VALUES ('123456', 0, 'foo', 'bar')") 
    127  
     124        cursor.execute("INSERT INTO session VALUES ('123456', 0, 0)") 
     125        cursor.execute("INSERT INTO session_attribute VALUES " 
     126                       "('123456', 0, 'foo', 'bar')") 
    128127        incookie = Cookie() 
    129128        incookie['trac_session'] = '123456' 
    130129        req = Mock(authname='anonymous', base_path='/', incookie=incookie, 
     
    133132        self.assertEqual('bar', session['foo']) 
    134133        del session['foo'] 
    135134        session.save() 
    136         cursor.execute("SELECT COUNT(*) FROM session WHERE sid='123456' AND " 
    137                        "authenticated=0 AND var_name='foo'")  
     135        cursor.execute("SELECT COUNT(*) FROM session_attribute " 
     136                       "WHERE sid='123456' AND name='foo'")  
    138137        self.assertEqual(0, cursor.fetchone()[0]) 
    139138 
    140139    def test_purge_anonymous_session(self): 
     
    143142        """ 
    144143        cursor = self.db.cursor() 
    145144        cursor.execute("INSERT INTO session " 
    146                        "VALUES ('987654', 0, 'last_visit', %s)", 
     145                       "VALUES ('987654', 0, %s)", 
    147146                       (time.time() - PURGE_AGE - 3600,)) 
     147        cursor.execute("INSERT INTO session_attribute VALUES " 
     148                       "('987654', 0, 'foo', 'bar')") 
    148149         
    149150        # We need to modify a different session to trigger the purging 
    150151        incookie = Cookie() 
     
    169170        # Make sure the session has data so that it doesn't get dropped 
    170171        cursor = self.db.cursor() 
    171172        cursor.execute("INSERT INTO session " 
    172                        "VALUES ('123456', 0, 'last_visit', %s)", 
     173                       "VALUES ('123456', 0, %s)", 
    173174                       (int(now - UPDATE_INTERVAL - 3600),)) 
     175        cursor.execute("INSERT INTO session_attribute VALUES " 
     176                       "('123456', 0, 'foo', 'bar')") 
    174177 
    175178        incookie = Cookie() 
    176179        incookie['trac_session'] = '123456' 
    177180        req = Mock(authname='anonymous', base_path='/', incookie=incookie, 
    178181                   outcookie=Cookie()) 
    179182        session = Session(self.env, req) 
     183        del session['foo'] 
    180184        session.save() 
    181185 
    182186        cursor.execute("SELECT COUNT(*) FROM session WHERE sid='123456' AND " 
     
    193197        session['foo'] = 'bar' 
    194198        session.save() 
    195199        cursor = self.db.cursor() 
    196         cursor.execute("SELECT var_value FROM session WHERE sid='john' AND " 
    197                        "authenticated=1 AND var_name='foo'")  
     200        cursor.execute("SELECT value FROM session_attribute WHERE sid='john'" 
     201                       "AND name='foo'")  
    198202        self.assertEqual('bar', cursor.fetchone()[0]) 
    199203 
    200204    def test_modify_authenticated_session_var(self): 
     
    203207        accordingly for an authenticated session. 
    204208        """ 
    205209        cursor = self.db.cursor() 
    206         cursor.execute("INSERT INTO session VALUES ('john', 1, 'foo', 'bar')") 
     210        cursor.execute("INSERT INTO session VALUES ('john', 1, 0)") 
     211        cursor.execute("INSERT INTO session_attribute VALUES " 
     212                       "('john', 1, 'foo', 'bar')") 
    207213 
    208214        req = Mock(authname='john', base_path='/', incookie=Cookie()) 
    209215        session = Session(self.env, req) 
    210216        self.assertEqual('bar', session['foo']) 
    211217        session['foo'] = 'baz' 
    212218        session.save() 
    213         cursor.execute("SELECT var_value FROM session WHERE sid='john' AND " 
    214                        "authenticated=1 AND var_name='foo'")  
     219        cursor.execute("SELECT value FROM session_attribute " 
     220                       "WHERE sid='john' AND name='foo'")  
    215221        self.assertEqual('baz', cursor.fetchone()[0]) 
    216222 
    217223    def test_delete_authenticated_session_var(self): 
     
    220226        for an authenticated session. 
    221227        """ 
    222228        cursor = self.db.cursor() 
    223         cursor.execute("INSERT INTO session VALUES ('john', 1, 'foo', 'bar')") 
     229        cursor.execute("INSERT INTO session VALUES ('john', 1, 0)") 
     230        cursor.execute("INSERT INTO session_attribute VALUES " 
     231                       "('john', 1, 'foo', 'bar')") 
    224232 
    225233        req = Mock(authname='john', base_path='/', incookie=Cookie()) 
    226234        session = Session(self.env, req) 
    227235        self.assertEqual('bar', session['foo']) 
    228236        del session['foo'] 
    229237        session.save() 
    230         cursor.execute("SELECT COUNT(*) FROM session WHERE sid='john' AND " 
    231                        "authenticated=1 AND var_name='foo'")  
     238        cursor.execute("SELECT COUNT(*) FROM session_attribute " 
     239                       "WHERE sid='john' AND name='foo'")  
    232240        self.assertEqual(0, cursor.fetchone()[0]) 
    233241 
    234242    def test_update_session(self): 
     
    240248 
    241249        # Make sure the session has data so that it doesn't get dropped 
    242250        cursor = self.db.cursor() 
    243         cursor.executemany("INSERT INTO session VALUES ('123456', 0, %s, %s)", 
    244                            [('last_visit', int(now - UPDATE_INTERVAL - 3600)), 
    245                             ('foo', 'bar')]) 
     251        cursor.execute("INSERT INTO session VALUES ('123456', 0, 1)") 
     252        cursor.execute("INSERT INTO session_attribute VALUES " 
     253                       "('123456', 0, 'foo', 'bar')") 
    246254 
    247255        incookie = Cookie() 
    248256        incookie['trac_session'] = '123456' 
     
    254262 
    255263        self.assertEqual(PURGE_AGE, outcookie['trac_session']['expires']) 
    256264 
    257         cursor.execute("SELECT var_value FROM session WHERE sid='123456' AND " 
    258                        "authenticated=0 AND var_name='last_visit'") 
     265        cursor.execute("SELECT last_visit FROM session WHERE sid='123456' AND " 
     266                       "authenticated=0") 
    259267        self.assertAlmostEqual(now, int(cursor.fetchone()[0]), -1) 
    260268 
    261269 
  • trac/web/session.py

     
    33# Copyright (C) 2004-2005 Edgewall Software 
    44# Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com> 
    55# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de> 
     6# Copyright (C) 2006 Jonas Borgström <jonas@edgewall.com> 
    67# All rights reserved. 
    78# 
    89# This software is licensed as described in the file COPYING, which 
     
    3435        self.env = env 
    3536        self.req = req 
    3637        self.sid = None 
     38        self.last_visit = 0 
     39        self._new = True 
    3740        self._old = {} 
    3841        if req.authname == 'anonymous': 
    3942            if not req.incookie.has_key(COOKIE_KEY): 
     
    5861        db = self.env.get_db_cnx() 
    5962        cursor = db.cursor() 
    6063        self.sid = sid 
    61         cursor.execute("SELECT var_name,var_value FROM session " 
     64        cursor.execute("SELECT last_visit FROM session " 
    6265                       "WHERE sid=%s AND authenticated=%s", 
    6366                       (sid, int(authenticated))) 
     67        row = cursor.fetchone() 
     68        if not row: 
     69            return 
     70        self._new = False 
     71        self.last_visit = int(row[0]) 
     72        cursor.execute("SELECT name,value FROM session_attribute " 
     73                       "WHERE sid=%s and authenticated=%s", 
     74                       (sid, int(authenticated))) 
    6475        for name, value in cursor: 
    6576            self[name] = value 
    6677        self._old.update(self) 
    6778 
    6879        # Refresh the session cookie if this is the first visit since over a day 
    69         if not authenticated and self.has_key('last_visit'): 
    70             if time.time() - int(self['last_visit']) > UPDATE_INTERVAL: 
     80        if not authenticated and self.last_visit: 
     81            if time.time() - self.last_visit > UPDATE_INTERVAL: 
    7182                self.bake_cookie() 
    7283 
    7384    def change_sid(self, new_sid): 
     
    7889            return 
    7990        db = self.env.get_db_cnx() 
    8091        cursor = db.cursor() 
    81         cursor.execute("SELECT sid FROM session WHERE sid=%s " 
    82                        "AND authenticated=0", (new_sid,)) 
     92        cursor.execute("SELECT sid FROM session WHERE sid=%s", (new_sid,)) 
    8393        if cursor.fetchone(): 
    8494            raise TracError(Markup('Session "%s" already exists.<br />' 
    8595                                   'Please choose a different session ID.', 
     
    8797        self.env.log.debug('Changing session ID %s to %s' % (self.sid, new_sid)) 
    8898        cursor.execute("UPDATE session SET sid=%s WHERE sid=%s " 
    8999                       "AND authenticated=0", (new_sid, self.sid)) 
     100        cursor.execute("UPDATE session_attribute SET sid=%s " 
     101                       "WHERE sid=%s and authenticated=0", 
     102                       (new_sid, self.sid)) 
    90103        db.commit() 
    91104        self.sid = new_sid 
    92105        self.bake_cookie() 
     
    107120            # simply delete the anonymous session 
    108121            cursor.execute("DELETE FROM session WHERE sid=%s " 
    109122                           "AND authenticated=0", (sid,)) 
     123            cursor.execute("DELETE FROM session_attribute WHERE sid=%s " 
     124                           "AND authenticated=0", (sid,)) 
    110125        else: 
    111126            # Otherwise, update the session records so that the session ID is 
    112127            # the user name, and the authenticated flag is set 
     
    116131            cursor.execute("UPDATE session SET sid=%s,authenticated=1 " 
    117132                           "WHERE sid=%s AND authenticated=0", 
    118133                           (self.req.authname, sid)) 
     134            cursor.execute("UPDATE session_attribute SET sid=%s," 
     135                           "authenticated=1 WHERE sid=%s", 
     136                           (self.req.authname, sid)) 
     137        self._new = False 
    119138        db.commit() 
    120139 
    121140        self.sid = sid 
     
    127146            # persist it 
    128147            return 
    129148 
    130         changed = False 
    131         now = int(time.time()) 
    132  
    133         if self.req.authname == 'anonymous': 
    134             # Update the session last visit time if it is over an hour old, 
    135             # so that session doesn't get purged 
    136             last_visit = int(self.get('last_visit', 0)) 
    137             if now - last_visit > UPDATE_INTERVAL: 
    138                 self.env.log.info("Refreshing session %s" % self.sid) 
    139                 self['last_visit'] = now 
    140  
    141             # If the only data in the session is the last_visit time, it makes 
    142             # no sense to keep the session around 
    143             if len(self.items()) == 1: 
    144                 del self['last_visit'] 
    145  
    146149        db = self.env.get_db_cnx() 
    147150        cursor = db.cursor() 
    148151        authenticated = int(self.req.authname != 'anonymous') 
    149152 
    150         # Find all new or modified session variables and persist their values to 
    151         # the database 
    152         for k,v in self.items(): 
    153             if not self._old.has_key(k): 
    154                 self.env.log.debug('Adding variable %s with value "%s" to ' 
    155                                    'session %s' % (k, v, 
    156                                    self.sid or self.req.authname)) 
    157                 cursor.execute("INSERT INTO session VALUES(%s,%s,%s,%s)", 
    158                                (self.sid, authenticated, k, v)) 
    159                 changed = True 
    160             elif v != self._old[k]: 
    161                 self.env.log.debug('Changing variable %s from "%s" to "%s" in ' 
    162                                    'session %s' % (k, self._old[k], v, 
    163                                    self.sid)) 
    164                 cursor.execute("UPDATE session SET var_value=%s " 
    165                                "WHERE sid=%s AND authenticated=%s " 
    166                                "AND var_name=%s", (v, self.sid, authenticated, 
    167                                k)) 
    168                 changed = True 
     153        if self._new: 
     154            self._new = False 
     155            cursor.execute("INSERT INTO session (sid,last_visit,authenticated) " 
     156                           "VALUES(%s,%s,%s)", (self.sid, 
     157                                                self.last_visit, 
     158                                                authenticated)) 
     159        if self._old.items() != self.items(): 
     160            attrs = [(self.sid, authenticated, k, v) for k, v in self.items()] 
     161            cursor.execute("DELETE FROM session_attribute WHERE sid=%s", 
     162                           (self.sid,)) 
     163            self._old = dict(self.items()) 
     164            if attrs: 
     165                cursor.executemany("INSERT INTO session_attribute " 
     166                                   "(sid,authenticated,name,value) " 
     167                                   "VALUES(%s,%s,%s,%s)", attrs) 
     168            elif not authenticated: 
     169                # No need to keep around empty unauthenticated sessions 
     170                cursor.execute("DELETE FROM session " 
     171                               "WHERE sid=%s AND authenticated=0" % (self.sid,)) 
     172                return 
    169173 
    170         # Find all variables that have been deleted and also remove them from 
    171         # the database 
    172         for k in [k for k in self._old.keys() if not self.has_key(k)]: 
    173             self.env.log.debug('Deleting variable %s from session %s' 
    174                                % (k, self.sid or self.req.authname)) 
    175             cursor.execute("DELETE FROM session WHERE sid=%s AND " 
    176                            "authenticated=%s AND var_name=%s", 
    177                            (self.sid, authenticated, k)) 
    178             changed = True 
    179  
    180         if changed: 
     174        now = int(time.time()) 
     175        # Update the session last visit time if it is over an hour old, 
     176        # so that session doesn't get purged 
     177        if now - self.last_visit > UPDATE_INTERVAL: 
     178            self.last_visit = now 
     179            self.env.log.info("Refreshing session %s" % self.sid) 
     180            cursor.execute('UPDATE session SET last_visit=%s ' 
     181                           'WHERE sid=%s AND authenticated=%s', 
     182                           (self.last_visit, self.sid, authenticated)) 
    181183            # Purge expired sessions. We do this only when the session was 
    182184            # changed as to minimize the purging. 
    183185            mintime = now - PURGE_AGE 
    184186            self.env.log.debug('Purging old, expired, sessions.') 
    185             cursor.execute("DELETE FROM session WHERE authenticated=0 AND " 
    186                            "sid IN (SELECT sid FROM session WHERE " 
    187                            "var_name='last_visit' AND var_value < %s)", 
     187            cursor.execute("DELETE FROM session_attribute " 
     188                           "WHERE authenticated=0 AND sid " 
     189                           "IN (SELECT sid FROM session WHERE " 
     190                           "authenticated=0 AND last_visit < %s)", 
    188191                           (mintime,)) 
    189  
    190             db.commit() 
     192            cursor.execute("DELETE FROM session WHERE " 
     193                           "authenticated=0 AND last_visit < %s", 
     194                           (mintime,)) 
     195        db.commit()