Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core-Database] added 2 new functions to help avoid code duplication #3428

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions php/libraries/Database.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,77 @@ class Database
return $result;
}

/**
* Runs an SQL select statement as a prepared query and re-indexes
* the results using the given unique non-nullable key.
*
* @param string $query The SQL SELECT query to be run
* @param array $params Values to use for binding to the prepared statement
* @param string $uniqueKey Key to use when re-indexing, this key must be a
* single column, must be unique and must not be
* nullable
*
* @throws LorisException If the supplied key is empty or null
* @throws DatabaseException If the key is not part of the query itself
* @throws DatabaseException If the key is not unique within the resulting set
*
* @return array An array of arrays containing the data. The outermost array is
* associative and uses the supplied $uniqueKey parameter as
* a key for each of the sub-arrays with the format
* rowPrimaryKey=>rowValuesArray. Each nested array represents
* a row from the returned by the query. Each element in the
* nested array is an associative array representing the row in
* the format ColumnName => Value
*/
function pselectWithIndexKey($query, $params, $uniqueKey)
{
if (is_null($uniqueKey) || empty($uniqueKey)) {
throw new LorisException(
"The pselectWithIndexKey() function expects the uniqueKey parameter
to not be null or empty. If re-indexing on the primary key is
not necessary please use the pselect() function instead."
);
}

$result = $this->pselect($query, $params);

if (empty($result)) {
return $result;
}

$filteredResult = array();
// re-order the return array
foreach ($result as $row) {
// Check first that the row contains the primary key supplied
if (!array_key_exists($uniqueKey, $row)) {
throw new DatabaseException(
"The query supplied to pselectWithIndexKey() does not contain
the unique key to re-index on. Make sure to supply the
appropriate key in the SELECT statement to match the supplied
parameter of this function",
$query
);
}

// Check that the primary key is indeed unique to avoid overriding data
// in the result array
if (isset($filteredResult[$row[$uniqueKey]])) {
throw new DatabaseException(
"The uniqueKey supplied to pselectWithIndexKey() does not appear
to be unique or is nullable. This function expects the key to
be both UNIQUE and NOT NULL.",
$query
);
}

// If you get here, we just need to build the new array
$filteredResult[$row[$uniqueKey]] = $row;
unset($filteredResult[$row[$uniqueKey]][$uniqueKey]);
}

return $filteredResult;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me and will be very useful - this will replace a lot of the work that the biobank DAO is currently doing when querying the database.

/**
* Runs a query as a prepared statement and returns the first row as an
* associative array. Automatically adds a limit clause to the query being
Expand Down Expand Up @@ -772,6 +843,8 @@ class Database
* @param string $query The SQL SELECT query to be run
* @param array $params Values to use for binding to prepared statement
*
* @throws DatabaseException if the query selected more than one column
*
* @return array Associative array of form rowID=>value containing all values
* for the first element of the select statement
*/
Expand All @@ -796,6 +869,82 @@ class Database
return $result;
}

