new $DB->get_existing_record() DML method

new $DB->get_existing_record() DML method

by Petr Skoda -
Number of replies: 18
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
At present there are very many places in code like:
if (!$record = $DB->ger_record('table', $conditions)) {
 print_error('generalproblem');
}
It is usually 66% of wasted lines and one extra language string with information that does not help with real problem solution anyway.

I am proposing to add new method to moodle_database class
function get_existing_record($tablename, array $conditions, $errorcode='', $module='');

This method would work like get_record() but it would throw exception if record not found. Eloy is strongly opposing this because there is nothing like this in standard SQL wink

What do you think, should we have such method in 2.0?

Before:
if (!empty($forum)) { // User is starting a new discussion in a forum if (! $forum = $DB->get_record('forum', array('id' => $forum))) { print_error('invalidforumid', 'forum'); } } else if (!empty($reply)) { // User is writing a new reply if (! $parent = forum_get_post_full($reply)) { print_error('invalidparentpostid', 'forum'); } if (! $discussion = $DB->get_record('forum_discussions', array('id' => $parent->discussion))) { print_error('notpartofdiscussion', 'forum'); } if (! $forum = $DB->get_record('forum', array('id' => $discussion->forum))) { print_error('invalidforumid'); } } if (! $course = $DB->get_record('course', array('id' => $forum->course))) { print_error('invalidcourseid'); } if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) { // For the logs print_error('invalidcoursemodule'); } else { $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); }
After:
if (!empty($forum)) { $forum = $DB->get_existing_record('forum', array('id' => $forum)); } else if (!empty($reply)) { $parent = forum_get_post_full($reply); $discussion = $DB->get_existing_record('forum_discussions', array('id' => $parent->discussion)); $forum = $DB->get_existing_record('forum', array('id' => $discussion->forum)); } $course = $DB->get_existing_record('course', array('id' => $forum->course)); $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, true); $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
Average of ratings: Useful (1)
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by Eloy Lafuente (stronk7) -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Peer reviewers Picture of Plugin developers Picture of Testers
Well,

if waste of lines is the key then, perhaps we could have something like:

list($c, $d, $p, $r, $cm, $ctx) = forum_retrofit_as_much_as_possible($forum, $reply); // obviously, joking


just one line and same functionality and will work in all the forum pages! tongueout big grin

Seriously, IMO, that's "polluting" the database abstraction layer with functions not doing "standard" database tasks.

I understand and agree 100% that, if possible, code can / must be reduced / centralised. 100%. But the database layer cannot be the place for all that extra functionality.

Some months ago we agreed that all the get_xxx() (read) functions will be returning false/empty if no data is found, while all the write operations will be throwing exception if something wrong happens.

I think it's a simple / correct behaviour and any exception to that simple principle must be avoided (alternatively, that behaviour can be re-analysed / changed, of course, but always as a whole).

I can imagine ppl wanting to throw exceptions in other get_records/recordset() functions, why not? The justification is exactly the same than the one exposed above for the get_record() function. Will we create then all those get_existing_records/existing_recordset() new functions? Just to provide Moodle with the ability to throw exceptions in all the read database functions? Sincerely I think no.

