Problem/Motivation

If you create any kind of workflow, attach it to a node type and then go to the node add page you trigger the following PDO exception:

PDOException: (SELECT t.tid, t.target_sid as state_id, s.state AS state_name, s.weight AS state_weight FROM {workflow_transitions} t INNER JOIN {workflow_states} s ON s.sid = t.target_sid WHERE t.sid = :sid AND s.status = 1 ORDER BY state_weight) UNION (SELECT s.sid as tid, s.sid as state_id, s.state as state_name, s.weight as state_weight FROM {workflow_states} s WHERE s.sid = :sid AND s.status = 1) ORDER BY state_weight, state_id (prepared: (SELECT t.tid, t.target_sid as state_id, s.state AS state_name, s.weight AS state_weight FROM "WORKFLOW_TRANSITIONS" t INNER JOIN "WORKFLOW_STATES" s ON s.sid = t.target_sid WHERE t.sid = :sid AND s.status = 1 ORDER BY state_weight) UNION (SELECT s.sid as tid, s.sid as state_id, s.state as state_name, s.weight as state_weight FROM "WORKFLOW_STATES" s WHERE s.sid = :sid AND s.status = 1) ORDER BY state_weight, state_id ) e: SQLSTATE[HY000]: General error: 907 OCIStmtExecute: ORA-00907: missing right parenthesis (ext\pdo_oci\oci_statement.c:148) args: Array ( [:sid] => 1 ) in workflow_allowable_transitions() (line 1336 of D:\wwwroot\mytc\sites\all\modules\contrib\workflow\workflow.module).

I'm not sure exactly if this is a bug with Workflow or with the Oracle driver. I'm cross posting the issue between the two projects.

Thanks,

Comments

minoroffense’s picture

minoroffense’s picture

