One of my long standing pet-peeves has been the tendency/standard to place queries, documentation, and any long string of text on one line. This is just plain unreadable.

Back in high school my Algebra teacher used to get on students that tried to put their whole problem on one line. Her reasoning was simple, the human eye is designed to track vertically. Meaning we can more easily read and understand something in a vertical format rather than a horizontal one.

Interestingly, I have noticed this to hold true in every instance I have run into it.

In my contrib modules (as I did before I came to Drupal) I write all my queries on multiple lines. They are very easy to read, even the long ones.

Since Drupal core sets a standard for contrib, even if we don't see the large edge cases in core I can vogue for them in contrib. I won't point to the code involved, but I came across a query of over 1,000 characters in length with numerous joins, sub-selects, etc, etc. It was completely impossible to read, much less debug. I had to copy it into a text editor, place it on numerous lines, and indent it as you would code. It then became a 1000x easier to read and modify.

The same holds true with the long strings of text that are used for descriptions and documentation. It also seems inconsistent to limit documentation strings to 80 characters with a passion and ignore their counterparts because they are contained within ' '.

Before proposing a standard of making any moves towards solving this issue I would like to know if anyone else agrees with me. Even if you don't like the simple solutions I would like to know if you have a hard time reading queries and long strings of text.

Comments

mikey_p’s picture

I'm in favor of this, except in the case of strings, which are often tedious to break up over multiple lines, depending on their length and so on.

I would propose another major area that would benefit from this, are function calls with many many long parameters.

For example l() with a long link string, concatenated URL, and an options array with 2 or 3 nested arrays in it.

boombatower’s picture

Yes, that is a good example and something I also do.

If we can a few more solid opinions, lets start on an official standard.

boombatower’s picture

Status: Active » Needs review

So lets just do queries for now.

I find it a bit odd that we write all DBTNG queries on multiple lines and leave db_query()'s on one MASSIVE line.

For example:

$result = db_query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(':schema' => $schema, ':table' => $table_name, ':default' => '%nextval%'));

and

$action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();

Compared with:

  $result = db_select('users', 'u')
    ->fields('u', array('uid', 'data'))
    ->condition('uid', $sandbox['last_user_processed'], '>')
    ->where('data IS NOT NULL')
    ->range(0, $limit)
    ->execute();

Those are just plain unreadable...and I have found MUCH MUCH worse in contrib. I literally have to copy them into a seperate text document and format them on separate lines so I can read them.

Just as we don't put multiple statements on the same line...even though we can...lets not do it with SQL.

I propose the following rules (as the core of this, I'm sure we'll find edge cases or weird things):

  1. If query is made up of two parts it can stay on one line.
  2. If query is made up of more than two parts each part must be placed on a separate line.

Taking that into account the above queries become (readable):

