1# This file is part of Buildbot. Buildbot is free software: you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Copyright Buildbot Team Members 15 16import sqlalchemy as sa 17 18from twisted.internet import defer 19 20from buildbot.db import NULL 21from buildbot.db import base 22 23 24class BuildDataDict(dict): 25 pass 26 27 28class BuildDataConnectorComponent(base.DBConnectorComponent): 29 30 def _insert_race_hook(self, conn): 31 # called so tests can simulate a race condition during insertion 32 pass 33 34 @defer.inlineCallbacks 35 def setBuildData(self, buildid, name, value, source): 36 def thd(conn): 37 build_data_table = self.db.model.build_data 38 39 update_values = { 40 'value': value, 41 'length': len(value), 42 'source': source, 43 } 44 45 insert_values = { 46 'buildid': buildid, 47 'name': name, 48 'value': value, 49 'length': len(value), 50 'source': source, 51 } 52 53 while True: 54 q = build_data_table.update() 55 q = q.where((build_data_table.c.buildid == buildid) & 56 (build_data_table.c.name == name)) 57 q = q.values(update_values) 58 r = conn.execute(q) 59 if r.rowcount > 0: 60 return 61 r.close() 62 63 self._insert_race_hook(conn) 64 65 try: 66 q = build_data_table.insert().values(insert_values) 67 r = conn.execute(q) 68 return 69 except (sa.exc.IntegrityError, sa.exc.ProgrammingError): 70 # there's been a competing insert, retry 71 pass 72 73 yield self.db.pool.do(thd) 74 75 @defer.inlineCallbacks 76 def getBuildData(self, buildid, name): 77 def thd(conn): 78 build_data_table = self.db.model.build_data 79 80 q = build_data_table.select().where((build_data_table.c.buildid == buildid) & 81 (build_data_table.c.name == name)) 82 res = conn.execute(q) 83 row = res.fetchone() 84 if not row: 85 return None 86 return self._row2dict(conn, row) 87 res = yield self.db.pool.do(thd) 88 return res 89 90 @defer.inlineCallbacks 91 def getBuildDataNoValue(self, buildid, name): 92 def thd(conn): 93 build_data_table = self.db.model.build_data 94 95 q = sa.select([build_data_table.c.buildid, 96 build_data_table.c.name, 97 build_data_table.c.length, 98 build_data_table.c.source]) 99 q = q.where((build_data_table.c.buildid == buildid) & 100 (build_data_table.c.name == name)) 101 res = conn.execute(q) 102 row = res.fetchone() 103 if not row: 104 return None 105 return self._row2dict_novalue(conn, row) 106 res = yield self.db.pool.do(thd) 107 return res 108 109 @defer.inlineCallbacks 110 def getAllBuildDataNoValues(self, buildid): 111 def thd(conn): 112 build_data_table = self.db.model.build_data 113 114 q = sa.select([build_data_table.c.buildid, 115 build_data_table.c.name, 116 build_data_table.c.length, 117 build_data_table.c.source]) 118 q = q.where(build_data_table.c.buildid == buildid) 119 120 return [self._row2dict_novalue(conn, row) 121 for row in conn.execute(q).fetchall()] 122 res = yield self.db.pool.do(thd) 123 return res 124 125 @defer.inlineCallbacks 126 def deleteOldBuildData(self, older_than_timestamp): 127 build_data = self.db.model.build_data 128 builds = self.db.model.builds 129 130 def count_build_datum(conn): 131 res = conn.execute(sa.select([sa.func.count(build_data.c.id)])) 132 count = res.fetchone()[0] 133 res.close() 134 return count 135 136 def thd(conn): 137 count_before = count_build_datum(conn) 138 139 if self.db._engine.dialect.name == 'sqlite': 140 # sqlite does not support delete with a join, so for this case we use a subquery, 141 # which is much slower 142 143 q = sa.select([builds.c.id]) 144 q = q.where((builds.c.complete_at >= older_than_timestamp) | 145 (builds.c.complete_at == NULL)) 146 147 q = build_data.delete().where(build_data.c.buildid.notin_(q)) 148 else: 149 q = build_data.delete() 150 q = q.where(builds.c.id == build_data.c.buildid) 151 q = q.where((builds.c.complete_at >= older_than_timestamp) | 152 (builds.c.complete_at == NULL)) 153 res = conn.execute(q) 154 res.close() 155 156 count_after = count_build_datum(conn) 157 return count_before - count_after 158 159 res = yield self.db.pool.do(thd) 160 return res 161 162 def _row2dict(self, conn, row): 163 return BuildDataDict(buildid=row.buildid, 164 name=row.name, 165 value=row.value, 166 length=row.length, 167 source=row.source) 168 169 def _row2dict_novalue(self, conn, row): 170 return BuildDataDict(buildid=row.buildid, 171 name=row.name, 172 value=None, 173 length=row.length, 174 source=row.source) 175