Or imagine you want to perform something different from throwing an exception in a lot of insert_record() uses over the whole Moodle, say, silly example I know, send one email. Will we create one insert_record_send_email() method in the DB abstraction layer just for that? Sincerely I think no. I know the example is ridiculous (so don't try to fix it wink ), but the underlying question is the same. And the answer should be the same too: No.

Also, the name is not clear IMO: get_existing_record(), obviously the DB only is going to give you existing records, until now, I don't know DBs able to "imagine" records on the fly. big grin

Finally, I know we are talking only about adding one simple method to $DB (a few lines performing one get_record + throw exception), in order to save some lines here and there (lazy developers!) and, from that perspective, it doesn't hurt me too much.

Instead, my concerns are about how that tiny change affects to the general behaviour / status-quo existing until now. And if that can be used, in the future, to justify abusing the DB layer by adding more and more, said with irony, "cool" features continuously. sad

One DB must do whatever one DB does. Our $DB too, of course. Nothing else. -1 here (although I guess that the vast majority of you, fellow and lazy - like me- developers, will vote the opposite) wink

Ciao smile
In reply to Eloy Lafuente (stronk7)

Re: new $DB->get_existing_record() DML method

by Petr Skoda -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
All DML and DDL operations throw exception if something unexpected goes wrong, it is nothing write specific.

With this logic you would have to remove all $DB->get_record() methods because they are not standard either wink Where did you see returning false from SQL if record not found? Why should be exception more non-standard than false?

The major point here is that get_record() returns mixed results (false or object), but the rest is not returning mixed results (only arrays, bools or ints, but always only one type from each method), so in fact throwing exception makes much more sense to me instead of immediately checking results and printing silly error notices.

Sometimes we need to know if record exist and then use it, but majority of cases we expect that the record must exist.
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by sam marshall -
Picture of Core developers Picture of Peer reviewers Picture of Plugin developers
Yes please!

Rather than a new method, I would kind of prefer if get_record always does this (anybody requiring the current behaviour should do get_records and look for count 0). However failing that change, the suggested one is a big improvement.

Where are the 1,000 Eloys that are supposed to be expressing their opinion against this? I only see one. smile

--sam
In reply to sam marshall

Re: new $DB->get_existing_record() DML method

by Petr Skoda -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
Yes sam,

since 1.6 or something like that we were forced to start using get_records() much more often because the get_record() does not tolerate more returned records anyway sad

I would personally vote for putting the exception directly into get_record() but I am a bit too afraid to break BC here sad
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by Eloy Lafuente (stronk7) -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Peer reviewers Picture of Plugin developers Picture of Testers
Petr said: I would personally vote for putting the exception directly into get_record() but I am a bit too afraid to break BC here

probably +1 here, though yes, can have a big impact. Just a quick grep, looking for something that surely will break:

grep -r 'if.*(.*get_record(' *

returns > 1,200 occurrences sad

PS: And what about 1 extra parameter in the function call, defaulting to current behaviour (return false) but allowing to change it (throw exception). Perhaps, also, time to drop the nasty "multiple" parameter.
In reply to sam marshall

Re: new $DB->get_existing_record() DML method

by Eloy Lafuente (stronk7) -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Peer reviewers Picture of Plugin developers Picture of Testers
Sam said: Where are the 1,000 Eloys that are supposed to be expressing their opinion against this? I only see one.

...awaiting you, hidden somewhere, surely in a dark place, presto to give you one surprise... suddenly, perhaps causing some of your own subsystems to start throwing exceptions here and there... causing you to immediately regret trying to do silly jokes... so, dear Sam, be careful and take cover your back!

big grin tongueout evil dead

Ciao smile
In reply to Eloy Lafuente (stronk7)

Re: new $DB->get_existing_record() DML method

by Petr Skoda -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
hmm,
public function get_record($table, array $conditions, $fields='*', $ignoremultiple=false)

We would have to replace $ignoremultiple by $throwexceptionifnotfound smile
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by Nicolas Connault -
Yes Petr, that's exactly what I was thinking. The only problem is that $ignoremultiple is not quite the same as $throwexceptionifnotfound. Having a different meaning / purpose, we would have to be very careful about how the current code "adopts" this new interpretation of the 4th param.
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
I completely agree that encapsulating the
if (!($thing = $DB->get_record(...)) {
throw new moodle_exception(...);
}
in a function would be a good thing.

I am normally pretty picky about APIs, and keeping them clean, but I do understand why Eloy thinks this should not be on $DB. It seems like a sensible part of the DB API to me.

I don't like Petr's idea of adding $errorcode='', $module='' arguments. If the specific error message is important, we should leave the if and the throw in the calling code.

However, for the many situations where you are getting a record really should exist (for example $DB->get_record('course_categories', array('id' => $course->id)); - a place where you are following a foreign key link) then the error checking in the calling script is unnecessary clutter.

About re-purposing the $ignoremultiple=false parameter. We could rename that to $strictness, then create some constants

IGNORE_MISSING = 0
IGNORE_MULTIPLE = 1
MUST_EXIST = 2

that is backwards compatible, and the constant names hopefully would make the code more readable.
Average of ratings: Useful (2)
In reply to Tim Hunt

Re: new $DB->get_existing_record() DML method

by Petr Skoda -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
I love MUST_EXIST constant big grin
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by Penny Leach -
I like the idea of $strictness constants and using the same method rather than adding another one.

(Adding paint liberally)
In reply to Tim Hunt

Re: new $DB->get_existing_record() DML method

by Hubert Chathi -
Is the intent for the new constants to be mutually exclusive, or would you be able to pass "IGNORE_MULTIPLE | MUST_EXIST" (=3) to be able to both ignore multiple values, and to throw an exception if nothing exists?
In reply to Hubert Chathi

Re: new $DB->get_existing_record() DML method

by Petr Skoda -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
+1 for exclusive, maybe we do not even need the other constants, just true/1, false/0 and MUST_EXIST/2 would be enough. We never used the ignore multiple flag much.
In reply to Petr Skoda

Re: new $DB->get_existing_record() DML method

by Martin Dougiamas -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Particularly helpful Moodlers Picture of Plugin developers Picture of Testers
This sounds like a good idea in that it makes the code shorter (and hopefully faster to write!) without losing readability.

Perhaps $DB->must_get_record() works better as a name (or family of names if this is going to be replicated consistently for other $DB->get functions).

I do see Eloy's point about the purity of the class, but I think if we see these as "add-ons" to DML then it's OK with me. The point of an API is to be as useful as possible after all.
Average of ratings: Useful (1)
In reply to Martin Dougiamas

Re: new $DB->get_existing_record() DML method

by Petr Skoda -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers
I have added new $strictness parameter to get_record() and get_filed() family of methods, adding new methods with different prefix would require at least 6 new methods - thus making the API significantly larger.

The code is in cvs MDL-19689, unittests included. It is not yet used anywhere, so there is still possibility to revert it or change implementation. Any more thought and ideas are welcome.

Thanks everybody!

PS: please have a look at the code/ in-line docs and test if possible wink