浏览代码

#383: support negative numbers in OQL
Enhancements: support MySQL bitwise operators (&, |, ^, <<, >>) and hexadecimal numbers (up to 64-bit).

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2709 a333f486-631f-4898-b8df-5754b55c2be0

dflaven 12 年之前
父节点
当前提交
803bf4b9d2
共有 7 个文件被更改,包括 645 次插入480 次删除
  1. 1 1
      core/expression.class.inc.php
  2. 91 54
      core/oql/oql-lexer.php
  3. 28 2
      core/oql/oql-lexer.plex
  4. 478 419
      core/oql/oql-parser.php
  5. 12 2
      core/oql/oql-parser.y
  6. 21 0
      core/oql/oqlquery.class.inc.php
  7. 14 2
      test/testlist.inc.php

+ 1 - 1
core/expression.class.inc.php

@@ -383,7 +383,7 @@ class ScalarExpression extends UnaryExpression
 {
 	public function __construct($value)
 	{
-		if (!is_scalar($value) && !is_null($value))
+		if (!is_scalar($value) && !is_null($value) && (!$value instanceof OqlHexValue))
 		{
 			throw new CoreException('Attempt to create a scalar expression from a non scalar', array('var_type'=>gettype($value)));
 		}

+ 91 - 54
core/oql/oql-lexer.php

@@ -17,6 +17,7 @@
 //   You should have received a copy of the GNU Affero General Public License
 //   along with iTop. If not, see <http://www.gnu.org/licenses/>
 
+
 /**
  * OQL syntax analyzer, to be used prior to run the lexical analyzer
  *
@@ -119,6 +120,11 @@ class OQLLexerRaw
                 '/\G-/ ',
                 '/\GAND/ ',
                 '/\GOR/ ',
+                '/\G\\|/ ',
+                '/\G&/ ',
+                '/\G\\^/ ',
+                '/\G<</ ',
+                '/\G>>/ ',
                 '/\G,/ ',
                 '/\G\\(/ ',
                 '/\G\\)/ ',
@@ -168,7 +174,8 @@ class OQLLexerRaw
                 '/\GABOVE STRICT/ ',
                 '/\GNOT ABOVE/ ',
                 '/\GNOT ABOVE STRICT/ ',
-                '/\G(0x[0-9a-fA-F]+|[0-9]+)/ ',
+                '/\G(0x[0-9a-fA-F]+)/ ',
+                '/\G([0-9]+)/ ',
                 '/\G\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/ ',
                 '/\G([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/ ',
                 '/\G:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/ ',
@@ -333,271 +340,301 @@ class OQLLexerRaw
     function yy_r1_13($yy_subpatterns)
     {
 
-	$this->token = OQLParser::COMA;
+	$this->token = OQLParser::BITWISE_OR;
     }
     function yy_r1_14($yy_subpatterns)
     {
 
-	$this->token = OQLParser::PAR_OPEN;
+	$this->token = OQLParser::BITWISE_AND;
     }
     function yy_r1_15($yy_subpatterns)
     {
 
-	$this->token = OQLParser::PAR_CLOSE;
+	$this->token = OQLParser::BITWISE_XOR;
     }
     function yy_r1_16($yy_subpatterns)
     {
 
-	$this->token = OQLParser::REGEXP;
+	$this->token = OQLParser::BITWISE_LEFT_SHIFT;
     }
     function yy_r1_17($yy_subpatterns)
     {
 
-	$this->token = OQLParser::EQ;
+	$this->token = OQLParser::BITWISE_RIGHT_SHIFT;
     }
     function yy_r1_18($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_EQ;
+	$this->token = OQLParser::COMA;
     }
     function yy_r1_19($yy_subpatterns)
     {
 
-	$this->token = OQLParser::GT;
+	$this->token = OQLParser::PAR_OPEN;
     }
     function yy_r1_20($yy_subpatterns)
     {
 
-	$this->token = OQLParser::LT;
+	$this->token = OQLParser::PAR_CLOSE;
     }
     function yy_r1_21($yy_subpatterns)
     {
 
-	$this->token = OQLParser::GE;
+	$this->token = OQLParser::REGEXP;
     }
     function yy_r1_22($yy_subpatterns)
     {
 
-	$this->token = OQLParser::LE;
+	$this->token = OQLParser::EQ;
     }
     function yy_r1_23($yy_subpatterns)
     {
 
-	$this->token = OQLParser::LIKE;
+	$this->token = OQLParser::NOT_EQ;
     }
     function yy_r1_24($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_LIKE;
+	$this->token = OQLParser::GT;
     }
     function yy_r1_25($yy_subpatterns)
     {
 
-	$this->token = OQLParser::IN;
+	$this->token = OQLParser::LT;
     }
     function yy_r1_26($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_IN;
+	$this->token = OQLParser::GE;
     }
     function yy_r1_27($yy_subpatterns)
     {
 
-	$this->token = OQLParser::INTERVAL;
+	$this->token = OQLParser::LE;
     }
     function yy_r1_28($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_IF;
+	$this->token = OQLParser::LIKE;
     }
     function yy_r1_29($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_ELT;
+	$this->token = OQLParser::NOT_LIKE;
     }
     function yy_r1_30($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_COALESCE;
+	$this->token = OQLParser::IN;
     }
     function yy_r1_31($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_ISNULL;
+	$this->token = OQLParser::NOT_IN;
     }
     function yy_r1_32($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_CONCAT;
+	$this->token = OQLParser::INTERVAL;
     }
     function yy_r1_33($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_SUBSTR;
+	$this->token = OQLParser::F_IF;
     }
     function yy_r1_34($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_TRIM;
+	$this->token = OQLParser::F_ELT;
     }
     function yy_r1_35($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_DATE;
+	$this->token = OQLParser::F_COALESCE;
     }
     function yy_r1_36($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_DATE_FORMAT;
+	$this->token = OQLParser::F_ISNULL;
     }
     function yy_r1_37($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_CURRENT_DATE;
+	$this->token = OQLParser::F_CONCAT;
     }
     function yy_r1_38($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_NOW;
+	$this->token = OQLParser::F_SUBSTR;
     }
     function yy_r1_39($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_TIME;
+	$this->token = OQLParser::F_TRIM;
     }
     function yy_r1_40($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_TO_DAYS;
+	$this->token = OQLParser::F_DATE;
     }
     function yy_r1_41($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_FROM_DAYS;
+	$this->token = OQLParser::F_DATE_FORMAT;
     }
     function yy_r1_42($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_YEAR;
+	$this->token = OQLParser::F_CURRENT_DATE;
     }
     function yy_r1_43($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_MONTH;
+	$this->token = OQLParser::F_NOW;
     }
     function yy_r1_44($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_DAY;
+	$this->token = OQLParser::F_TIME;
     }
     function yy_r1_45($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_HOUR;
+	$this->token = OQLParser::F_TO_DAYS;
     }
     function yy_r1_46($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_MINUTE;
+	$this->token = OQLParser::F_FROM_DAYS;
     }
     function yy_r1_47($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_SECOND;
+	$this->token = OQLParser::F_YEAR;
     }
     function yy_r1_48($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_DATE_ADD;
+	$this->token = OQLParser::F_MONTH;
     }
     function yy_r1_49($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_DATE_SUB;
+	$this->token = OQLParser::F_DAY;
     }
     function yy_r1_50($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_ROUND;
+	$this->token = OQLParser::F_HOUR;
     }
     function yy_r1_51($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_FLOOR;
+	$this->token = OQLParser::F_MINUTE;
     }
     function yy_r1_52($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_INET_ATON;
+	$this->token = OQLParser::F_SECOND;
     }
     function yy_r1_53($yy_subpatterns)
     {
 
-	$this->token = OQLParser::F_INET_NTOA;
+	$this->token = OQLParser::F_DATE_ADD;
     }
     function yy_r1_54($yy_subpatterns)
     {
 
-	$this->token = OQLParser::BELOW;
+	$this->token = OQLParser::F_DATE_SUB;
     }
     function yy_r1_55($yy_subpatterns)
     {
 
-	$this->token = OQLParser::BELOW_STRICT;
+	$this->token = OQLParser::F_ROUND;
     }
     function yy_r1_56($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_BELOW;
+	$this->token = OQLParser::F_FLOOR;
     }
     function yy_r1_57($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_BELOW_STRICT;
+	$this->token = OQLParser::F_INET_ATON;
     }
     function yy_r1_58($yy_subpatterns)
     {
 
-	$this->token = OQLParser::ABOVE;
+	$this->token = OQLParser::F_INET_NTOA;
     }
     function yy_r1_59($yy_subpatterns)
     {
 
-	$this->token = OQLParser::ABOVE_STRICT;
+	$this->token = OQLParser::BELOW;
     }
     function yy_r1_60($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_ABOVE;
+	$this->token = OQLParser::BELOW_STRICT;
     }
     function yy_r1_61($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NOT_ABOVE_STRICT;
+	$this->token = OQLParser::NOT_BELOW;
     }
     function yy_r1_62($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NUMVAL;
+	$this->token = OQLParser::NOT_BELOW_STRICT;
     }
     function yy_r1_63($yy_subpatterns)
     {
 
-	$this->token = OQLParser::STRVAL;
+	$this->token = OQLParser::ABOVE;
     }
     function yy_r1_64($yy_subpatterns)
     {
 
-	$this->token = OQLParser::NAME;
+	$this->token = OQLParser::ABOVE_STRICT;
     }
     function yy_r1_65($yy_subpatterns)
     {
 
-	$this->token = OQLParser::VARNAME;
+	$this->token = OQLParser::NOT_ABOVE;
     }
     function yy_r1_66($yy_subpatterns)
     {
 
+	$this->token = OQLParser::NOT_ABOVE_STRICT;
+    }
+    function yy_r1_67($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::HEXVAL;
+    }
+    function yy_r1_68($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NUMVAL;
+    }
+    function yy_r1_69($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::STRVAL;
+    }
+    function yy_r1_70($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NAME;
+    }
+    function yy_r1_71($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::VARNAME;
+    }
+    function yy_r1_72($yy_subpatterns)
+    {
+
 	$this->token = OQLParser::DOT;
     }
 

+ 28 - 2
core/oql/oql-lexer.plex

@@ -95,6 +95,11 @@ math_plus  = "+"
 math_minus = "-"
 log_and    = "AND"
 log_or     = "OR"
+bitwise_and    = "&"
+bitwise_or     = "|"
+bitwise_xor     = "^"
+bitwise_leftshift     = "<<"
+bitwise_rightshift     = ">>"
 regexp     = "REGEXP"
 eq         = "="
 not_eq     = "!="
@@ -156,8 +161,11 @@ not_above_strict = "NOT ABOVE STRICT"
 //
 // numval     = /([0-9]+|0x[0-9a-fA-F]+)/
 // Does not work either, the hexadecimal numbers are not matched properly
-// The following seems to work...
-numval     = /(0x[0-9a-fA-F]+|[0-9]+)/
+// Anyhow let's distinguish the hexadecimal values from decimal integers, hex numbers will be stored as strings
+// and passed as-is to MySQL which enables us to pass 64-bit values without messing with them in PHP
+//
+hexval     = /(0x[0-9a-fA-F]+)/
+numval     = /([0-9]+)/
 strval     = /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/
 name       = /([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/
 varname    = /:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/
@@ -204,6 +212,21 @@ log_and {
 log_or {
 	$this->token = OQLParser::LOG_OR;
 }
+bitwise_or {
+	$this->token = OQLParser::BITWISE_OR;
+}
+bitwise_and {
+	$this->token = OQLParser::BITWISE_AND;
+}
+bitwise_xor {
+	$this->token = OQLParser::BITWISE_XOR;
+}
+bitwise_leftshift {
+	$this->token = OQLParser::BITWISE_LEFT_SHIFT;
+}
+bitwise_rightshift {
+	$this->token = OQLParser::BITWISE_RIGHT_SHIFT;
+}
 coma {
 	$this->token = OQLParser::COMA;
 }
@@ -351,6 +374,9 @@ not_above {
 not_above_strict {
 	$this->token = OQLParser::NOT_ABOVE_STRICT;
 }
+hexval {
+	$this->token = OQLParser::HEXVAL;
+}
 numval {
 	$this->token = OQLParser::NUMVAL;
 }

文件差异内容过多而无法显示
+ 478 - 419
core/oql/oql-parser.php


+ 12 - 2
core/oql/oql-parser.y

@@ -164,19 +164,23 @@ name(A) ::= NAME(X). {
 	}
 	A = new OqlName($name, $this->m_iColPrev);
 }
-
-num_value(A) ::= NUMVAL(X). {A=X;}
+num_value(A) ::= NUMVAL(X). {A=(int)X;}
+num_value(A) ::= MATH_MINUS NUMVAL(X). {A=(int)-X;}
+num_value(A) ::= HEXVAL(X). {A=new OqlHexValue(X);}
 str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));}
 
 
 operator1(A) ::= num_operator1(X). {A=X;}
+operator1(A) ::= bitwise_operator1(X). {A=X;}
 operator2(A) ::= num_operator2(X). {A=X;}
 operator2(A) ::= str_operator(X). {A=X;}
 operator2(A) ::= REGEXP(X). {A=X;}
 operator2(A) ::= EQ(X). {A=X;}
 operator2(A) ::= NOT_EQ(X). {A=X;}
 operator3(A) ::= LOG_AND(X). {A=X;}
+operator3(A) ::= bitwise_operator3(X). {A=X;}
 operator4(A) ::= LOG_OR(X). {A=X;}
+operator4(A) ::= bitwise_operator4(X). {A=X;}
 
 num_operator1(A) ::= MATH_DIV(X). {A=X;}
 num_operator1(A) ::= MATH_MULT(X). {A=X;}
@@ -190,6 +194,12 @@ num_operator2(A) ::= LE(X). {A=X;}
 str_operator(A) ::= LIKE(X). {A=X;}
 str_operator(A) ::= NOT_LIKE(X). {A=X;}
 
+bitwise_operator1(A) ::= BITWISE_LEFT_SHIFT(X). {A=X;}
+bitwise_operator1(A) ::= BITWISE_RIGHT_SHIFT(X). {A=X;}
+bitwise_operator3(A) ::= BITWISE_AND(X). {A=X;}
+bitwise_operator4(A) ::= BITWISE_OR(X). {A=X;}
+bitwise_operator4(A) ::= BITWISE_XOR(X). {A=X;}
+
 list_operator(A) ::= IN(X). {A=X;}
 list_operator(A) ::= NOT_IN(X). {A=X;}
 

+ 21 - 0
core/oql/oqlquery.class.inc.php

@@ -54,6 +54,27 @@ class OqlName
 	} 
 }
 
+/**
+ * 
+ * Store hexadecimal values as strings so that we can support 64-bit values
+ *
+ */
+class OqlHexValue
+{
+	protected $m_sValue;
+
+	public function __construct($sValue)
+	{
+		$this->m_sValue = $sValue;
+	}
+	
+	public function __toString()
+	{
+		return $this->m_sValue;
+	}
+	
+}
+
 class OqlJoinSpec
 {
 	protected $m_oClass;

+ 14 - 2
test/testlist.inc.php

@@ -140,6 +140,12 @@ class TestOQLParser extends TestFunction
 		$aQueries = array(
 			'SELECT toto' => true,
 			'SELECT toto WHERE toto.a = 1' => true,
+			'SELECT toto WHERE toto.a = -1' => true,
+			'SELECT toto WHERE toto.a = (1-1)' => true,
+			'SELECT toto WHERE toto.a = (-1+3)' => true,
+			'SELECT toto WHERE toto.a = (3+-1)' => true,
+			'SELECT toto WHERE toto.a = (3--1)' => true,
+			'SELECT toto WHERE toto.a = (3++1)' => false,
 			'SELECT toto WHERE toto.a = 0xC' => true,
 			'SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\'' => true,
 			'SELECT toto WHERE toto.a = :myparameter' => true,
@@ -150,7 +156,12 @@ class TestOQLParser extends TestFunction
 			'SELECT toto WHHHERE toto.a = "1"' => false,
 			'SELECT toto WHERE toto.a == "1"' => false,
 			'SELECT toto WHERE toto.a % 1' => false,
-			//'SELECT toto WHERE toto.a LIKE 1' => false,
+			'SELECT toto WHERE toto.a & 1' => true, // bitwise and
+			'SELECT toto WHERE toto.a | 1' => true, // bitwise or
+			'SELECT toto WHERE toto.a ^ 1' => true, // bitwise xor
+			'SELECT toto WHERE toto.a << 1' => true, // bitwise left shift
+			'SELECT toto WHERE toto.a >> 1' => true, // bitwise right shift
+		//'SELECT toto WHERE toto.a LIKE 1' => false,
 			'SELECT toto WHERE toto.a like \'arg\'' => false,
 			'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true,
 			'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false,
@@ -184,7 +195,8 @@ class TestOQLParser extends TestFunction
 			"SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true,
 			"SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true,
 			"SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true,
-
+			"SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2" => true,
+		
 			'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true,
 
 			// Several objects in a row...

部分文件因为文件数量过多而无法显示