The offending code is the following:

  // Yes. This is a monster. This should, in all absolute seriousness, be cut down to size.
  // I also REALLY do not like the insecure field pieces in here.
  $results = db_query(''
      . '(SELECT t.tid, t.' . $field . ' as state_id, s.state AS state_name, s.weight AS state_weight '
        . 'FROM {workflow_transitions} t '
        . 'INNER JOIN {workflow_states} s ON s.sid = t.' . $field . ' '
        . 'WHERE t.' . $field_where . ' = :sid AND s.status = 1 '
        . 'ORDER BY state_weight) '
    . 'UNION '
      . '(SELECT s.sid as tid, s.sid as state_id, s.state as state_name, s.weight as state_weight '
        . 'FROM {workflow_states} s '
        . 'WHERE s.sid = :sid AND s.status = 1) '
    . 'ORDER BY state_weight, state_id',
    array(':sid' => $sid));
  foreach ($results as $transition) {

What if we converted this to use db_select? Let Drupal generate the SQL code as required instead of using db_query directly?

minoroffense’s picture

For example

  // Top Query
  $query = db_select('workflow_transitions', 't');
  $query->innerJoin('workflow_states', 's', 's.sid = t.target_sid');
  $query->addField('t', 'tid');

  // Check the direction of the query
  if ($dir == 'to') {
    $query->addField('t', 'target_sid', 'state_id');
    $query->condition('t.sid', $sid);
  }
  else {
    $query->addField('t', 'sid', 'state_id');
    $query->condition('t.target_sid', $sid);
  }

  $query->addField('s', 'state', 'state_name');
  $query->addField('s', 'weight', 'state_weight');

  $query->condition('s.status', 1);
  //$query->orderBy('state_weight');

  // Bottom Query
  $query2 = db_select('workflow_states', 's');

  $query2->addField('s', 'sid', 'tid');
  $query2->addField('s', 'sid', 'state_id');
  $query2->addField('s', 'state', 'state_name');
  $query2->addField('s', 'weight', 'state_weight');

  $query2->condition('s.sid', $sid);
  $query2->condition('s.status', 1);

  $query2->orderBy('state_weight');
  $query2->orderBy('state_id');

  // Add the union of the two queries
  $query->union($query2, 'UNION');

  $results = $query->execute();

By using "addField" you can build in the conditions for the direction of the query without any inline string stuff as the original has.

Not tested at all, but short of any typos it should work.

EDIT: Tested and it works with MySQL. The location of the orderBy methods is important (seem to only be available on the second query)

minoroffense’s picture

Alright, so that fixed the syntax error, but now I get another issue

PDOException: SELECT t.tid AS tid, t.target_sid AS state_id, s.state AS state_name, s.weight AS state_weight FROM {workflow_transitions} t INNER JOIN {workflow_states} s ON s.sid = t.target_sid WHERE (t.sid = :db_condition_placeholder_0) AND (s.status = :db_condition_placeholder_1) ORDER BY state_weight ASC UNION SELECT s.sid AS tid, s.sid AS state_id, s.state AS state_name, s.weight AS state_weight FROM {workflow_state} s WHERE (s.sid = :db_condition_placeholder_2) AND (s.status = :db_condition_placeholder_3) ORDER BY state_weight ASC, state_id ASC (prepared: SELECT t.tid AS tid, t.target_sid AS state_id, s.state AS state_name, s.weight AS state_weight FROM "WORKFLOW_TRANSITIONS" t INNER JOIN "WORKFLOW_STATES" s ON s.sid = t.target_sid WHERE (t.sid = :db_condition_placeholder_0) AND (s.status = :db_condition_placeholder_1) ORDER BY state_weight ASC UNION SELECT s.sid AS tid, s.sid AS state_id, s.state AS state_name, s.weight AS state_weight FROM "WORKFLOW_STATE" s WHERE (s.sid = :db_condition_placeholder_2) AND (s.status = :db_condition_placeholder_3) ORDER BY state_weight ASC, state_id ASC ) e: SQLSTATE[HY000]: General error: 933 OCIStmtExecute: ORA-00933: SQL command not properly ended (ext\pdo_oci\oci_statement.c:148) args: Array ( [:db_condition_placeholder_0] => 1 [:db_condition_placeholder_1] => 1 [:db_condition_placeholder_2] => 1 [:db_condition_placeholder_3] => 1 ) in workflow_allowable_transitions() (line 1378 of D:\wwwroot\mytc\sites\all\modules\contrib\workflow\workflow.module).

Here's the generated SQL

SELECT t.tid AS tid, t.target_sid AS state_id, s.state AS state_name, s.weight AS state_weight
FROM 
{workflow_transitions} t
INNER JOIN {workflow_states} s ON s.sid = t.target_sid
WHERE  (t.sid = :db_condition_placeholder_0) AND (s.status = :db_condition_placeholder_1) 
ORDER BY state_weight ASC, state_id ASC UNION ALL SELECT s.sid AS tid, s.sid AS state_id, s.state AS state_name, s.weight AS state_weight
FROM 
{workflow_state} s
WHERE  (s.sid = :db_condition_placeholder_0) AND (s.status = :db_condition_placeholder_1) 

Can anyone see something wrong with the generated UNION query?

minoroffense’s picture

Status: Active » Needs review

I've tested the above rewrite of the query in comment #3 and it seems to work in both Oracle and MySQL.

Do you require a patch file or is this code block acceptable?

Bastlynn’s picture

A patch file would be most useful once the issue with the union is cleared up. I'll take a closer look at the union once I've had my coffee and taken out some of the other patch requests in the queue.

David_Rothstein’s picture

A likely cause for the UNION issue is this core bug: #1145076: UNION queries don't support ORDER BY clauses

bircher’s picture

Thank you for addressing this issue.
I have the same problem with SQLite.

I think it would not be a bad idea to split the query up, so I would also welcome a patch file for the fork of minorOffense.
The patch works for me and my happy SQLight, and I would happily rtbc the patch so it can be applied.

nancydru’s picture

Status: Needs review » Needs work

The inner join depends on the $dir param. It's not taken into account in #3.

nancydru’s picture

Assigned: Unassigned » nancydru
Status: Needs work » Needs review

Here's what I have:

function workflow_allowable_transitions($sid, $dir = 'to', $roles = 'ALL') {
  $transitions = array();
  
  // Main query from transitions table.
  $query = db_select('workflow_transitions', 't')
    ->fields('t', array('tid'));
    
  if ($dir == 'to') {
    $query->innerJoin('workflow_states', 's', 's.sid = t.target_sid');
    $query->addField('t', 'target_sid', 'state_id');
    $query->condition('t.sid', $sid);
  }
  else {
    $query->innerJoin('workflow_states', 's', 's.sid = t.sid');
    $query->addField('t', 'sid', 'state_id');
    $query->condition('t.target_sid', $sid);
  }
  
  $query->addField('s', 'state', 'state_name');
  $query->addField('s', 'weight', 'state_weight');
  $query->addField('s', 'sysid');
  $query->condition('s.status', 1);
  
  // Now let's get the current state.
  $query2 = db_select('workflow_states', 's');
  $query2->addField('s', 'sid', 'tid');
  $query2->addField('s', 'sid', 'state_id');
  $query2->addField('s', 'state', 'state_name');
  $query2->addField('s', 'weight', 'state_weight');
  $query2->addField('s', 'sysid');
  $query2->condition('s.status', 1);
  $query2->condition('s.sid', $sid);

  $query2->orderBy('state_weight');
  $query2->orderBy('state_id');
  
  // Add the union of the two queries
  $query->union($query2, 'UNION');

  $results = $query->execute();
  
  foreach ($results as $transition) {
    if ($roles == 'ALL'  // Superuser.
      || $sid == $transition->state_id // Include current state for same-state transitions.
      || workflow_transition_allowed($transition->tid, $roles)) {
      $transitions[] = $transition;
    }
  }
  return $transitions;
}

This seems to function as the old code did, at least in MySql.

nancydru’s picture

Issue tags: +1.2 release blocker

Adding release blocker

nancydru’s picture

Status: Needs review » Fixed

My change has been committed

Automatically closed -- issue fixed for 2 weeks with no activity.