| Home | Trees | Indices | Help |
|---|
|
|
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
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
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
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
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
234 """
235 Returns a boolean depending on the availability of the database.
236 """
237 return (lib_appglobals.active_db is not None);
238
239
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
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 #
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 #
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 #
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 #
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 #
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 #
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
| Home | Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Fri Sep 19 19:58:44 2008 | http://epydoc.sourceforge.net |