Package base :: Package includes :: Module database
[hide private]

Source Code for Module base.includes.database

  1  #!/usr/bin/env python 
  2  # $Id: database.inc,v 1.96 2008/07/19 12:31:14 dries Exp $ 
  3   
  4  """ 
  5    Wrapper for database interface code. 
  6   
  7    @package includes 
  8    @see <a href='http://drupy.net'>Drupy Homepage</a> 
  9    @see <a href='http://drupal.org'>Drupal Homepage</a> 
 10    @note Drupy is a port of the Drupal project. 
 11    @note This file was ported from Drupal's includes/database.inc 
 12    @author Brendon Crawford 
 13    @copyright 2008 Brendon Crawford 
 14    @contact message144 at users dot sourceforge dot net 
 15    @created 2008-01-10 
 16    @version 0.1 
 17    @note License: 
 18   
 19      This program is free software; you can redistribute it and/or 
 20      modify it under the terms of the GNU General Public License 
 21      as published by the Free Software Foundation; either version 2 
 22      of the License, or (at your option) any later version. 
 23   
 24      This program is distributed in the hope that it will be useful, 
 25      but WITHOUT ANY WARRANTY; without even the implied warranty of 
 26      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 27      GNU General Public License for more details. 
 28   
 29      You should have received a copy of the GNU General Public License 
 30      along with this program; if not, write to: 
 31       
 32      The Free Software Foundation, Inc., 
 33      51 Franklin Street, Fifth Floor, 
 34      Boston, MA  02110-1301, 
 35      USA 
 36  """ 
 37   
 38  __version__ = "$Revision: 1 $" 
 39   
 40  # 
 41  # Includes 
 42  # 
 43  from lib.drupy import DrupyPHP as php 
 44  from sites.default import settings 
 45  import appglobals as lib_appglobals 
 46  import bootstrap as lib_bootstrap 
 47  import database_mysqli as db 
 48   
 49   
 50  # 
 51  # A hash value to check when outputting database errors, php.md5('DB_ERROR'). 
 52  # 
 53  # @see drupal_error_handler() 
 54  # 
 55  DB_ERROR = 'a515ac9c2796ca0e23adbe92c68fc9fc' 
 56  DB_QUERY_REGEXP = '/(%d|%s|%%|%f|%b|%n)/'; 
 57   
 58   
 59  # 
 60  # @defgroup database Database abstraction layer 
 61  # @{ 
 62   
