root

Changeset 707

Show
Ignore:
Timestamp:
09/22/09 00:56:11 (10 months ago)
Author:
wbond
Message:

Fixed tickets #317, #318, #320 - Cleaned up >< intersection operator for fRecordSet::build(), added support for NULL second value and added !~, &~, >< operators and OR conditions to fRecordSet::filter()

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • fActiveRecord.php

    r701 r707 Hide Line Numbers
    1616 * @link       http://flourishlib.com/fActiveRecord 
    1717 *  
    18  * @version    1.0.0b44 
     18 * @version    1.0.0b45 
     19 * @changes    1.0.0b45  Added support for `!~`, `&~`, `><` and OR comparisons to ::checkConditions(), made object handling in ::checkConditions() more robust [wb, 2009-09-21] 
    1920 * @changes    1.0.0b44  Updated code for new fValidationException API [wb, 2009-09-18] 
    2021 * @changes    1.0.0b43  Updated code for new fRecordSet API [wb, 2009-09-16] 
     
    177178     
    178179    /** 
     180     * Checks to see if a record matches a condition 
     181     *  
     182     * @internal 
     183     *  
     184     * @param  string $operator  The record to check 
     185     * @param  mixed  $value     The value to compare to 
     186     * @param  mixed $result     The result of the method call(s) 
     187     * @return boolean  If the comparison was successful 
     188     */ 
     189    static private function checkCondition($operator, $value, $result) 
     190    { 
     191        $was_array = is_array($value); 
     192        if (!$was_array) { $value = array($value); } 
     193        foreach ($value as $i => $_value) { 
     194            if (is_object($_value)) { 
     195                if ($_value instanceof fActiveRecord) { 
     196                    continue; 
     197                } 
     198                if (method_exists($_value, '__toString')) { 
     199                    $value[$i] = $_value->__toString(); 
     200                }    
     201            }    
     202        } 
     203        if (!$was_array) { $value = $value[0]; } 
     204         
     205        $was_array = is_array($result); 
     206        if (!$was_array) { $result = array($result); } 
     207        foreach ($result as $i => $_result) { 
     208            if (is_object($_result)) { 
     209                if ($_result instanceof fActiveRecord) { 
     210                    continue; 
     211                } 
     212                if (method_exists($_result, '__toString')) { 
     213                    $result[$i] = $_result->__toString(); 
     214                }    
     215            }    
     216        } 
     217        if (!$was_array) { $result = $result[0]; } 
     218         
     219        $match_all   = $operator == '&~'; 
     220        $negate_like = $operator == '!~'; 
     221         
     222        switch ($operator) { 
     223            case '&~': 
     224            case '!~': 
     225            case '~': 
     226                if (!$match_all && !$negate_like && !is_array($value) && is_array($result)) { 
     227                    $value = fORMDatabase::parseSearchTerms($value, TRUE); 
     228                }    
     229                     
     230                settype($value, 'array'); 
     231                settype($result, 'array'); 
     232                 
     233                if (count($result) > 1) { 
     234                    foreach ($value as $_value) { 
     235                        $found = FALSE; 
     236                        foreach ($result as $_result) { 
     237                            if (fUTF8::ipos($_result, $_value) !== FALSE) { 
     238                                $found = TRUE; 
     239                            } 
     240                        } 
     241                        if (!$found) { 
     242                            return FALSE; 
     243                        }    
     244                    } 
     245                } else { 
     246                    $found = FALSE; 
     247                    foreach ($value as $_value) { 
     248                        if (fUTF8::ipos($result[0], $_value) !== FALSE) { 
     249                            $found = TRUE; 
     250                        } elseif ($match_all) { 
     251                            return FALSE; 
     252                        } 
     253                    } 
     254                    if ((!$negate_like && !$found) || ($negate_like && $found)) { 
     255                        return FALSE; 
     256                    } 
     257                }     
     258                break; 
     259             
     260            case '=': 
     261                if ($value instanceof fActiveRecord && $result instanceof fActiveRecord) { 
     262                    if (get_class($value) != get_class($result) || !$value->exists() || !$result->exists() || self::hash($value) != self::hash($result)) { 
     263                        return FALSE; 
     264                    } 
     265                     
     266                } elseif (is_array($value) && !in_array($result, $value)) { 
     267                    return FALSE; 
     268                         
     269                } elseif (!is_array($value) && $result != $value) { 
     270                    return FALSE;    
     271                } 
     272                break; 
     273                 
     274            case '!': 
     275                if ($value instanceof fActiveRecord && $result instanceof fActiveRecord) { 
     276                    if (get_class($value) == get_class($result) && $value->exists() && $result->exists() && self::hash($value) == self::hash($result)) { 
     277                        return FALSE; 
     278                    } 
     279                     
     280                } elseif (is_array($value) && in_array($result, $value)) { 
     281                    return FALSE;    
     282                     
     283                } elseif (!is_array($value) && $result == $value) { 
     284                    return FALSE;    
     285                } 
     286                break; 
     287             
     288            case '<': 
     289                if ($result >= $value) { 
     290                    return FALSE;    
     291                } 
     292                break; 
     293             
     294            case '<=': 
     295                if ($result > $value) { 
     296                    return FALSE;    
     297                } 
     298                break; 
     299             
     300            case '>': 
     301                if ($result <= $value) { 
     302                    return FALSE;    
     303                } 
     304                break; 
     305             
     306            case '>=': 
     307                if ($result < $value) { 
     308                    return FALSE;    
     309                } 
     310                break; 
     311        } 
     312         
     313        return TRUE;         
     314    } 
     315     
     316     
     317    /** 
    179318     * Checks to see if a record matches all of the conditions 
    180319     *  
     
    190329             
    191330            // Split the operator off of the end of the method name 
    192             if (in_array(substr($method, -2), array('<=', '>=', '!=', '<>'))) { 
     331            if (in_array(substr($method, -2), array('<=', '>=', '!=', '<>', '!~', '&~', '><'))) { 
    193332                $operator = strtr( 
    194333                    substr($method, -2), 
     
    204343            } 
    205344             
    206             $multi_method = FALSE; 
    207              
    208             if ($operator == '~' && strpos($method, '|')) { 
    209                 $multi_method = TRUE; 
    210                 $result = array(); 
    211                 foreach(explode('|', $method) as $_method) { 
    212                     $result[] = $record->$_method();     
    213                 } 
     345            if (preg_match('#(?<!\|)\|(?!\|)#', $method)) { 
     346                 
     347                $methods   = explode('|', $method); 
     348                $values    = $value; 
     349                $operators = array(); 
     350                 
     351                foreach ($methods as &$_method) { 
     352                    if (in_array(substr($_method, -2), array('<=', '>=', '!=', '<>', '!~', '&~', '><'))) { 
     353                        $operators[] = strtr( 
     354                            substr($_method, -2), 
     355                            array( 
     356                                '<>' => '!', 
     357                                '!=' => '!' 
     358                            ) 
     359                        ); 
     360                        $_method     = substr($_method, 0, -2); 
     361                    } elseif (!ctype_alnum(substr($_method, -1))) { 
     362                        $operators[] = substr($_method, -1); 
     363                        $_method     = substr($_method, 0, -1); 
     364                    } 
     365                } 
     366                $operators[] = $operator; 
     367                 
     368                 
     369                if (sizeof($operators) == 1) { 
     370                 
     371                    // Handle fuzzy searches 
     372                    if ($operator == '~') { 
    214373                     
     374                        $results = array(); 
     375                        foreach ($methods as $method) { 
     376                            $results[] = $record->$method();     
     377                        } 
     378                        if (!self::checkCondition($operator, $value, $results)) { 
     379                            return FALSE;    
     380                        } 
     381                     
     382                    // Handle intersection 
     383                    } elseif ($operator == '><') { 
     384                         
     385                        if (sizeof($methods) != 2 || sizeof($values) != 2) { 
     386                            throw new fProgrammerException( 
     387                                'The intersection operator, %s, requires exactly two methods and two values', 
     388                                $operator 
     389                            );   
     390                        } 
     391                                     
     392                        $results    = array(); 
     393                        $results[0] = $record->{$methods[0]}(); 
     394                        $results[1] = $record->{$methods[1]}(); 
     395                         
     396                        if ($results[1] === NULL && $values[1] === NULL) { 
     397                            if (!self::checkCondition('=', $values[0], $results[0])) { 
     398                                return FALSE; 
     399                            } 
     400                             
     401                             
     402                        } else { 
     403                             
     404                            $starts_between_values = FALSE; 
     405                            $overlaps_value_1      = FALSE; 
     406                             
     407                            if ($values[1] !== NULL) { 
     408                                $start_lt_value_1      = self::checkCondition('<', $values[0], $results[0]); 
     409                                $start_gt_value_2      = self::checkCondition('>', $values[1], $results[0]); 
     410                                $starts_between_values = !$start_lt_value_1 && !$start_gt_value_2; 
     411                            } 
     412                            if ($results[1] !== NULL) { 
     413                                $start_gt_value_1 = self::checkCondition('>', $values[0], $results[0]); 
     414                                $end_lt_value_1   = self::checkCondition('<', $values[0], $results[1]); 
     415                                $overlaps_value_1 = !$start_gt_value_1 && !$end_lt_value_1; 
     416                            } 
     417                             
     418                            if (!$starts_between_values && !$overlaps_value_1) { 
     419                                return FALSE; 
     420                            } 
     421                        } 
     422                     
     423                    } else { 
     424                        throw new fProgrammerException( 
     425                            'An invalid comparison operator, %s, was specified for multiple columns', 
     426                            $operator 
     427                        ); 
     428                    } 
     429                     
     430                // Handle OR conditions 
     431                } else { 
     432                     
     433                    if (sizeof($methods) != sizeof($values)) { 
     434                        throw new fProgrammerException( 
     435                            'When performing an %1$s comparison there must be an equal number of methods and values, however there are not', 
     436                            'OR', 
     437                            sizeof($methods), 
     438                            sizeof($values) 
     439                        ); 
     440                    } 
     441                     
     442                    if (sizeof($methods) != sizeof($operators)) { 
     443                        throw new fProgrammerException( 
     444                            'When performing an %s comparison there must be a comparison operator for each column, however one or more is missing', 
     445                            'OR' 
     446                        ); 
     447                    } 
     448                     
     449                    $results    = array(); 
     450                    $iterations = sizeof($methods); 
     451                    for ($i=0; $i<$iterations; $i++) { 
     452                        $results[] = self::checkCondition($operators[$i], $values[$i], $record->{$methods[$i]}()); 
     453                    } 
     454                     
     455                    if (!array_filter($results)) { 
     456                        return FALSE;    
     457                    } 
     458                     
     459                } 
     460                 
     461            // Single method comparisons     
    215462            } else { 
    216463                $result = $record->$method(); 
    217             } 
    218              
    219             switch ($operator) { 
    220                 case '~': 
    221                     // This is a fuzzy search type operation since it is using the output of multiple methods 
    222                     if ($multi_method) { 
    223                         if (!is_array($value)) { 
    224                             $value = fORMDatabase::parseSearchTerms($value, TRUE); 
    225                         }    
    226                          
    227                         foreach ($value as $_value) { 
    228                             $found = FALSE; 
    229                             foreach ($result as $_result) { 
    230                                 if (fUTF8::ipos($_result, $_value) !== FALSE) { 
    231                                     $found = TRUE; 
    232                                 }  
    233                             } 
    234                             if (!$found) { 
    235                                 return FALSE; 
    236                             }    
    237                         } 
    238                      
    239                     // This is a simple LIKE match against one or more values 
    240                     } else { 
    241                         // Ensure the method output is present in at least one value of the array 
    242                         if (is_array($value)) { 
    243                          
    244                             $found = FALSE; 
    245                             foreach ($value as $_value) { 
    246                                 if (fUTF8::ipos($result, $_value) !== FALSE) { 
    247                                     $found = TRUE; 
    248                                 }    
    249                             } 
    250                             if (!$found) { 
    251                                 return FALSE;    
    252                             } 
    253                                  
    254                         // Ensure the method is present in the value 
    255                         } elseif (!is_array($value) && fUTF8::ipos($result, $value) === FALSE) { 
    256                             return FALSE;    
    257                         }    
    258                     } 
    259                     break; 
    260                  
    261                 case '=': 
    262                     if ($value instanceof fActiveRecord && $result instanceof fActiveRecord) { 
    263                         if (get_class($value) != get_class($result) || !$value->exists() || !$result->exists() || self::hash($value) != self::hash($result)) { 
    264                             return FALSE; 
    265                         } 
    266                          
    267                     } elseif (is_array($value) && !in_array($result, $value)) { 
    268                         return FALSE; 
    269                              
    270                     } elseif (!is_array($value) && $result != $value) { 
    271                         return FALSE;    
    272                     } 
    273                     break; 
    274                      
    275                 case '!': 
    276                     if ($value instanceof fActiveRecord && $result instanceof fActiveRecord) { 
    277                         if (get_class($value) == get_class($result) && $value->exists() && $result->exists() && self::hash($value) == self::hash($result)) { 
    278                             return FALSE; 
    279                         } 
    280                          
    281                     } elseif (is_array($value) && in_array($result, $value)) { 
    282                         return FALSE;    
    283                          
    284                     } elseif (!is_array($value) && $result == $value) { 
    285                         return FALSE;    
    286                     } 
    287                     break; 
    288                  
    289                 case '<': 
    290                     if ($result >= $value) { 
    291                         return FALSE;    
    292                     } 
    293                     break; 
    294                  
    295                 case '<=': 
    296                     if ($result > $value) { 
    297                         return FALSE;    
    298                     } 
    299                     break; 
    300                  
    301                 case '>': 
    302                     if ($result <= $value) { 
    303                         return FALSE;    
    304                     } 
    305                     break; 
    306                  
    307                 case '>=': 
    308                     if ($result < $value) { 
    309                         return FALSE;    
    310                     } 
    311                     break; 
     464                if (!self::checkCondition($operator, $value, $result)) { 
     465                    return FALSE;    
     466                } 
    312467            }    
    313468        } 
  • fORMDatabase.php

    r646 r707 Hide Line Numbers
    1111 * @link       http://flourishlib.com/fORMDatabase 
    1212 *  
    13  * @version    1.0.0b14 
     13 * @version    1.0.0b15 
     14 * @changes    1.0.0b15  Streamlined intersection operator SQL and added support for the second value being NULL [wb, 2009-09-21] 
    1415 * @changes    1.0.0b14  Added support for the intersection operator `><` to ::createWhereClause() [wb, 2009-07-13] 
    1516 * @changes    1.0.0b13  Added support for the `AND LIKE` operator `&~` to ::createWhereClause() [wb, 2009-07-09] 
     
    651652                        } 
    652653                                                              
    653                         $part_1 = '(' . $columns[0] . ' <= ' . self::escapeBySchema($table, $columns[0], $values[0]) . ' AND ' . $columns[1] . ' >= ' . self::escapeBySchema($table, $columns[1], $values[0]) . ')'; 
    654                         $part_2 = '(' . $columns[0] . ' <= ' . self::escapeBySchema($table, $columns[0], $values[1]) . ' AND ' . $columns[1] . ' >= ' . self::escapeBySchema($table, $columns[1], $values[1]) . ')'; 
    655                         $part_3 = '(' . $columns[0] . ' >= ' . self::escapeBySchema($table, $columns[0], $values[0]) . ' AND ' . $columns[1] . ' <= ' . self::escapeBySchema($table, $columns[1], $values[1]) . ')'; 
    656                         $part_4 = '(' . $columns[1] . ' IS NULL AND ' . $columns[0] . ' >= ' . self::escapeBySchema($table, $columns[0], $values[0]) . ' AND ' . $columns[0] . ' <= ' . self::escapeBySchema($table, $columns[0], $values[1]) . ')'; 
    657                          
    658                         $sql[] = ' (' . $part_1 . ' OR ' . $part_2 . ' OR ' . $part_3 . ' OR ' . $part_4 . ') '; 
     654                        if ($values[1] === NULL) { 
     655                            $part_1 = '(' . $columns[1] . ' IS NULL AND ' . $columns[0] . ' = ' . self::escapeBySchema($table, $columns[0], $values[0]) . ')'; 
     656                            $part_2 = '(' . $columns[1] . ' IS NOT NULL AND ' . $columns[0] . ' <= ' . self::escapeBySchema($table, $columns[0], $values[0]) . ' AND ' . $columns[1] . ' >= ' . self::escapeBySchema($table, $columns[1], $values[0]) . ')'; 
     657                        } else { 
     658                            $part_1 = '(' . $columns[0] . ' <= ' . self::escapeBySchema($table, $columns[0], $values[0]) . ' AND ' . $columns[1] . ' >= ' . self::escapeBySchema($table, $columns[1], $values[0]) . ')'; 
     659                            $part_2 = '(' . $columns[0] . ' >= ' . self::escapeBySchema($table, $columns[0], $values[0]) . ' AND ' . $columns[0] . ' <= ' . self::escapeBySchema($table, $columns[0], $values[1]) . ')'; 
     660                        } 
     661                         
     662                        $sql[] = ' (' . $part_1 . ' OR ' . $part_2 . ') '; 
    659663                     
    660664                    } else { 
  • fRecordSet.php

    r705 r707 Hide Line Numbers
    1010 * @link       http://flourishlib.com/fRecordSet 
    1111 *  
    12  * @version    1.0.0b25 
     12 * @version    1.0.0b26 
     13 * @changes    1.0.0b26  Updated the documentation for ::build() and ::filter() to reflect new functionality [wb, 2009-09-21] 
    1314 * @changes    1.0.0b25  Fixed ::map() to work with string-style static method callbacks in PHP 5.1 [wb, 2009-09-18] 
    1415 * @changes    1.0.0b24  Backwards Compatibility Break - renamed ::buildFromRecords() to ::buildFromArray(). Added ::buildFromCall(), ::buildFromMap() and `::build{RelatedRecords}()` [wb, 2009-09-16] 
     
    7071     * 'column!~'                   => array(VALUE, VALUE2, ... )   // (column NOT LIKE '%VALUE%' AND column NOT LIKE '%VALUE2%' AND column ... ) 
    7172     * 'column!|column2<|column3='  => array(VALUE, VALUE2, VALUE3) // (column <> '%VALUE%' OR column2 < '%VALUE2%' OR column3 = '%VALUE3%') 
    72      * 'column|column2><'           => array(VALUE, VALUE2)         // ((column <= VALUE AND column2 >= VALUE) OR (column <= VALUE2 AND column2 >= VALUE2) OR (column >= VALUE AND column2 <= VALUE2) OR (column2 IS NULL AND column >= VALUE AND column <= VALUE2))  
     73     * 'column|column2><'           => array(VALUE, VALUE2)         // WHEN VALUE === NULL: ((column2 IS NULL AND column = VALUE) OR (column2 IS NOT NULL AND column <= VALUE AND column2 >= VALUE)) 
     74     *                                                              // WHEN VALUE !== NULL: ((column <= VALUE AND column2 >= VALUE) OR (column >= VALUE AND column <= VALUE2)) 
    7375     * 'column|column2|column3~'    => VALUE                        // (column LIKE '%VALUE%' OR column2 LIKE '%VALUE%' OR column3 LIKE '%VALUE%') 
    7476     * 'column|column2|column3~'    => array(VALUE, VALUE2, ... )   // ((column LIKE '%VALUE%' OR column2 LIKE '%VALUE%' OR column3 LIKE '%VALUE%') AND (column LIKE '%VALUE2%' OR column2 LIKE '%VALUE2%' OR column3 LIKE '%VALUE2%') AND ... ) 
     
    818820     * {{{ 
    819821     * // The following forms work for any $value that is not an array 
    820      * 'methodName='                         => $value  // If the output is equal to $value 
    821      * 'methodName!'                         => $value  // If the output is not equal to $value 
    822      * 'methodName!='                        => $value  // If the output is not equal to $value 
    823      * 'methodName<>'                        => $value  // If the output is not equal to $value 
    824      * 'methodName<'                         => $value  // If the output is less than $value 
    825      * 'methodName<='                        => $value  // If the output is less than or equal to $value 
    826      * 'methodName>'                         => $value  // If the output is greater than $value 
    827      * 'methodName>='                        => $value  // If the output is greater than or equal to $value 
    828      * 'methodName~'                         => $value  // If the output contains the $value (case insensitive) 
    829      * 'methodName|methodName2|methodName3~' => $value  // Parses $value as a search string and make sure each term is present in at least one output (case insensitive) 
     822     * 'methodName='                           => $value  // If the output is equal to $value 
     823     * 'methodName!'                           => $value  // If the output is not equal to $value 
     824     * 'methodName!='                          => $value  // If the output is not equal to $value 
     825     * 'methodName<>'                          => $value  // If the output is not equal to $value 
     826     * 'methodName<'                           => $value  // If the output is less than $value 
     827     * 'methodName<='                          => $value  // If the output is less than or equal to $value 
     828     * 'methodName>'                           => $value  // If the output is greater than $value 
     829     * 'methodName>='                          => $value  // If the output is greater than or equal to $value 
     830     * 'methodName~'                           => $value  // If the output contains the $value (case insensitive) 
     831     * 'methodName!~'                          => $value  // If the output does not contain the $value (case insensitive) 
     832     * 'methodName|methodName2|methodName3~'   => $value  // Parses $value as a search string and make sure each term is present in at least one output (case insensitive) 
    830833     *  
    831834     * // The following forms work for any $array that is an array 
    832      * 'methodName='                         => $array  // If the output is equal to at least one value in $array 
    833      * 'methodName!'                         => $array  // If the output is not equal to any value in $array 
    834      * 'methodName!='                        => $array  // If the output is not equal to any value in $array 
    835      * 'methodName<>'                        => $array  // If the output is not equal to any value in $array 
    836      * 'methodName~'                         => $array  // If the output contains one of the strings in $array (case insensitive) 
    837      * 'methodName|methodName2|methodName3~' => $array  // If each value in the array is present in the output of at least one method (case insensitive) 
     835     * 'methodName='                           => $array  // If the output is equal to at least one value in $array 
     836     * 'methodName!'                           => $array  // If the output is not equal to any value in $array 
     837     * 'methodName!='                          => $array  // If the output is not equal to any value in $array 
     838     * 'methodName<>'                          => $array  // If the output is not equal to any value in $array 
     839     * 'methodName~'                           => $array  // If the output contains one of the strings in $array (case insensitive) 
     840     * 'methodName!~'                          => $array  // If the output contains none of the strings in $array (case insensitive) 
     841     * 'methodName&~'                          => $array  // If the output contains all of the strings in $array (case insensitive) 
     842     * 'methodName|methodName2|methodName3~'   => $array  // If each value in the array is present in the output of at least one method (case insensitive) 
     843     *  
     844     * // The following works for an equal number of methods and values in the array 
     845     * 'methodName!|methodName2<|methodName3=' => array($value, $value2, $value3) // An OR statement - one of the method to value comparisons must be TRUE 
     846     *  
     847     * // The following accepts exactly two methods and two values, although the second value may be NULL 
     848     * 'methodName|methodName2><'              => array($value, $value2) // If the range of values from the methods intersects the range of $value and $value2 - should be dates, times, timestamps or numbers 
    838849     * }}}  
    839850     *