From 87fe8d05c2117fc2d062ae1bee79344376339ff4 Mon Sep 17 00:00:00 2001 From: CrawfordCurrie Date: Fri, 9 Apr 2010 12:46:24 +0000 Subject: [PATCH] Item2247: query optimisation (constant folding) reduces size of query expressions. git-svn-id: http://svn.foswiki.org/trunk@7131 0b4bb1d4-4e5a-0410-9cc4-b2b747904278 --- UnitTestContrib/test/unit/QueryTests.pm | 373 +++++++++--------- core/lib/Foswiki/Contrib/core/MANIFEST | 1 + core/lib/Foswiki/Func.pm | 3 +- core/lib/Foswiki/Query/BinaryOP.pm | 28 ++ core/lib/Foswiki/Query/Node.pm | 77 +++- core/lib/Foswiki/Query/OP.pm | 6 + core/lib/Foswiki/Query/OP_and.pm | 11 + core/lib/Foswiki/Query/OP_or.pm | 11 + core/lib/Foswiki/Query/OP_ref.pm | 29 +- core/lib/Foswiki/Query/UnaryOP.pm | 6 + .../Store/QueryAlgorithms/BruteForce.pm | 4 + 11 files changed, 346 insertions(+), 203 deletions(-) diff --git a/UnitTestContrib/test/unit/QueryTests.pm b/UnitTestContrib/test/unit/QueryTests.pm index d7deba90bb..0187a2e9b1 100644 --- a/UnitTestContrib/test/unit/QueryTests.pm +++ b/UnitTestContrib/test/unit/QueryTests.pm @@ -46,7 +46,7 @@ sub set_up { rev => '23', date => '25', } - ); + ); $meta->putKeyed( 'FILEATTACHMENT', { @@ -59,8 +59,7 @@ sub set_up { rev => '105', date => '99', } - ); - $meta->put( 'FORM', { name => 'TestForm' } ); + ); $meta->put( 'TOPICINFO', { @@ -69,7 +68,7 @@ sub set_up { format => '1.1', version => '1.1913', } - ); + ); $meta->put( 'TOPICMOVED', { @@ -78,7 +77,8 @@ sub set_up { from => 'BouvardEtPecuchet', to => 'ThePlague', } - ); + ); + $meta->put( 'FORM', { name => 'TestForm' } ); $meta->put( 'TOPICPARENT', { name => '' } ); $meta->putKeyed( 'PREFERENCE', { name => 'Red', value => '0' } ); $meta->putKeyed( 'PREFERENCE', { name => 'Green', value => '1' } ); @@ -86,16 +86,16 @@ sub set_up { $meta->putKeyed( 'PREFERENCE', { name => 'White', value => '0' } ); $meta->putKeyed( 'PREFERENCE', { name => 'Yellow', value => '1' } ); $meta->putKeyed( 'FIELD', - { name => "number", title => "Number", value => "99" } ); + { name => "number", title => "Number", value => "99" } ); $meta->putKeyed( 'FIELD', - { name => "string", title => "String", value => "String" } ); + { name => "string", title => "String", value => "String" } ); $meta->putKeyed( 'FIELD', - { name => "StringWithChars", title => "StringWithChars", - value => "n\nn t\tt s\\s q'q o#o h#h X~X \\b \\a \\e \\f \\r \\cX" } ); + { name => "StringWithChars", title => "StringWithChars", + value => "n\nn t\tt s\\s q'q o#o h#h X~X \\b \\a \\e \\f \\r \\cX" } ); $meta->putKeyed( 'FIELD', - { name => "boolean", title => "Boolean", value => "1" } ); + { name => "boolean", title => "Boolean", value => "1" } ); $meta->putKeyed( 'FIELD', - { name => "macro", value => "%RED%" } ); + { name => "macro", value => "%RED%" } ); $meta->{_text} = "Green ideas sleep furiously"; $this->{meta} = $meta; @@ -131,40 +131,49 @@ SUB } sub check { - my ( $this, $s, $r, $syntaxOnly ) = @_; + my ( $this, $s, %opts ) = @_; # First check that standard evaluator my $queryParser = new Foswiki::Query::Parser(); my $query = $queryParser->parse($s); my $meta = $this->{meta}; my $val = $query->evaluate( tom => $meta, data => $meta ); - if ( ref($r) ) { - $this->assert_deep_equals( $r, $val, - "Expected ".ref($r)." $r, got " - . Foswiki::Query::Node::toString($val) - . " for $s in " - . join( ' ', caller ) ); + if ( ref($opts{'eval'}) ) { + $this->assert_deep_equals( $opts{'eval'}, $val, + "Expected ".ref($opts{'eval'})." $opts{'eval'}, got " + . Foswiki::Query::Node::toString($val) + . " for $s in " + . join( ' ', caller ) ); } - elsif (defined $r) { - $this->assert_str_equals( $r, $val, - "Expected scalar $r, got " - . Foswiki::Query::Node::toString($val) - . " for $s in " - . join( ' ', caller ) ); + elsif (defined $opts{'eval'}) { + $this->assert_str_equals( $opts{'eval'}, $val, + "Expected scalar $opts{'eval'}, got " + . Foswiki::Query::Node::toString($val) + . " for $s in " + . join( ' ', caller ) ); } else { $this->assert(!defined($val), - "Expected undef, got " - . Foswiki::Query::Node::toString($val) - . " for $s in " - . join( ' ', caller ) ); + "Expected undef, got " + . Foswiki::Query::Node::toString($val) + . " for $s in " + . join( ' ', caller ) ); } - unless ($syntaxOnly) { + unless ($opts{syntaxOnly}) { + if (defined $opts{simpler}) { + $query->simplify( tom => $meta, data => $meta ); + $this->assert_str_equals( + $opts{simpler}, $query->stringify(), + $query->stringify()." is not $opts{simpler}"); + } elsif ($query->evaluatesToConstant()) { + $this->assert($opts{simpler}, $query->stringify()." should be variable"); + } + # Next check the search algorithm my $expr = "%SEARCH{\"$s\" type=\"query\" excludetopic=\"WebPreferences,$this->{test_topic}\" nonoise=\"on\" format=\"\$topic\"}%"; my $list = $this->{test_topicObject}->expandMacros($expr); - if ($r) { + if ($opts{'eval'}) { $this->assert_str_equals( 'HitTopic', $list); } else { $this->assert_str_equals( '', $list); @@ -174,68 +183,68 @@ sub check { sub verify_atoms { my $this = shift; - $this->check( "'0'", '0' ); - $this->check( "''", '' ); - $this->check( "1", 1 ); - $this->check( "-1", -1 ); - $this->check( "-1.1965432e-3", -1.1965432e-3 ); - $this->check( "number", 99 ); - $this->check( "text", "Green ideas sleep furiously" ); - $this->check( "string", 'String' ); - $this->check( "boolean", 1 ); - $this->check( "macro", '%RED%' ); - $this->check( "notafield", undef ); + $this->check( "'0'", eval=> '0', simpler=>0 ); + $this->check( "''", eval=> '', simpler=>0 ); + $this->check( "1", eval=> 1, simpler=>1 ); + $this->check( "-1", eval=> -1, simpler=>-1 ); + $this->check( "-1.1965432e-3", eval=>-1.1965432e-3, simpler=>-1.1965432e-3 ); + $this->check( "number", eval=> 99 ); + $this->check( "text", eval=> "Green ideas sleep furiously" ); + $this->check( "string", eval=> 'String' ); + $this->check( "boolean", eval=> 1 ); + $this->check( "macro", eval=> '%RED%' ); + $this->check( "notafield", eval=> undef ); } sub verify_meta_dot { my $this = shift; - $this->check( "META:FORM", { name => 'TestForm' } ); - $this->check( "META:FORM.name", 'TestForm' ); - $this->check( "form.name", 'TestForm' ); - $this->check( "info.author", 'AlbertCamus' ); - $this->check( "fields.number", 99 ); - $this->check( "fields.string", 'String' ); - $this->check( "notafield.string", undef ); + $this->check( "META:FORM", eval=>{ name => 'TestForm' } ); + $this->check( "META:FORM.name", eval=>'TestForm' ); + $this->check( "form.name", eval=> 'TestForm' ); + $this->check( "info.author", eval=> 'AlbertCamus' ); + $this->check( "fields.number", eval=> 99 ); + $this->check( "fields.string", eval=> 'String' ); + $this->check( "notafield.string", eval=> undef ); } sub verify_array_integer_index { my $this = shift; - $this->check( "preferences[0].name", 'Red' ); - $this->check( "preferences[1]", { name => 'Green', value => 1 } ); - $this->check( "preferences[2].name", 'Blue' ); - $this->check( "preferences[3].name", 'White' ); - $this->check( "preferences[4].name", 'Yellow' ); + $this->check( "preferences[0].name", eval=>'Red' ); + $this->check( "preferences[1]", eval=>{ name => 'Green', value => 1 }); + $this->check( "preferences[2].name", eval=>'Blue' ); + $this->check( "preferences[3].name", eval=>'White' ); + $this->check( "preferences[4].name", eval=>'Yellow' ); # Integer part used as the index - $this->check( "preferences[1.9].name", 'Green' ); + $this->check( "preferences[1.9].name", eval=>'Green' ); # From-the-end indices - $this->check( "preferences[-1].name", 'Yellow' ); - $this->check( "preferences[-2].name", 'White' ); - $this->check( "preferences[-3].name", 'Blue' ); - $this->check( "preferences[-4].name", 'Green' ); - $this->check( "preferences[-5].name", 'Red' ); + $this->check( "preferences[-1].name", eval=>'Yellow' ); + $this->check( "preferences[-2].name", eval=>'White' ); + $this->check( "preferences[-3].name", eval=>'Blue' ); + $this->check( "preferences[-4].name", eval=>'Green' ); + $this->check( "preferences[-5].name", eval=>'Red' ); # Out-of-range indices - $this->check( "preferences[5]", undef ); - $this->check( "preferences[-6]", undef ); + $this->check( "preferences[5]", eval=>undef ); + $this->check( "preferences[-6]", eval=>undef ); } sub verify_array_dot { my $this = shift; - $this->check( "preferences[value=0].Red", 0 ); - $this->check( "preferences[value=1].Yellow", 1 ); + $this->check( "preferences[value=0].Red", eval=> 0 ); + $this->check( "preferences[value=1].Yellow", eval=>1 ); } sub verify_meta_squabs { my $this = shift; - $this->check( "fields[name='number'].value", 99 ); - $this->check( "fields[name='number' AND value='99'].value", 99 ); - $this->check( "fields[name='number' AND value='99'].value", 99 ); + $this->check( "fields[name='number'].value", eval=> 99 ); + $this->check( "fields[name='number' AND value='99'].value", eval=>99 ); + $this->check( "fields[name='number' AND value='99'].value", eval=>99 ); } sub verify_array_squab { my $this = shift; - $this->check( "preferences[value=0][name='Blue'].name", "Blue" ); + $this->check( "preferences[value=0][name='Blue'].name", eval=>"Blue" ); } sub verify_slashes { @@ -244,194 +253,198 @@ sub verify_slashes { sub verify_boolean_uops { my $this = shift; - $this->check( "not number", 0 ); - $this->check( "not boolean", 0 ); - $this->check( "not 0", 1 ); - $this->check( "not notafield", 1 ); + $this->check( "not number", eval=> 0 ); + $this->check( "not boolean", eval=>0 ); + $this->check( "not 0", eval=> 1, simpler=>1); + $this->check( "not notafield", eval=>1 ); } sub verify_string_uops { my $this = shift; - $this->check( "uc string", 'STRING' ); - $this->check( "uc(string)", "STRING" ); - $this->check( "lc string", 'string' ); - $this->check( "lc(notafield)", undef ); - $this->check( "uc 'string'", 'STRING' ); - $this->check( "uc (notafield)", undef ); - $this->check( "lc 'STRING'", 'string' ); + $this->check( "uc string", eval=> 'STRING' ); + $this->check( "uc(string)", eval=> "STRING" ); + $this->check( "lc string", eval=> 'string' ); + $this->check( "lc(notafield)", eval=> undef ); + $this->check( "uc 'string'", eval=>'STRING', simpler=>"'STRING'"); + $this->check( "uc (notafield)", eval=>undef ); + $this->check( "lc 'STRING'", eval=>'string', simpler=>"'string'"); } sub verify_string_bops { my $this = shift; - $this->check( "string='String'", 1 ); - $this->check( "string='String '", 0 ); - $this->check( "string~'String '", 0 ); - $this->check( "string='Str'", 0 ); - $this->check( "string~'?trin?'", 1 ); - $this->check( "string~'*'", 1 ); - $this->check( "string~'*String'", 1 ); - $this->check( "string~'*trin*'", 1 ); - $this->check( "string~'*in?'", 1 ); - $this->check( "string~'*ri?'", 0 ); - $this->check( "string~'??????'", 1 ); - $this->check( "string~'???????'", 0 ); - $this->check( "string~'?????'", 0 ); - $this->check( "'SomeTextToTestFor'~'Text'", 0 ); - $this->check( "'SomeTextToTestFor'~'*Text'", 0 ); - $this->check( "'SomeTextToTestFor'~'Text*'", 0 ); - $this->check( "'SomeTextToTestFor'~'*Text*'", 1 ); - $this->check( "string!='Str'", 1 ); - $this->check( "string!='String '", 1 ); - $this->check( "string!='String'", 0 ); - $this->check( "string!='string'", 1 ); - $this->check( "string='string'", 0 ); - $this->check( "string~'string'", 0 ); + $this->check( "string='String'", eval=> 1 ); + $this->check( "string='String '", eval=> 0 ); + $this->check( "string~'String '", eval=> 0 ); + $this->check( "string='Str'", eval=> 0 ); + $this->check( "string~'?trin?'", eval=> 1 ); + $this->check( "string~'*'", eval=> 1 ); + $this->check( "string~'*String'", eval=> 1 ); + $this->check( "string~'*trin*'", eval=> 1 ); + $this->check( "string~'*in?'", eval=> 1 ); + $this->check( "string~'*ri?'", eval=> 0 ); + $this->check( "string~'??????'", eval=> 1 ); + $this->check( "string~'???????'", eval=> 0 ); + $this->check( "string~'?????'", eval=> 0 ); + $this->check( "'SomeTextToTestFor'~'Text'", eval=> 0, simpler=>0); + $this->check( "'SomeTextToTestFor'~'*Text'", eval=> 0, simpler=>0); + $this->check( "'SomeTextToTestFor'~'Text*'", eval=> 0, simpler=>0); + $this->check( "'SomeTextToTestFor'~'*Text*'", eval=>1, simpler=>1); + $this->check( "string!='Str'", eval=> 1 ); + $this->check( "string!='String '", eval=> 1 ); + $this->check( "string!='String'", eval=> 0 ); + $this->check( "string!='string'", eval=> 1 ); + $this->check( "string='string'", eval=> 0 ); + $this->check( "string~'string'", eval=> 0 ); } sub test_string_bops { my $this = shift; - $this->check( "macro='\%RED\%'", 1, 1 ); - $this->check( "macro~'\%RED?'", 1, 1 ); - $this->check( "macro~'?RED\%'", 1, 1 ); + $this->check( "macro='\%RED\%'", eval=> 1, syntaxOnly=>1 ); + $this->check( "macro~'\%RED?'", eval=> 1, syntaxOnly=>1 ); + $this->check( "macro~'?RED\%'", eval=> 1, syntaxOnly=>1 ); } sub verify_length { my $this = shift; - $this->check( "length attachments", 2 ); - $this->check( "length META:PREFERENCE", 5 ); - $this->check( "length 'five'", 4 ); - $this->check( "length info", 4 ); - $this->check( "length notafield", 0 ); + $this->check( "length attachments", eval=> 2 ); + $this->check( "length META:PREFERENCE", eval=>5 ); + $this->check( "length 'five'", eval=> 4, simpler=>4); + $this->check( "length info", eval=> 4 ); + $this->check( "length notafield", eval=> 0 ); } sub verify_d2n { my $this = shift; $this->check( "d2n '" . Foswiki::Time::formatTime( 0, '$iso', 'servertime' ) - . "'", 0 ); + . "'", eval => 0, simpler => 0 ); my $t = time; $this->check( "d2n '" . Foswiki::Time::formatTime( $t, '$iso', 'servertime' ) - . "'", $t ); - $this->check( "d2n 'not a time'", undef ); - $this->check( "d2n 0", undef ); - $this->check( "d2n notatime", undef ); + . "'", eval => $t, simpler => $t ); + $this->check( "d2n 'not a time'", eval=>undef, simpler=>0 ); + $this->check( "d2n 0", eval=>undef, simpler=>0 ); + $this->check( "d2n notatime", eval=>undef ); } sub verify_num_bops { my $this = shift; - $this->check( "number=99", 1 ); - $this->check( "number=98", 0 ); - $this->check( "number!=99", 0 ); - $this->check( "number!=0", 1 ); - $this->check( "number<100", 1 ); - $this->check( "number<99", 0 ); - $this->check( "number>98", 1 ); - $this->check( "number>99", 0 ); - $this->check( "number<=99", 1 ); - $this->check( "number<=100", 1 ); - $this->check( "number<=98", 0 ); - $this->check( "number>=98", 1 ); - $this->check( "number>=99", 1 ); - $this->check( "number>=100", 0 ); - - $this->check( "number=notafield", 0); - $this->check( "0=notafield", 0); - $this->check( "notafield=number", 0); - $this->check( "number!=notafield", 1); - $this->check( "notafield!=number", 1); - $this->check( "number>=notafield", 1); - $this->check( "notafield>=number", 0); - $this->check( "number<=notafield", 0); - $this->check( "notafield<=number", 1); - $this->check( "number>notafield", 1); - $this->check( "notafield>number", 0); - $this->check( "numbercheck( "notafieldcheck( "notafield=undefined", 1); + $this->check( "number=99", eval=> 1 ); + $this->check( "99=99", eval=> 1, simpler=>1); + $this->check( "number=98", eval=> 0 ); + $this->check( "number!=99", eval=> 0 ); + $this->check( "number!=0", eval=> 1 ); + $this->check( "number<100", eval=> 1 ); + $this->check( "number<99", eval=> 0 ); + $this->check( "number>98", eval=> 1 ); + $this->check( "number>99", eval=> 0 ); + $this->check( "number<=99", eval=> 1 ); + $this->check( "number<=100", eval=>1 ); + $this->check( "number<=98", eval=> 0 ); + $this->check( "number>=98", eval=> 1 ); + $this->check( "number>=99", eval=> 1 ); + $this->check( "number>=100", eval=>0 ); + + $this->check( "number=notafield", eval=>0 ); + $this->check( "0=notafield", eval=>0 ); + $this->check( "notafield=number", eval=>0 ); + $this->check( "number!=notafield", eval=>1 ); + $this->check( "notafield!=number", eval=>1 ); + $this->check( "number>=notafield", eval=>1 ); + $this->check( "notafield>=number", eval=>0 ); + $this->check( "number<=notafield", eval=>0 ); + $this->check( "notafield<=number", eval=>1 ); + $this->check( "number>notafield", eval=>1 ); + $this->check( "notafield>number", eval=>0 ); + $this->check( "number0 ); + $this->check( "notafield1 ); + + $this->check( "notafield=undefined", eval=>1 ); } sub verify_boolean_bops { my $this = shift; - $this->check( "1 AND 1", 1 ); - $this->check( "0 AND 1", 0 ); - $this->check( "1 AND 0", 0 ); - - $this->check( "1 OR 1", 1 ); - $this->check( "0 OR 1", 1 ); - $this->check( "1 OR 0", 1 ); - - $this->check( "number=99 AND string='String'", 1 ); - $this->check( "number=98 AND string='String'", 0 ); - $this->check( "number=99 AND string='Sring'", 0 ); - $this->check( "number=99 OR string='Spring'", 1 ); - $this->check( "number=98 OR string='String'", 1 ); - $this->check( "number=98 OR string='Spring'", 0 ); - - $this->check( "notafield AND 1", 0 ); - $this->check( "1 AND notafield", 0 ); - $this->check( "0 AND notafield", 0 ); - $this->check( "notafield OR 1", 1 ); - $this->check( "1 OR notafield", 1 ); - $this->check( "notafield OR 0", 0 ); - $this->check( "0 OR notafield", 0 ); + $this->check( "1 AND 1", eval=>1, simpler=>1); + $this->check( "0 AND 1", eval=>0, simpler=>0); + $this->check( "1 AND 0", eval=>0, simpler=>0); + $this->check( "0 AND 0", eval=>0, simpler=>0); + + $this->check( "1 OR 1", eval=>1, simpler=>1); + $this->check( "0 OR 1", eval=>1, simpler=>1); + $this->check( "1 OR 0", eval=>1, simpler=>1); + $this->check( "0 OR 0", eval=>0, simpler=>0); + + $this->check( "number=99 AND string='String'", eval=>1 ); + $this->check( "number=98 AND string='String'", eval=>0 ); + $this->check( "number=99 AND string='Sring'", eval=> 0 ); + $this->check( "number=99 OR string='Spring'", eval=> 1 ); + $this->check( "number=98 OR string='String'", eval=> 1 ); + $this->check( "number=98 OR string='Spring'", eval=> 0 ); + + $this->check( "notafield AND 1", eval=> 0 ); + $this->check( "1 AND notafield", eval=> 0 ); + $this->check( "0 AND notafield", eval=> 0, simpler=>0); + $this->check( "notafield OR 1", eval=> 1, simpler=>1); + $this->check( "1 OR notafield", eval=> 1, simpler=>1); + $this->check( "notafield OR 0", eval=> 0 ); + $this->check( "0 OR notafield", eval=> 0 ); } sub verify_match_fail { my $this = shift; - $this->check( "'A'=~'B'", 0); + $this->check( "'A'=~'B'", eval=>0, simpler=>0); } sub verify_match_good { my $this = shift; - $this->check( "'A'=~'A'", 1); + $this->check( "'A'=~'A'", eval=>1, simpler=>1); } sub verify_partial_match { my $this = shift; - $this->check( "'AA'=~'A'", 1); + $this->check( "'AA'=~'A'", eval=>1, simpler=>1); } sub verify_word_bound_match_good { my $this = shift; - $this->check( "'foo bar baz'=~'\\bbar\\b'", 1); + $this->check( "'foo bar baz'=~'\\bbar\\b'", eval=>1, simpler=>1); } sub verify_word_bound_match_fail { my $this = shift; - $this->check( "'foo bar baz'=~'\\bbam\\b'", 0); + $this->check( "'foo bar baz'=~'\\bbam\\b'", eval=>0, simpler=>0); } sub verify_word_end_match_fail { my $this = shift; - $this->check( "'foob'=~'foo\\b'", 0); + $this->check( "'foob'=~'foo\\b'", eval=>0, simpler=>0); } sub verify_ref { my $this = shift; - $this->check( "'HitTopic'/number=99", 1); - $this->check( "'$this->{test_web}.HitTopic'/number=99", 1); - $this->check( "'NotATopic'/number", undef); - $this->check( "'NotATopic'/number=99", 0); + $this->check( "'HitTopic'/number", eval=>99, simpler=>99); + $this->check( "'HitTopic'/number=99", eval=>1, simpler=>1); + $this->check( "'$this->{test_web}.HitTopic'/number=99", eval=>1, simpler=>1); + $this->check( "'NotATopic'/rev", eval=>undef, simpler=>0); + $this->check( "'NotATopic'/rev=23", eval=>0, simpler=>0); } sub test_backslash_match_fail { my $this = shift; - $this->check( "' \\ '=~' \\\\ '", 0, 1); + $this->check( "' \\ '=~' \\\\ '", eval=>0, syntaxOnly=>1, simpler=>1); } sub test_backslash_match_good { my $this = shift; - $this->check( "' \\\' '=~' \\\' '", 1, 1); + $this->check( "' \\\' '=~' \\\' '", eval=>1, syntaxOnly=>1, simpler=>1); } sub test_constant_strings { my $this = shift; my $in = 'n\nn t\tt s\\\\s q\\\'q o\\043o h\\x23h X\\x{7e}X \\b \\a \\e \\f \\r \\cX'; - $this->check( "'$in'=StringWithChars", 1, 1 ); + $this->check( "'$in'=StringWithChars", eval=>1, syntaxOnly=>1 ); } sub conjoin { @@ -449,7 +462,7 @@ sub conjoin { else { $expr = "( $ae $A $be ) $B $ce"; } - $this->check( $expr, $r ); + $this->check( $expr, eval=>$r ); } sub verify_brackets { diff --git a/core/lib/Foswiki/Contrib/core/MANIFEST b/core/lib/Foswiki/Contrib/core/MANIFEST index 6b4a8aede2..9591a6200c 100644 --- a/core/lib/Foswiki/Contrib/core/MANIFEST +++ b/core/lib/Foswiki/Contrib/core/MANIFEST @@ -688,6 +688,7 @@ lib/Foswiki/Prefs/Web.pm 0444 lib/Foswiki/Query/BinaryOP.pm 0444 lib/Foswiki/Query/HoistREs.pm 0444 lib/Foswiki/Query/Node.pm 0444 +lib/Foswiki/Query/OP.pm 0444 lib/Foswiki/Query/OP_and.pm 0444 lib/Foswiki/Query/OP_d2n.pm 0444 lib/Foswiki/Query/OP_dot.pm 0444 diff --git a/core/lib/Foswiki/Func.pm b/core/lib/Foswiki/Func.pm index 91113f7132..378704e303 100644 --- a/core/lib/Foswiki/Func.pm +++ b/core/lib/Foswiki/Func.pm @@ -24,7 +24,7 @@ API version $Date$ (revision $Rev$) *Since* _date_ indicates where functions or parameters have been added since the baseline of the API (Foswiki 1.0.0). The _date_ indicates the earliest date of a Foswiki release that will support that function or -parameter. +parameter. See Foswiki:Download.ReleaseDates for version release dates. *Deprecated* _date_ indicates where a function or parameters has been [[http://en.wikipedia.org/wiki/Deprecation][deprecated]]. Deprecated @@ -3279,6 +3279,7 @@ Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://F Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors are listed in the AUTHORS file in the root of this distribution. NOTE: Please extend that file, not this notice. + Additional copyrights apply to some or all of the code in this file as follows: diff --git a/core/lib/Foswiki/Query/BinaryOP.pm b/core/lib/Foswiki/Query/BinaryOP.pm index bfaef01d32..5eee3fa679 100644 --- a/core/lib/Foswiki/Query/BinaryOP.pm +++ b/core/lib/Foswiki/Query/BinaryOP.pm @@ -57,4 +57,32 @@ sub evalTest { } } +sub evaluatesToConstant { + my $this = shift; + my $node = shift; + return 0 unless $node->{params}[0]->evaluatesToConstant(@_); + return $node->{params}[1]->evaluatesToConstant(@_); +} + 1; +__END__ + +Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/ + +Copyright (C) 2009 Foswiki Contributors. All Rights Reserved. +Foswiki Contributors are listed in the AUTHORS file in the root +of this distribution. NOTE: Please extend that file, not this notice. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. For +more details read LICENSE in the root of this distribution. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +As per the GPL, removal of this notice is prohibited. + +Author: Crawford Currie http://c-dot.co.uk diff --git a/core/lib/Foswiki/Query/Node.pm b/core/lib/Foswiki/Query/Node.pm index c5790f728a..b0432b9a1b 100644 --- a/core/lib/Foswiki/Query/Node.pm +++ b/core/lib/Foswiki/Query/Node.pm @@ -127,22 +127,77 @@ sub evaluate { return $result; } +=begin TML + +---++ evaluatesToConstant(%opts) + +Determine if this node evaluates to a constant or not. "Constant" is defined +as "anything that doesn't involve actually looking in searched topics". +This function takes the same parameters (%domain) as evaluate(). Note that +no reference to the tom or data web or topic will be made, so you can +simply pass an arbitrary Foswiki::Meta. + +=cut + +sub evaluatesToConstant { + my $this = shift; + if (!ref($this->{op}) + && ($this->{op} == $Foswiki::Infix::Node::NUMBER + || $this->{op} == $Foswiki::Infix::Node::STRING)) { + return 1; + } + elsif (ref($this->{op})) { + return $this->{op}->evaluatesToConstant($this, @_); + } + return 0; +} + +=begin TML + +---++ simplify(%opts) + +Simplify the query by spotting constant expressions and evaluating them, +replacing the constant expression with an atomic value in the expression tree. +This function takes the same parameters (%domain) as evaluate(). Note that +no reference to the tom or data web or topic will be made, so you can +simply pass an arbitrary Foswiki::Meta. + +=cut + +sub simplify { + my $this = shift; + + if ($this->evaluatesToConstant(@_)) { + my $c = $this->evaluate(@_) || 0; + if ($c =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/) { + $this->{op} = $Foswiki::Infix::Node::NUMBER; + } else { + $this->{op} = $Foswiki::Infix::Node::STRING; + } + @{$this->{params}} = ( $c ); + } else { + for my $f (@{$this->{params}}) { + if (UNIVERSAL::can($f, 'simplify')) { + $f->simplify(@_); + } + } + } +} + 1; __DATA__ Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/ -# Copyright (C) 2008-2009 Foswiki Contributors. All Rights Reserved. -# Foswiki Contributors are listed in the AUTHORS file in the root -# of this distribution. NOTE: Please extend that file, not this notice. -# -# Additional copyrights apply to some or all of the code in this -# file as follows: -# -# Copyright (C) 2005-2007 TWiki Contributors. All Rights Reserved. -# TWiki Contributors are listed in the AUTHORS file in the root -# of this distribution. NOTE: Please extend that file, not this notice. -# +Copyright (C) 2008-2010 Foswiki Contributors. All Rights Reserved. +Foswiki Contributors are listed in the AUTHORS file in the root +of this distribution. NOTE: Please extend that file, not this notice. + +Additional copyrights apply to some or all of the code in this +file as follows: + +Copyright (C) 2005-2007 TWiki Contributors. All Rights Reserved. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 diff --git a/core/lib/Foswiki/Query/OP.pm b/core/lib/Foswiki/Query/OP.pm index 4ec53e3951..d19f521bad 100644 --- a/core/lib/Foswiki/Query/OP.pm +++ b/core/lib/Foswiki/Query/OP.pm @@ -8,6 +8,12 @@ sub new { return bless(\%opts, $class); } +# Does this operator evaluate to a constant? +# See Foswiki::Query::Node::evaluatesToConstant +sub evaluatesToConstant { + return 0; +} + 1; __END__ diff --git a/core/lib/Foswiki/Query/OP_and.pm b/core/lib/Foswiki/Query/OP_and.pm index d2ee64b24e..c42084e29a 100644 --- a/core/lib/Foswiki/Query/OP_and.pm +++ b/core/lib/Foswiki/Query/OP_and.pm @@ -27,6 +27,17 @@ sub evaluate { return $b->evaluate(@_) ? 1 : 0; } +sub evaluatesToConstant { + my $this = shift; + my $node = shift; + my $ac = $node->{params}[0]->evaluatesToConstant(@_); + my $bc = $node->{params}[1]->evaluatesToConstant(@_); + return 1 if ($ac && $bc); + return 1 if $ac && ! $node->{params}[0]->evaluate(@_); + return 1 if $bc && ! $node->{params}[1]->evaluate(@_); + return 0; +} + 1; __DATA__ diff --git a/core/lib/Foswiki/Query/OP_or.pm b/core/lib/Foswiki/Query/OP_or.pm index f423c45699..6019c4dff6 100644 --- a/core/lib/Foswiki/Query/OP_or.pm +++ b/core/lib/Foswiki/Query/OP_or.pm @@ -27,6 +27,17 @@ sub evaluate { return $b->evaluate(@_) ? 1 : 0; } +sub evaluatesToConstant { + my $this = shift; + my $node = shift; + my $ac = $node->{params}[0]->evaluatesToConstant(@_); + my $bc = $node->{params}[1]->evaluatesToConstant(@_); + return 1 if ($ac && $bc); + return 1 if $ac && $node->{params}[0]->evaluate(@_); + return 1 if $bc && $node->{params}[1]->evaluate(@_); + return 0; +} + 1; __DATA__ diff --git a/core/lib/Foswiki/Query/OP_ref.pm b/core/lib/Foswiki/Query/OP_ref.pm index 01ff389e17..98a5dfa0e0 100644 --- a/core/lib/Foswiki/Query/OP_ref.pm +++ b/core/lib/Foswiki/Query/OP_ref.pm @@ -65,23 +65,30 @@ sub evaluate { return \@result; } +sub evaluatesToConstant { + my $this = shift; + my $node = shift; + return 1 if $node->{params}[0]->evaluatesToConstant(@_); + # param[1] may contain non-constant terms, but that's OK because + # they are evaluated relative to the (constant) param[0] + return 0; +} + 1; __DATA__ Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/ -# Copyright (C) 2008-2009 Foswiki Contributors. All Rights Reserved. -# Foswiki Contributors are listed in the AUTHORS file in the root -# of this distribution. NOTE: Please extend that file, not this notice. -# -# Additional copyrights apply to some or all of the code in this -# file as follows: -# -# Copyright (C) 2005-2007 TWiki Contributors. All Rights Reserved. -# TWiki Contributors are listed in the AUTHORS file in the root -# of this distribution. NOTE: Please extend that file, not this notice. -# +Copyright (C) 2008-2009 Foswiki Contributors. All Rights Reserved. +Foswiki Contributors are listed in the AUTHORS file in the root +of this distribution. NOTE: Please extend that file, not this notice. + +Additional copyrights apply to some or all of the code in this +file as follows: + +Copyright (C) 2005-2007 TWiki Contributors. All Rights Reserved. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 diff --git a/core/lib/Foswiki/Query/UnaryOP.pm b/core/lib/Foswiki/Query/UnaryOP.pm index 8f173fa21f..3882e1a340 100644 --- a/core/lib/Foswiki/Query/UnaryOP.pm +++ b/core/lib/Foswiki/Query/UnaryOP.pm @@ -26,6 +26,12 @@ sub evalUnary { } } +sub evaluatesToConstant { + my $this = shift; + my $node = shift; + return $node->{params}[0]->evaluatesToConstant(@_); +} + 1; __END__ diff --git a/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm b/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm index 11cda35f2f..b6cd05d086 100644 --- a/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm +++ b/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm @@ -37,6 +37,10 @@ sub query { # return new Foswiki::Search::InfoCache($session, ''); #} + # Eliminate static expressions + my $context = Foswiki::Meta->new( $session, $session->{webName} ); + $query->simplify(tom => $context, data => $context ); + my $webNames = $options->{web} || ''; my $recurse = $options->{'recurse'} || ''; my $isAdmin = $session->{users}->isAdmin( $session->{user} );