/**
* Runs an SQL select statement as a prepared query and re-indexes
* the results using the given unique non-nullable key in the same
* format as the pselectCol() function.
*
* @param string $query The SQL SELECT query to be run
* @param array $params Values to use for binding to the prepared statement
* @param string $uniqueKey Key to use when re-indexing, this key must be a
* single column, must be unique and must not be
* nullable
*
* @throws LorisException If the supplied key is empty or null
* @throws DatabaseException If the key is not part of the query itself or
* if there are not exactly 2 columns selected
* @throws DatabaseException If the key is not unique within the resulting set
*
* @return array Associative array of form uniqueKey=>value containing all
* value for the non-uniqueKey element of the select statement
*/
function pselectColWithIndexKey($query, $params, $uniqueKey)
{
if (is_null($uniqueKey) || empty($uniqueKey)) {
throw new LorisException(
"The pselectColWithIndexKey() function expects the uniqueKey
parameter to not be null or empty. If re-indexing on the primary
key is not necessary please use the pselectCol() function instead."
);
}

$result = $this->pselect($query, $params);

if (empty($result)) {
return $result;
}

$filteredResult = array();
// re-order the return array
foreach ($result as $row) {
$colNumber = count($row);
// Check first that the row contains the primary key supplied
if (!array_key_exists($uniqueKey, $row) || $colNumber !== 2) {
throw new DatabaseException(
"The query supplied to pselectColWithIndexKey() should only
contain the unique key and one other column in the SELECT
clause. Make sure to supply the appropriate key in the SELECT
statement to match the supplied parameter of this function.",
$query
);
}

// Check that the primary key is indeed unique to avoid overriding data
// in the result array
if (isset($filteredResult[$row[$uniqueKey]])) {
throw new DatabaseException(
"The uniqueKey supplied to pselectColWithIndexKey() does not
appear to be unique or is nullable. This function expects the
key to be both UNIQUE and NOT NULL.",
$query
);
}

// Store the value of the key for this specific row then unset that
// value from the $row array so that the only element remaining is
// the column desired and its value for this row
$uniqueKeyValue = $row[$uniqueKey];
unset($row[$uniqueKey]);

// Note: reset() rewinds array's internal pointer to the first element
// and returns the value of the first array element, in this case the
// desired column value
$filteredResult[$uniqueKeyValue] = reset($row);
}

return $filteredResult;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fail to see how this is useful in light of the first function - is the plan to make 1:1 index to value mappings? If that is the case, it does not seem that you are able to specify which column you are targeting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think of it, you're right. Doesn't pselectCol always return a single column? Isn't this just requiring an extra parameter @ridz1208?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here...
Imagine the following table (notice that the primary key is an autoincrement but they are not consecutive to distinguish them from row numbers)
screenshot from 2018-02-05 10-51-16

Ideally what you want and what is used all throughout the loris codebase is an array of the form $centerID=>$centerName as follows

$DB->pselectColWithIndexKey("SELECT CenterID, Name FROM psc WHERE CenterID<11",array(),"CenterID")
Array
(
    [1] => CCNA Testing Site
    [5] => True North Clinical Research (Halifax)
    [6] => St. Joseph's Hospital
    [7] => Centre for Diagnosis and Research on Alzheimer's Disease (CEDRA), Memory Clinic
    [8] => Centre Hospitalier de l'Université de Montréal
    [10] => Douglas Mental Health University Institute, Memory Clinic
)

But currently we can only get one of the following... in either case we will need a secondary forloop to extract the key=>value association we are looking for.

$DB->pselect("SELECT CenterID, Name FROM psc WHERE CenterID<11",array())
Array
(
    [0] => Array
        (
            [CenterID] => 1
            [Name] => CCNA Testing Site
        )

    [1] => Array
        (
            [CenterID] => 5
            [Name] => True North Clinical Research (Halifax)
        )

    [2] => Array
        (
            [CenterID] => 6
            [Name] => St. Joseph's Hospital
        )

    [3] => Array
        (
            [CenterID] => 7
            [Name] => Centre for Diagnosis and Research on Alzheimer's Disease (CEDRA), Memory Clinic
        )

    [4] => Array
        (
            [CenterID] => 8
            [Name] => Centre Hospitalier de l'Université de Montréal
        )

    [5] => Array
        (
            [CenterID] => 10
            [Name] => Douglas Mental Health University Institute, Memory Clinic
        )

)
$DB->pselectCol("SELECT Name FROM psc WHERE CenterID<11",array())
Array
(
    [0] => CCNA Testing Site
    [1] => True North Clinical Research (Halifax)
    [2] => St. Joseph's Hospital
    [3] => Centre for Diagnosis and Research on Alzheimer's Disease (CEDRA), Memory Clinic
    [4] => Centre Hospitalier de l'Université de Montréal
    [5] => Douglas Mental Health University Institute, Memory Clinic
)
$DB->pselectWithIndexKey("SELECT CenterID, Name FROM psc WHERE CenterID<11",array(),"CenterID")
Array
(
    [1] => Array
        (
            [Name] => CCNA Testing Site
        )

    [5] => Array
        (
            [Name] => True North Clinical Research (Halifax)
        )

    [6] => Array
        (
            [Name] => St. Joseph's Hospital
        )

    [7] => Array
        (
            [Name] => Centre for Diagnosis and Research on Alzheimer's Disease (CEDRA), Memory Clinic
        )

    [8] => Array
        (
            [Name] => Centre Hospitalier de l'Université de Montréal
        )

    [10] => Array
        (
            [Name] => Douglas Mental Health University Institute, Memory Clinic
        )

)

I think this function might just be a better version of pselectCol but I dont think it is redundant with pselectWithIndexKey because it returns a flatened array directly useable by the code requesting it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@driusan did the explanation make it clearer ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the fact that I didn't see the comment until now, yes.

/**
* Runs a query as a prepared statement and returns the value of the first
* column of the first row
Expand Down