$result = db_query("SELECT column_name, data_type, column_default
                    FROM information_schema.columns 
                    WHERE table_schema = :schema AND table_name = :table
                    AND (
                      data_type = 'bytea'
                      OR (numeric_precision IS NOT NULL AND column_default LIKE :default)
                    )", array(':schema' => $schema, ':table' => $table_name, ':default' => '%nextval%'));

and

$action = db_query("SELECT callback, parameters
                    FROM {actions}
                    WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();

The parenthesis are formatted just like array definitions and the nested (... AND ...) is left on one line like we do if conditions. We can add more rules if we like...but that is the basic premise.

moshe weitzman’s picture

Good initiative. I would like a little more leniency though. I would like the option to keep each part of the clause on same line. So I think the WHERE clause below should be valid.

SELECT foo
FROM bar
WHERE status=1 AND uid=3

Also, lets leave non-SQL strings out of this. Also note that the point of this is not to stay within 80 chars necesarily. If a single SELECT line exceeds 80 chars, so be it. Most editors handle that fine.

As an aside, 2 row output of the verbose results in run-tests.sh annoys me. So one man's beauty is another man's vomit.

boombatower’s picture

Title: DX: Take advantage of the return key, readable queries and strings » DX: Take advantage of the return key, readable queries

I have no qualms about 80 characters...we execeede it ALL OVER.

I think where clauses on one line is fine with where clauses on one line as long as they are logical. Same reasoning behind large if conditions.

SELECT foo
FROM bar
WHERE status = 1
AND uid = 3

would also be fine and:

SELECT foo
FROM bar
WHERE status = 1
AND uid = 3
AND more = 5

Any yes, as I wrote, lets keep this to SQL.

mikey_p’s picture

Given that DTBNG is going to eliminate many of these long queries, I think including function parameters, and multiple condition conditionals is just as critical (although there appears to be a standard for that, in places):

Take the following line (condition) from system.admin.inc:

            if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op'])) ||
                (isset($required_version['preg']) && !preg_match($required_version['preg'], $current_version))) {

That one is bad, but not too bad as it's already wrapped after the ||, but take a single line statement like this:


    $status_long .= t('This module requires PHP version @php_required and is incompatible with PHP version !php_version.', array('@php_required' => $php_required, '!php_version' => phpversion()));

That's 197 characters, and extremely hard to read/edit.

gerhard killesreiter’s picture

Having everything on one line is actually part of the Drupal coding standard for over 8 years now. I see no reason to drop it.

boombatower’s picture

This issue is trying to change that and states the reasons for it.

moshe weitzman’s picture

@killes - lots of smart and dumb things have lasted 8 years. This is a meaningless comment.

"I see no reason to change it." If you disagree with the reasons stated in this issue, please describe why. As it stands, this sentence does not advance the conversation either.

dries’s picture

Personally, I think we're too aggressive with breaking things up in multiple lines. Many of the short queries we have should really be on one line, IMO.

I think this:
$result = db_query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(':schema' => $schema, ':table' => $table_name, ':default' => '%nextval%'));

is easier to read and understand than

  $result = db_select('users', 'u')
    ->fields('u', array('uid', 'data'))
    ->condition('uid', $sandbox['last_user_processed'], '>')
    ->where('data IS NOT NULL')
    ->range(0, $limit)
    ->execute();

Personally, I don't feel like we need to introduce rules or guidelines for this. Coding standards are very important, but let's not try to institutionalize everything.

Anyway, given all the things we need to do before code freeze, this is super-low priority.

catch’s picture

I also prefer static queries on one line, it's more scannable.

if a query is really, really long, then more times than not it's likely to be using db_select() anyway, nothing stops incredibly long queries being split across multiple lines in particular cases - if coder.module is enforcing one line, then that check could always be relaxed beyond a certain number of characters.

boombatower’s picture

To clarify...I'm not comparing DBTNG queries to static ones...I would have to agree that static are more clear. This is comparing static one line queries very multiline. SQL is a language just like PHP and it provides a syntax of its own and can be formatted...

Seems like in early comments I have support and now later comments are against this...so seems somewhat 50/50.

I don't think there is anyway to argue that a single line query is easier to read...given science..and general experience. If we decide that we don't want to make a standard for this is one thing.

catch’s picture

Compare:

SELECT foo FROM bar WHERE status = 1 AND uid = 3
SELECT foo
FROM bar
WHERE status = 1
AND uid = 3

Now compare:

I walked quickly to the doctor because I hurt my arm.

to:

I walked quickly
to the doctor
because I
hurt my arm.

Except it's even worse, because with SQL, it's more like:

I WALKED quickly
TO the doctor
BECAUSE i
HURT my arm

It's a big wall of verbs and prepositions. However on one line, you at least get the connection a bit more

I WALKED quickly TO the doctor BECAUSE i HURT my arm

I know that's a stupid example, but I couldn't find another way to explain why I find one line more readable.

boombatower’s picture

The SQL example is great...I can quickly find the WHERE clause and everything.

I gave a nice, more complex example in #3:

$result = db_query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(':schema' => $schema, ':table' => $table_name, ':default' => '%nextval%'));

vs