63 -def update_sql(sql):
64 """ 65 Allow the use of different database servers using the same code base. 66 67 Drupal provides a slim database abstraction layer to provide developers with 68 the ability to support multiple database servers easily. The intent of this 69 layer is to preserve the syntax and power of SQL as much as possible, while 70 letting Drupal control the pieces of queries that need to be written 71 differently for different servers and provide basic security checks. 72 73 Most Drupal database queries are performed by a call to db_query() or 74 db_query_range(). Module authors should also consider using 75 pager_query() for 76 queries that return results that need to be presented on multiple pages, and 77 tablesort_sql() for generating appropriate queries for sortable tables. 78 79 For example, one might wish to return a list of the most recent 10 nodes 80 authored by a given user. Instead of directly issuing the SQL query 81 @code 82 SELECT n.title, n.body, n.created FROM node n WHERE n.uid = uid \ 83 LIMIT 0, 10; 84 @endcode 85 one would instead call the Drupal functions: 86 @code 87 result = db_query_range('SELECT n.title, n.body, n.created 88 FROM {node} n WHERE n.uid = %d', uid, 0, 10); 89 while (node = db_fetch_object(result)) { 90 // Perform operations on node->body, etc. here. 91 } 92 @endcode 93 Curly braces are used around "node" to provide table prefixing via 94 db_prefix_tables(). The explicit use of a user ID is pulled out into an 95 argument passed to db_query() so that SQL injection attacks from user input 96 can be caught and nullified. The LIMIT syntax varies between database 97 servers, 98 so that is abstracted into db_query_range() arguments. Finally, note the 99 common pattern of iterating over the result set using db_fetch_object(). 100 101 Perform an SQL query and return success or failure. 102 103 @param sql 104 A string containing a complete SQL query. %-substitution 105 parameters are not supported. 106 @return 107 An array containing the keys: 108 success: a boolean indicating whether the query succeeded 109 query: the SQL query executed, passed through check_plain() 110 """ 111 result = db_query(sql, true); 112 return {'success' : (result != False), 'query' : check_plain(sql)};
113 114 115
116 -def prefix_tables(sql):
117 """ 118 Append a database prefix to all tables in a query. 119 120 Queries sent to Drupal should wrap all table names in curly brackets. This 121 function searches for this syntax and adds Drupal's table prefix to all 122 tables, allowing Drupal to coexist with other systems in the same 123 database if necessary. 124 125 @param sql 126 A string containing a partial or entire SQL query. 127 @return 128 The properly-prefixed string. 129 """ 130 if (php.is_array(settings.db_prefix)): 131 if (php.array_key_exists('default', settings.db_prefix)): 132 tmp = settings.db_prefix; 133 del(tmp['default']); 134 for key,val in tmp.items(): 135 sql = php.strtr(sql, {('{' + key + '}') : (val + key)}); 136 return php.strtr(sql, {'{' : settings.db_prefix['default'], '}' : ''}); 137 else: 138 for key,val in settings.db_prefix.items(): 139 sql = php.strtr(sql, {('{' + key + '}') : (val + key)}); 140 return php.strtr(sql, {'{' : '', '}' : ''}); 141 else: 142 return php.strtr(sql, {'{' : settings.db_prefix, '}' : ''});
143 144
145 -def set_active(name = 'default'):
146 """ 147 Activate a database for future queries. 148 149 If it is necessary to use external databases in a project, this function can 150 be used to change where database queries are sent. If the database has not 151 yet been used, it is initialized using the URL specified for that name in 152 Drupal's configuration file. If this name is not defined, a duplicate of the 153 default connection is made instead. 154 155 Be sure to change the connection back to the default when done with custom 156 code. 157 158 @param name 159 The name assigned to the newly active database connection. If omitted, the 160 default connection will be made active. 161 162 @return the name of the previously active database or FALSE if non was 163 found. 164 165 @todo BC: Need to eventually resolve the database importing mechanism here 166 right now we are statically loading mysql at the top, but eventually we need 167 to get this figured out 168 """ 169 php.static(set_active, 'db_conns', {}) 170 php.static(set_active, 'active_name', False) 171 if (settings.db_url == None): 172 install_goto('install.py'); 173 if (not php.isset(set_active.db_conns, name)): 174 # Initiate a new connection, using the named DB URL specified. 175 if (isinstance(settings.db_url, dict)): 176 connect_url = (settings.db_url[name] if \ 177 php.array_key_exists(name, settings.db_url) else \ 178 settings.db_url['default']); 179 else: 180 connect_url = settings.db_url; 181 lib_appglobals.db_type = \ 182 php.substr(connect_url, 0, php.strpos(connect_url, '://')); 183 #handler = "includes/database_%(db_type)s.py" % {'db_type' : db_type}; 184 #try: 185 # import db file here 186 #except ImportError: 187 # _db_error_page("The database type '" + db_type + \ 188 # "' is unsupported. Please use either 'mysql' or " + \ 189 # "'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases."); 190 set_active.db_conns[name] = db.connect(connect_url); 191 # We need to pass around the simpletest database prefix in the request 192 # and we put that in the user_agent php.header. 193 if (php.preg_match("/^simpletest\d+$/", php.SERVER['HTTP_USER_AGENT'])): 194 settings.db_prefix = php.SERVER['HTTP_USER_AGENT']; 195 previous_name = set_active.active_name; 196 # Set the active connection. 197 set_active.active_name = name; 198 lib_appglobals.active_db = set_active.db_conns[name]; 199 return previous_name;
200 201 202
203 -def _error_page(error = ''):
204 """ 205 Helper function to show fatal database errors. 206 207 Prints a themed maintenance page with the 'Site off-line' text, 208 adding the provided error message in the case of 'display_errors' 209 set to on. Ends the page request; no return. 210 211 @param error 212 The error message to be appended if 'display_errors' is on. 213 """ 214 lib_bootstrap.drupal_maintenance_theme(); 215 drupal_set_header('HTTP/1.1 503 Service Unavailable'); 216 drupal_set_title('Site off-line'); 217 message = '<p>The site is currently not available due to technical ' + \ 218 'problems. Please try again later. Thank you for your understanding.</p>'; 219 message += '<hr /><p><small>If you are the maintainer of this site, ' + \ 220 'please check your database settings in the ' + \ 221 '<code>settings.php</code> file and ensure that your hosting ' + \ 222 'provider\'s database server is running. For more help, ' + \ 223 'see the <a href="http://drupal.org/node/258">handbook</a>, or ' + \ 224 'contact your hosting provider.</small></p>'; 225 if (error and ini_get('display_errors')): 226 message += '<p><small>The ' + theme('placeholder', \ 227 lib_appglobals.db_type) + \ 228 ' error was: ' + theme('placeholder', error) + '.</small></p>'; 229 print theme('maintenance_page', message); 230 exit();
231 232
233 -def is_active():
234 """ 235 Returns a boolean depending on the availability of the database. 236 """ 237 return (lib_appglobals.active_db is not None);
238 239
240 -def _query_callback(match, init = False):
241 """ 242 Helper function for db_query(). 243 """ 244 php.static(_query_callback, 'args') 245 if (init): 246 _query_callback.args = list(match); 247 return; 248 # We must use type casting to int to convert FALSE/NULL/(TRUE?) 249 if match[1] == '%d': 250 # We don't need db_escape_string as numbers are db-safe 251 return str(int(php.array_shift(_query_callback.args))); 252 elif match[1] == '%s': 253 return db.escape_string(php.array_shift(_query_callback.args)); 254 elif match[1] == '%n': 255 # Numeric values have arbitrary precision, so can't be treated as float. 256 # is_numeric() allows hex values (0xFF), but they are not valid. 257 value = php.trim(php.array_shift(args)); 258 return (value if (php.is_numeric(value) and not \ 259 php.stripos(value, 'x')) else '0') 260 elif match[1] == '%%': 261 return '%'; 262 elif match[1] == '%f': 263 return float(php.array_shift(_query_callback.args)); 264 elif match[1] == '%b': # binary data 265 return db.encode_blob(php.array_shift(_query_callback.args));
266 267
268 -def placeholders(arguments, type = 'int'):
269 """ 270 Generate placeholders for an array of query arguments of a single type. 271 272 Given a Schema API field type, return correct %-placeholders to 273 embed in a query 274 275 @param arguments 276 An array with at least one element. 277 @param type 278 The Schema API type of a field (e.g. 'int', 'text', or 'varchar'). 279 """ 280 placeholder = db_type_placeholder(type); 281 return php.implode(',', php.array_fill(0, php.count(arguments), \ 282 placeholder));
283 284 285 286 # 287 # Helper function for db_rewrite_sql. 288 # 289 # Collects JOIN and WHERE statements via hook_db_rewrite_sql() 290 # Decides whether to select primary_key or DISTINCT(primary_key) 291 # 292 # @param query 293 # Query to be rewritten. 294 # @param primary_table 295 # Name or alias of the table which has the primary key field for this query. 296 # Typical table names would be: {blocks}, {comments}, {forum}, {node}, 297 # {menu}, {term_data} or {vocabulary}. However, in most cases the usual 298 # table alias (b, c, f, n, m, t or v) is used instead of the table name. 299 # @param primary_field 300 # Name of the primary field. 301 # @param args 302 # Array of additional arguments. 303 # @return 304 # An array: join statements, where statements, field or DISTINCT(field). 305 #
306 -def _rewrite_sql(query = '', primary_table = 'n', primary_field = 'nid', \ 307 args = []):
308 where = [] 309 join_ = [] 310 distinct = False 311 for plugin in lib_plugin.implements('db_rewrite_sql'): 312 result = lib_plugin.invoke(plugin, 'db_rewrite_sql', query, \ 313 primary_table, primary_field, args) 314 if (php.isset(result) and php.is_array(result)): 315 if (php.isset(result['where'])): 316 where.append( result['where'] ) 317 if (php.isset(result['join'])): 318 join_.append( result['join'] ) 319 if (php.isset(result['distinct']) and result['distinct']): 320 distinct = True 321 elif (php.isset(result)): 322 where.append( result ) 323 where = ('' if php.empty(where) else \ 324 ('(' + php.implode(') AND (', where) + ')') ) 325 join_ = ('' if php.empty(join) else php.implode(' ', join)) 326 return (join, where, distinct)
327 328 329 330 # 331 # Rewrites node, taxonomy and comment queries. Use it for 332 # listing queries. Do not 333 # use FROM table1, table2 syntax, use JOIN instead. 334 # 335 # @param query 336 # Query to be rewritten. 337 # @param primary_table 338 # Name or alias of the table which has the primary key field for this query. 339 # Typical table names would be: {blocks}, {comments}, {forum}, {node}, 340 # {menu}, {term_data} or {vocabulary}. However, it is more common to use the 341 # the usual table aliases: b, c, f, n, m, t or v. 342 # @param primary_field 343 # Name of the primary field. 344 # @param args 345 # An array of arguments, passed to the implementations 346 # of hook_db_rewrite_sql. 347 # @return 348 # The original query with JOIN and WHERE statements inserted from 349 # hook_db_rewrite_sql implementations. nid is rewritten if needed. 350 #
351 -def rewrite_sql(query, primary_table = 'n', primary_field = 'nid', args = []):
352 join_, where, distinct = _rewrite_sql(query, primary_table, \ 353 primary_field, args) 354 if (distinct): 355 query = distinct_field(primary_table, primary_field, query) 356 if (not php.empty(where) or not php.empty(join_)): 357 pattern = \ 358 '{ ' + \ 359 ' # Beginning of the string ' + \ 360 ' ^ ' + \ 361 ' ((?P<anonymous_view> ' + \ 362 ' # Everything within this set of parentheses ' + \ 363 ' # is named "anonymous view ' + \ 364 ' (?: ' + \ 365 ' # anything not parentheses ' + \ 366 ' [^()]++ ' + \ 367 ' | ' + \ 368 ' # an open parenthesis, more anonymous view and ' + \ 369 ' # finally a close parenthesis. ' + \ 370 ' \( (?P>anonymous_view) \) ' + \ 371 ' )* ' + \ 372 ' )[^()]+WHERE) ' + \ 373 '}X' 374 matches = [] 375 php.preg_match(pattern, query, matches) 376 if (where): 377 n = php.strlen(matches[1]) 378 second_part = php.substr(query, n) 379 first_part = php.substr(matches[1], 0, n - 5) + \ 380 " join WHERE where AND ( " 381 # PHP 4 does not support strrpos for strings. We emulate it. 382 haystack_reverse = php.strrev(second_part) 383 # No need to use strrev on the needle, we supply GROUP, ORDER, LIMIT 384 # reversed. 385 for needle_reverse in ('PUORG', 'REDRO', 'TIMIL'): 386 pos = php.strpos(haystack_reverse, needle_reverse) 387 if (pos != False): 388 # All needles are five characters long. 389 pos += 5 390 break 391 if (pos == False): 392 query = first_part + second_part + ')' 393 else: 394 query = first_part + substr(second_part, 0, -pos) + ')' + \ 395 php.substr(second_part, -pos) 396 else: 397 query = matches[1] + " join " + \ 398 php.substr(query, php.strlen(matches[1])) 399 return query
400 401 402 403 # 404 # Restrict a dynamic table, column or constraint name to safe characters. 405 # 406 # Only keeps alphanumeric and underscores. 407 #
408 -def escape_table(string_):
409 return php.preg_replace('/[^A-Za-z0-9_]+/', '', string_)
410 411 412 # 413 # A Drupal schema definition is an array structure representing one or 414 # more tables and their related keys and indexes. A schema is defined by 415 # hook_schema(), which usually lives in a modulename.install file. 416 # 417 # By implementing hook_schema() and specifying the tables your module 418 # declares, you can easily create and drop these tables on all 419 # supported database engines. You don't have to deal with the 420 # different SQL dialects for table creation and alteration of the 421 # supported database engines. 422 # 423 # hook_schema() should return an array with a key for each table that 424 # the module defines. 425 # 426 # The following keys are defined: 427 # 428 # - 'description': A string describing this table and its purpose. 429 # References to other tables should be enclosed in 430 # curly-brackets. For example, the node_revisions table 431 # description field might contain "Stores per-revision title and 432 # body data for each {node}." 433 # - 'fields': An associative array ('fieldname' : specification) 434 # that describes the table's database columns. The specification 435 # is also an array. The following specification parameters are defined: 436 # 437 # - 'description': A string describing this field and its purpose. 438 # References to other tables should be enclosed in 439 # curly-brackets. For example, the node table vid field 440 # description might contain "Always holds the largest (most 441 # recent):node_revisions}.vid value for this nid." 442 # - 'type': The generic datatype: 'varchar', 'int', 'serial' 443 # 'float', 'numeric', 'text', 'blob' or 'datetime'. Most types 444 # just map to the according database engine specific 445 # datatypes. Use 'serial' for auto incrementing fields. This 446 # will expand to 'int auto_increment' on mysql. 447 # - 'size': The data size: 'tiny', 'small', 'medium', 'normal', 448 # 'big'. This is a hint about the largest value the field will 449 # store and determines which of the database engine specific 450 # datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT). 451 # 'normal', the default, selects the base type (e.g. on MySQL, 452 # INT, VARCHAR, BLOB, etc.). 453 # 454 # Not all sizes are available for all data types. See 455 # db_type_map() for possible combinations. 456 # - 'not None': If True, no None values will be allowed in this 457 # database column. Defaults to False. 458 # - 'default': The field's default value. The PHP type of the 459 # value matters: '', '0', and 0 are all different. If you 460 # specify '0' as the default value for a type 'int' field it 461 # will not work because '0' is a string containing the 462 # character "zero", not an integer. 463 # - 'length': The maximal length of a type 'varchar' or 'text' 464 # field. Ignored for other field types. 465 # - 'unsigned': A boolean indicating whether a type 'int', 'float' 466 # and 'numeric' only is signed or unsigned. Defaults to 467 # False. Ignored for other field types. 468 # - 'precision', 'scale': For type 'numeric' fields, indicates 469 # the precision (total number of significant digits) and scale 470 # (decimal digits right of the decimal point). Both values are 471 # mandatory. Ignored for other field types. 472 # 473 # All parameters apart from 'type' are optional except that type 474 # 'numeric' columns must specify 'precision' and 'scale'. 475 # 476 # - 'primary key': An array of one or more key column specifiers (see below) 477 # that form the primary key. 478 # - 'unique key': An associative array of unique keys ('keyname' : 479 # specification). Each specification is an array of one or more 480 # key column specifiers (see below) that form a unique key on the table. 481 # - 'indexes': An associative array of indexes ('indexame' : 482 # specification). Each specification is an array of one or more 483 # key column specifiers (see below) that form an index on the 484 # table. 485 # 486 # A key column specifier is either a string naming a column or an 487 # array of two elements, column name and length, specifying a prefix 488 # of the named column. 489 # 490 # As an example, here is a SUBSET of the schema definition for 491 # Drupal's 'node' table. It show four fields (nid, vid, type, and 492 # title), the primary key on field 'nid', a unique key named 'vid' on 493 # field 'vid', and two indexes, one named 'nid' on field 'nid' and 494 # one named 'node_title_type' on the field 'title' and the first four 495 # bytes of the field 'type': 496 # 497 # @code 498 # schema['node'] = array( 499 # 'fields' : array( 500 # 'nid' : array('type' : 'serial', 'unsigned' : True, \ 501 # 'not None' : True), 502 # 'vid' : array('type' : 'int', 'unsigned' : True, \ 503 # 'not None' : True, 'default' : 0), 504 # 'type' : array('type' : 'varchar', 'length' : 32, \ 505 # 'not None' : True, 'default' : ''), 506 # 'title' : array('type' : 'varchar', 'length' : 128, \ 507 # 'not None' : True, 'default' : ''), 508 # ), 509 # 'primary key' : array('nid'), 510 # 'unique keys' : array( 511 # 'vid' : array('vid') 512 # ), 513 # 'indexes' : array( 514 # 'nid' : array('nid'), 515 # 'node_title_type' : array('title', array('type', 4)), 516 # ), 517 # ) 518 # @endcode 519 # 520 # @see drupal_install_schema() 521 # 522 # 523 # Create a new table from a Drupal table definition. 524 # 525 # @param ret 526 # Array to which query results will be added. 527 # @param name 528 # The name of the table to create. 529 # @param table 530 # A Schema API table definition array. 531 #
532 -def db_create_table(ret, name, table):
533 php.Reference.check(ret) 534 statements = create_table_sql(name, table) 535 for statement in statements: 536 ret.append( update_sql(statement) )
537 538 539 # 540 # Return an array of field names from an array of key/index column specifiers. 541 # 542 # This is usually an identity function but if a key/index uses a column prefix 543 # specification, this function extracts just the name. 544 # 545 # @param fields 546 # An array of key/index column specifiers. 547 # @return 548 # An array of field names. 549 #
550 -def field_names(fields):
551 ret = [] 552 for field in fields: 553 if (php.is_array(field)): 554 ret.append( field[0] ) 555 else: 556 ret.append( field ) 557 return ret
558 559 560 # 561 # Given a Schema API field type, return the correct %-placeholder. 562 # 563 # Embed the placeholder in a query to be passed to db_query and and pass as an 564 # argument to db_query a value of the specified type. 565 # 566 # @param type 567 # The Schema API type of a field. 568 # @return 569 # The placeholder string to embed in a query for that type. 570 #
571 -def type_placeholder(type_):
572 if \ 573 type_ == 'varchar' or \ 574 type_ == 'char' or \ 575 type_ == 'text' or \ 576 type_ == 'datetime': 577 return "'%s'" 578 elif type_ == 'numeric': 579 # Numeric values are arbitrary precision numbers. Syntacically, numerics 580 # should be specified directly in SQL. However, without single quotes 581 # the %s placeholder does not protect against non-numeric characters such 582 # as spaces which would expose us to SQL injection. 583 return '%n' 584 elif \ 585 type_ == 'serial' or \ 586 type_ == 'int': 587 return '%d' 588 elif type_ == 'float': 589 return '%f' 590 elif type_ == 'blob': 591 return '%b' 592 # There is no safe value to return here, so return something that 593 # will cause the query to fail. 594 return 'unsupported type ' + type_ + 'for db_type_placeholder'
595 596 597 598 599 600 601 602 603 # 604 # Aliases 605 # 606 result = db.result 607 query = db.query 608 fetch_object = db.fetch_object 609 fetch_assoc = db.fetch_assoc 610 escape_string = db.escape_string 611