1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 A wrapper around `sqlalchemy.create_engine` that handles all of the
18 special cases that Buildbot needs. Those include:
19
20 - pool_recycle for MySQL
21 - %(basedir) substitution
22 - optimal thread pool size calculation
23
24 """
25
26 import os
27 import sqlalchemy as sa
28 from twisted.python import log
29 from sqlalchemy.engine import strategies, url
30 from sqlalchemy.pool import NullPool, Pool
31 from buildbot.util import sautils
32
33
37
39
40
41
42
43
44
45
46 name = 'buildbot'
47
49 """For sqlite, percent-substitute %(basedir)s and use a full
50 path to the basedir. If using a memory database, force the
51 pool size to be 1."""
52 max_conns = None
53
54
55 if u.database:
56
57
58
59
60
61
62 kwargs.setdefault('poolclass', NullPool)
63
64 u.database = u.database % dict(basedir = kwargs['basedir'])
65 if not os.path.isabs(u.database[0]):
66 u.database = os.path.join(kwargs['basedir'], u.database)
67
68
69 if not u.database:
70 kwargs['pool_size'] = 1
71 max_conns = 1
72
73
74 if 'serialize_access' in u.query:
75 u.query.pop('serialize_access')
76 max_conns = 1
77
78 return u, kwargs, max_conns
79
81 """Special setup for sqlite engines"""
82
83 if u.database:
84 log.msg("setting database journal mode to 'wal'")
85 try:
86 engine.execute("pragma journal_mode = wal")
87 except:
88 log.msg("failed to set journal mode - database may fail")
89
91 """For mysql, take max_idle out of the query arguments, and
92 use its value for pool_recycle. Also, force use_unicode and
93 charset to be True and 'utf8', failing if they were set to
94 anything else."""
95
96 kwargs['pool_recycle'] = int(u.query.pop('max_idle', 3600))
97
98
99 storage_engine = u.query.pop('storage_engine', 'MyISAM')
100 kwargs['connect_args'] = {
101 'init_command' : 'SET storage_engine=%s' % storage_engine,
102 }
103
104 if 'use_unicode' in u.query:
105 if u.query['use_unicode'] != "True":
106 raise TypeError("Buildbot requires use_unicode=True " +
107 "(and adds it automatically)")
108 else:
109 u.query['use_unicode'] = True
110
111 if 'charset' in u.query:
112 if u.query['charset'] != "utf8":
113 raise TypeError("Buildbot requires charset=utf8 " +
114 "(and adds it automatically)")
115 else:
116 u.query['charset'] = 'utf8'
117
118
119
120
121
122
123 def checkout_listener(dbapi_con, con_record, con_proxy):
124 try:
125 cursor = dbapi_con.cursor()
126 cursor.execute("SELECT 1")
127 except dbapi_con.OperationalError, ex:
128 if ex.args[0] in (2006, 2013, 2014, 2045, 2055):
129
130 raise sa.exc.DisconnectionError()
131 raise
132
133
134
135 if sautils.sa_version() < (0,7,0):
136 class ReconnectingListener(object):
137 pass
138 rcl = ReconnectingListener()
139 rcl.checkout = checkout_listener
140 kwargs['listeners'] = [ rcl ]
141 else:
142 sa.event.listen(Pool, 'checkout', checkout_listener)
143
144 return u, kwargs, None
145
146 - def create(self, name_or_url, **kwargs):
147 if 'basedir' not in kwargs:
148 raise TypeError('no basedir supplied to create_engine')
149
150 max_conns = None
151
152
153 u = url.make_url(name_or_url)
154 if u.drivername.startswith('sqlite'):
155 u, kwargs, max_conns = self.special_case_sqlite(u, kwargs)
156 elif u.drivername.startswith('mysql'):
157 u, kwargs, max_conns = self.special_case_mysql(u, kwargs)
158
159
160 basedir = kwargs.pop('basedir')
161
162
163
164 if max_conns is None:
165 max_conns = kwargs.get('pool_size', 5) + kwargs.get('max_overflow', 10)
166
167 engine = strategies.ThreadLocalEngineStrategy.create(self,
168 u, **kwargs)
169
170
171
172 engine.optimal_thread_pool_size = max_conns
173
174
175 engine.buildbot_basedir = basedir
176
177 if u.drivername.startswith('sqlite'):
178 self.set_up_sqlite_engine(u, engine)
179
180 return engine
181
182 BuildbotEngineStrategy()
183
184
185
186
188 kwargs['strategy'] = 'buildbot'
189
190 return sa.create_engine(*args, **kwargs)
191