$result = db_query("SELECT column_name, data_type, column_default
                    FROM information_schema.columns
                    WHERE table_schema = :schema AND table_name = :table
                    AND (
                      data_type = 'bytea'
                      OR (numeric_precision IS NOT NULL AND column_default LIKE :default)
                    )", array(':schema' => $schema, ':table' => $table_name, ':default' => '%nextval%'));

The first is nice to read like a sentence, but good luck debugging or editing it.

I can provide tons of examples...essentially and sql query that is more then SELECT foo FROM bar WHERE 1 = 2...(even that) just gets hard to read/edit/and understand.

My personal favorite:

(SELECT pd.ftid, pd.cid, n.nid AS issue_id, n.title AS issue_title, n.uid, pi.pid, pi.rid, f.filepath AS patch_url, p.title AS project, u.name AS submitter$select FROM {pift_data} pd INNER JOIN {project_issues} pi ON pd.nid = pi.nid INNER JOIN {files} f ON pd.fid = f.fid INNER JOIN {node} n ON n.nid = pi.nid INNER JOIN {node} p ON p.nid = pi.pid INNER JOIN {users} u ON u.uid = pd.uid$join WHERE ((pd.status = %d AND pd.timestamp < %d) OR (pd.status = %d AND pd.timestamp < %d)) AND pd.cid = %d$where) UNION (SELECT pd.ftid, pd.cid, n.nid AS issue_id, n.title AS issue_title, n.uid, pi.pid, pi.rid, f.filepath AS patch_url, p.title AS project, u.name AS submitter$select FROM {pift_data} pd INNER JOIN {project_issues} pi ON pd.nid = pi.nid INNER JOIN {files} f ON pd.fid = f.fid INNER JOIN {node} n ON n.nid = pi.nid INNER JOIN {node} p ON p.nid = pi.pid INNER JOIN {users} u ON u.uid = pd.uid$join WHERE ((pd.status = %d AND pd.timestamp < %d) OR (pd.status = %d AND pd.timestamp < %d)) AND pd.cid <> %d$where) ORDER BY ftid

I had to place in external editor and format it just to see what the .... was going on. The following is a more complex query that is format...not the one above as I don't have a formatted copy:

UPDATE {pift_test}
SET status = %d
WHERE type = %d
AND id IN (
  SELECT f.fid
  FROM {files} f
  LEFT JOIN {upload} u
    ON f.fid = u.fid
  LEFT JOIN {comment_upload} cu
    ON f.fid = cu.fid

  JOIN {project_issues} pi
    ON (u.nid = pi.nid OR cu.nid = pi.nid)
  JOIN {pift_project} p
    ON pi.pid = p.pid
  JOIN {project_release_nodes} r
    ON pi.rid = r.nid

  WHERE pi.sid IN (" . implode(',', array_fill(0, count($sids), '%d')) . ")
  AND r.tag REGEXP '%s'
)
AND status > %d
AND last_tested < %d

It seems that I need to demonstrate the extreme to get my point across. Same reason books are written on pages and WRAP the text. Same reason web sites are designed with a hard width instead of fluid...please read easer when in a column.

m = 2
x = 1
y = mx + b

you do math vertically you don't but that on one line.

Notice the MySQL example: http://dev.mysql.com/doc/refman/5.0/en/select.html

Although you may never see these extreme examples in core...contrib has them...and we should set a standard.

To compare with code, which we separate into "statements" just like I want to to do with SQL statements.

$i = 0;
if ($i < 2) {
echo 'foo';
}

instead of:

$i = 0; if ($i < 2) { echo 'foo'; }

which of course reads like English: i is 0 which is less then 2 so print the word foo.

Doesn't mean we write it like that.

catch’s picture

pd.uid$join
Means this would have to use db_select() in D7, which means out current coding standards would already split it across multiple lines. If you have a 1,000 character query that's static, by all means split it, but why make a coding standard for it?

boombatower’s picture

So when I come to help contribute to a contrib module...I don't need to spend time formating a query so I can read it....

We have a difference of opinion that has no 100% answer...I've made my case...I have nothing more to say.

boombatower’s picture

Status: Needs review » Closed (won't fix)

Instead of letting this rot in the issue queue like the other 100s of issues...lets won't fix this since it won't happen.