Passing a query to a module_hook that uses a second database

Viewed 65

I would like to use another database connection that queries a database using a hook in my own module called 'connector'. This hook shall take a parameter that is a query which shall be executed using the secondary database before it switches back to the primary Drupal 7-database. The problem seems to be that the query is somehow generated for the (primary) Drupal database (as it's been created before switching to the secondary db), even though the SQL itself looks fine to me. What am I doing wrong here?

Hook:

function connector_api_db($query) {

$result = [];

$database_info = array(
    'database' => variable_get('connector_db_name'),
    'username' => variable_get('connector_db_user'),
    'password' => variable_get('connector_db_pwd'),
    'host' => variable_get('connector_db_host'),
    'driver' => 'mysql',
);

Database::addConnectionInfo('secondary_db', 'default', $database_info);

db_set_active('secondary_db'); // Initialize the connection

    /* This actually works but I can here not use my own query as a parameter in this function. Not what I want. */
    //$query = db_select('registration')->fields('registration')
//->condition('id', 46, '=');

echo $query->__toString() . "<br />"; /* Get query string */
var_dump($query->getArguments()); /* Get the arguments passed to the string */

$result = $query->execute()->fetchAssoc();

echo "<pre>";
print_r($result);
echo "</pre>";


db_set_active(); // without the paramater means set back to the default for the site
drupal_set_message(t('The queries have been made.'));

return $result;

}

Invoking the hook:

    $query = db_select('registration')
        ->fields('registration')
        ->condition('id', 46, '=');
$response = module_invoke('connector', 'api_db', $query);

This results into this:

SELECT registration.* FROM {registration} registration WHERE (id = :db_condition_placeholder_0) 

array(1) { [":db_condition_placeholder_0"]=> int(46) }

Additional uncaught exception thrown while handling exception.
Original
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table &#039;drupal.registration&#039; doesn&#039;t exist: SELECT registration.* FROM {registration} registration WHERE (id = :db_condition_placeholder_0) ; Array ( [:db_condition_placeholder_0] =&gt; 46 ) in connector_api_db() (line 70 of /<path-to-drupal>/drupal-7.61/modules/connector/connector.main.inc).
2 Answers

This is not an answer, strictly speaking, but rather a suggestion.

The thing is that your hook receives a $query already built with the original database connection, this explains the errors. To be effective, db_set_active() should be called before calling the Query constructor.

Otherwise, you would have to override constructors for both the Select class and its parent class Query, or more precisely "reconstruct" the Query builder instance for Select statements (and probably others too if you need range query etc.).

Also, unless having explicit requirements to provide a dedicated hook in this situation, it is not necessary.

For example it would be simpler to do something like this :

connector.module :

/**
 * Implements hook_init()
 * 
 * Adds secondary database connection information. 
 */
function connector_init() {
  $database_info = array(
      'database' => variable_get('connector_db_name'),
      'username' => variable_get('connector_db_user'),
      'password' => variable_get('connector_db_pwd'),
      'host' => variable_get('connector_db_host'),
      'driver' => 'mysql',
  );

  Database::addConnectionInfo('secondary_db', 'default', $database_info);  
}

/**
 * Switches from primary to secondary database and the other way round. 
 */
function connector_switch_db($db_key = 'secondary_db') {
  return db_set_active($db_key);
}

Example :

// Set secondary db active. 
$primary_db_key = connector_switch_db();

$query = db_select('registration')
        ->fields('registration')
        ->condition('id', 46, '=');

$result = $query->execute()->fetchAssoc();

// Switch back to the primary database. 
connector_switch_db($primary_db_key);

This softer approach also prevents hardcode like (...)->execute()->fetchAssoc() and let the implementation free to execute and fetch the results as needed.

For future reference, this is what I came up with (caching bits included):

function connector_init() {
    foreach([
        'connector_db_host',
        'connector_db_name',
        'connector_db_user',
        'connector_db_pwd',
    ] as $var) {
        drupal_static($var, variable_get($var, ''));
    }

    Database::addConnectionInfo('secondary_db', 'default', [
        'host' => drupal_static('connector_db_host'),
        'database' => drupal_static('connector_db_name'),
        'username' => drupal_static('connector_db_user'),
        'password' => drupal_static('connector_db_pwd'),
        'driver' => 'mysql',
    ]);
}

function connector_api_db($query) {

    $result = [];

    $sql_query = $query->__toString();
    $arguments = $query->getArguments();

    if (count($arguments) > 0) {
        ksort($arguments);
    }
    $cache_id = 'sql_' . md5($sql_query . '|' . print_r($arguments, 1));

    $cache = cache_get($cache_id, 'cache_connector');

    if ($cache && $cache->expire >= time()) {
        $result = $cache->data;
    } else {

        db_set_active('secondary_db'); // Switch to secondary db */
        $query_failed = FALSE;

        try {
            $db_result = db_query($sql_query, $arguments);
            $db_result = (0 === $db_result->rowCount()) ? [] : $db_result->fetchAssoc();
        } catch (\PDOException $e) {
            $db_result = [];
            $query_failed = TRUE;
        } finally {
            db_set_active(); /* switch back to default */
        }

        $result = (object)[
            'query' => $sql_query,
            'arguments' => $arguments,
            'result' => (object)$db_result
        ];

        if (!$query_failed) {
            cache_set($cache_id, $result, 'cache_connector', strtotime('+10 minutes'));
        }

    }

    return $result;

}
Related