database.inc

  1. 7.x drupal/includes/database/database.inc
  2. 7.x drupal/includes/database/pgsql/database.inc
  3. 7.x drupal/includes/database/sqlite/database.inc
  4. 7.x drupal/includes/database/mysql/database.inc
  5. 5.x drupal/includes/database.inc
  6. 6.x drupal/includes/database.inc
  7. 8.x drupal/core/includes/database.inc

Wrapper for database interface code.

Functions

Namesort descending Description
db_create_table Create a new table from a Drupal table definition.
db_distinct_field Adds the DISTINCT flag to the supplied query and returns the altered query.
db_escape_table Restrict a dynamic table, column or constraint name to safe characters.
db_field_names Return an array of field names from an array of key/index column specifiers.
db_is_active Returns a boolean depending on the availability of the database.
db_placeholders Generate placeholders for an array of query arguments of a single type.
db_prefix_tables Append a database prefix to all tables in a query.
db_rewrite_sql Rewrites node, taxonomy and comment queries. Use it for listing queries. Do not use FROM table1, table2 syntax, use JOIN instead.
db_set_active Activate a database for future queries.
db_type_placeholder Given a Schema API field type, return the correct %-placeholder.
update_sql Perform an SQL query and return success or failure.
_db_error_page Helper function to show fatal database errors.
_db_query_callback Helper function for db_query().
_db_rewrite_sql Helper function for db_rewrite_sql.

Constants

Namesort descending Description
DB_ERROR A hash value to check when outputting database errors, md5('DB_ERROR').
DB_QUERY_REGEXP Indicates the place holders that should be replaced in _db_query_callback().

File

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