Changes between Version 35 and Version 36 of TracDev/DatabaseApi
- Timestamp:
- Jul 16, 2018, 7:53:40 PM (6 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
TracDev/DatabaseApi
v35 v36 12 12 == Accessing the Database 13 13 14 The Database API has evolved in the various versions of Trac, and its usage differs substantially across Trac 0.11, 0.12 and 1.x. The following subsections explain these differences chronologically. 14 The Database API has evolved in the various versions of Trac, and its usage differs substantially across Trac 0.11, 0.12 and 1.x. The following subsections explain these differences chronologically. You can skip to the [#Trac1.0API last section] if you don't care about the history and only need to write code that works in Trac 1.0 and later. 15 15 16 16 === API before Trac 0.12 … … 36 36 === Trac 0.12 API 37 37 38 // 38 //The 0.12 style can still be used in 1.0, but for new code we encourage you to focus on the Trac version 1.0 and to adopt the even simpler 1.0 style. See next sections for details. The 0.12 style was removed in [milestone:1.3.1].// 39 39 40 40 As support for Python 2.3 has been dropped, we could make use of decorators to get rid of some of the boilerplate code: … … 83 83 In this example, when `myFunc1()` is called, it first executes the outer transaction (`do_outer_transaction()`) and then executes a nested transaction (`myFunc2()`) from within the outer transaction. The key observation is that '''nested transactions are atomic'''. 84 84 85 This means that either the ''whole'' (outer) transaction is either committed or aborted. So even if the nested transaction succeeded but the outer transaction fails (an exception is raised at the line with the "commit or rollback" comment), the ''whole'' transaction will be rolled back, i eincluding the changes made by the nested transaction.85 This means that either the ''whole'' (outer) transaction is either committed or aborted. So even if the nested transaction succeeded but the outer transaction fails (an exception is raised at the line with the "commit or rollback" comment), the ''whole'' transaction will be rolled back, i.e. including the changes made by the nested transaction. 86 86 87 87 '''Notes:''' 88 88 - Do **not** call `commit()` yourself in a transaction, even though this is still possible in the API for backward compatibility reasons. Not only is a commit performed automatically at the proper time (as explained above), but if you call it yourself, you risk to commit from within a nested transaction, possibly leading to an inconsistent state of the database. 89 - Using `env.get_read_db()` within a transaction reuses the same connection as used for the connection. So uncommitted changes made by the transaction will already be visible to the caller of `get_read_db()` (but not outside of the transaction - that is in another thread).89 - Using `env.get_read_db()` within a transaction reuses the same connection. So uncommitted changes made by the transaction will already be visible to the caller of `get_read_db()` (but not outside of the transaction - that is in another thread). 90 90 - Uncommited changes of a transaction are only visible to nested transactions in the same thread. Different threads use different database connections and therefore different transactions. To be more precise, the exact detail of what is visible to other threads is database specific and depends to the //isolation level// used. 91 91 92 === Trac 1.0API92 === API after Trac 1.0 #Trac1.0API 93 93 94 94 [=#Trac0.13API] 95 // This style is supported for version 1.0.x, 1.1.x and will be supported beyond as well.96 Starting with 1.3.1, this is actually the only way, as the deprecated APIs were removed.//95 //This style is supported for version 1.0 and later. 96 Starting with [milestone:1.3.1], this is actually the only way, as the deprecated APIs were removed.// 97 97 98 98 As we dropped support for Python 2.4, we could simplify the code a bit more by using the `with` keyword and context managers: … … 156 156 }}} 157 157 158 You can use a list comprehension or dict comprehension to generate a sequence from a query: 159 {{{#!python 160 from trac.env import Environment 161 162 env = Environment('/path/to/projenv') 163 def myFunc1(): 164 """Returns a list of tuples.""" 165 return [a, b, c for a, b, c in env.db_query(""" 166 SELECT a, b, c 167 FROM ... 168 """, (param1, param2))] 169 170 env = Environment('/path/to/projenv') 171 def myFunc1(): 172 """Equivalent to myFunc1, without tuple unpacking.""" 173 return [row for row in env.db_query(""" 174 SELECT a, b, c 175 FROM ... 176 """, (param1, param2))] 177 178 def myFunc3(): 179 """Equivalent to myFunc1.""" 180 # Or even more simply, if the data is returned 181 # directly from the database without modification. 182 return env.db_query(""" 183 SELECT a, b, c 184 FROM ... 185 """, (param1, param2)) 186 187 def myFunc4(): 188 """Returns a dict.""" 189 # This form of dict creation should be used in Python < 2.7 190 return [dict(a, b) for a, b in env.db_query(""" 191 SELECT a, b 192 FROM ... 193 """, (param1, param2))] 194 195 def myFunc5(): 196 """Returns a dict.""" 197 # Dict comprehensions available in Python 2.7 198 return {a: b for a, b in env.db_query(""" 199 SELECT a, b 200 FROM ... 201 """, (param1, param2))} 202 }}} 203 204 Note that dict comprehensions are only available in Python 2.7 205 and Trac < 1.3.1 supports Python 2.6. 206 158 207 These short forms also present an additional safety measure as they're only allowing a "SELECT" query to be executed by a read-only connection. Indeed, the same short forms are possible on both `env.db_transaction` and `env.db_query`. 159 208 160 209 === Configuration 161 210 162 To determine which database has to be used, Trac looks at the value of the {{{database}}} configuration option in [wiki:TracInitrac.ini], which should contain a database connection URI. The default value for this option tells Trac to use an SQLite database inside the environment directory:211 To determine which database has to be used, Trac looks at the value of the `database` configuration option in [TracIni#trac-database-option trac.ini], which should contain a database connection URI. The default value for this option tells Trac to use an SQLite database inside the environment directory: 163 212 164 213 {{{#!ini … … 167 216 }}} 168 217 169 ''The connection URI syntax has been designed to be compatible with that provided by [http://sqlobject.org/SQLObject.html#declaring-the-class SQLObject]. See also the section on SQLObject [http://sqlobject.org/SQLObject.html#declaring-a-connection connections]. The only supported URI schemes at this point are `sqlite`, `postgres` and `mysql`.'' 218 The connection URI syntax has been designed to be compatible with that provided by [http://sqlobject.org/SQLObject.html#declaring-the-class SQLObject]. See also the section on SQLObject [http://sqlobject.org/SQLObject.html#declaring-a-connection connections]. The only supported URI schemes at this point are `sqlite`, `postgres` and `mysql`. 170 219 171 220 === Pooled Connections 172 221 173 222 Prior to version 1.0, Trac used to operate the following way: 174 The {{{Environment}}} method {{{get_db_cnx()}}} returns a connection from the pool of connections. This connection needs to be returned, and Trac is written so that the return will happen automatically by the garbage collector if the code is written to follow a simple rule. When the garbage collector determines the pooled database connection is no longer being used, its `__del__` method will return the pooled connection to the pool for reuse. If you have set a lexical variable in the function's body to the pooled connection, this typically occurs when the function is returning. In the example above of {{{myFunc}}} it occurs at the return statement since {{{db}}} is a variable local to {{{myFunc}}}223 The `Environment` method `get_db_cnx()` returns a connection from the pool of connections. This connection needs to be returned, and Trac is written so that the return will happen automatically by the garbage collector if the code is written to follow a simple rule. When the garbage collector determines the pooled database connection is no longer being used, its `__del__` method will return the pooled connection to the pool for reuse. If you have set a lexical variable in the function's body to the pooled connection, this typically occurs when the function is returning. In the example above of `myFunc` it occurs at the return statement since `db` is a variable local to `myFunc`. 175 224 176 225 With the context managers introduced in Trac 1.0, we're able to return this Connection to the pool in a much more robust and direct way: … … 201 250 == Rules for DB API Usage 202 251 203 Different DB API modules have different ways to pass parameters to the cursors' {{{execute}}}method, and different ways to access query results. To keep the database API as thin as possible, Trac uses a relatively common subset in all database code.252 Different DB API modules have different ways to pass parameters to the cursors' `execute` method, and different ways to access query results. To keep the database API as thin as possible, Trac uses a relatively common subset in all database code. 204 253 205 254 === Parameter passing 206 255 207 Always use the "format" parameter style, and always with {{{%s}}}, because that's the only type that pyPgSQL supports. Statement parameters always need to be passed into execute as an actual sequence (list or tuple).256 Always use the "format" parameter style, and always with `%s`. Statement parameters always need to be passed into execute as an actual sequence (list or tuple). 208 257 209 258 So the following statements are okay: … … 219 268 }}} 220 269 221 At any cost, avoid [http://docs.python.org/library/stdtypes.html#string-formatting string formatting] to get values into the SQL statement. The database automatically escapes values you pass using {{{execute()}}}arguments, but the same is not true if you use string formatting, opening your code up to [wikipedia:SQL_injection SQL injection] attacks.222 223 On the other hand, you '''must''' use string formatting to dynamically specify names of tables or columns, i eanything that is not a value as such:270 At any cost, avoid [http://docs.python.org/library/stdtypes.html#string-formatting string formatting] to get values into the SQL statement. The database automatically escapes values you pass using `execute()` arguments, but the same is not true if you use string formatting, opening your code up to [wikipedia:SQL_injection SQL injection] attacks. 271 272 On the other hand, you '''must''' use string formatting to dynamically specify names of tables or columns, i.e. anything that is not a value as such: 224 273 225 274 {{{#!python