*/ public static $operators = array( /* * Some operators (*, =) may have ambiguous flags, because they depend on * the context they are being used in. * For example: 1. SELECT * FROM table; # SQL specific (wildcard) * SELECT 2 * 3; # arithmetic * 2. SELECT * FROM table WHERE foo = 'bar'; * SET @i = 0; */ // @see WP_SQLite_Token::FLAG_OPERATOR_ARITHMETIC '%' => 1, '*' => 1, '+' => 1, '-' => 1, '/' => 1, // @see WP_SQLite_Token::FLAG_OPERATOR_LOGICAL '!' => 2, '!=' => 2, '&&' => 2, '<' => 2, '<=' => 2, '<=>' => 2, '<>' => 2, '=' => 2, '>' => 2, '>=' => 2, '||' => 2, // @see WP_SQLite_Token::FLAG_OPERATOR_BITWISE '&' => 4, '<<' => 4, '>>' => 4, '^' => 4, '|' => 4, '~' => 4, // @see WP_SQLite_Token::FLAG_OPERATOR_ASSIGNMENT ':=' => 8, // @see WP_SQLite_Token::FLAG_OPERATOR_SQL '(' => 16, ')' => 16, '.' => 16, ',' => 16, ';' => 16, ); /** * List of keywords. * * The value associated to each keyword represents its flags. * * @see WP_SQLite_Token::FLAG_KEYWORD_RESERVED * WP_SQLite_Token::FLAG_KEYWORD_COMPOSED * WP_SQLite_Token::FLAG_KEYWORD_DATA_TYPE * ∂WP_SQLite_Token::FLAG_KEYWORD_KEY * WP_SQLite_Token::FLAG_KEYWORD_FUNCTION * * @var array */ public static $keywords = array( 'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1, 'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1, 'ONE' => 1, 'ROW' => 1, 'XID' => 1, 'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1, 'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1, 'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1, 'NAME' => 1, 'NEXT' => 1, 'NONE' => 1, 'ONLY' => 1, 'OPEN' => 1, 'PAGE' => 1, 'PORT' => 1, 'PREV' => 1, 'ROWS' => 1, 'SLOW' => 1, 'SOME' => 1, 'STOP' => 1, 'THAN' => 1, 'TYPE' => 1, 'VIEW' => 1, 'WAIT' => 1, 'WORK' => 1, 'X509' => 1, 'AFTER' => 1, 'BEGIN' => 1, 'BLOCK' => 1, 'BTREE' => 1, 'CACHE' => 1, 'CHAIN' => 1, 'CLOSE' => 1, 'ERROR' => 1, 'EVENT' => 1, 'EVERY' => 1, 'FIRST' => 1, 'FIXED' => 1, 'FLUSH' => 1, 'FOUND' => 1, 'HOSTS' => 1, 'LEVEL' => 1, 'LOCAL' => 1, 'LOCKS' => 1, 'MERGE' => 1, 'MUTEX' => 1, 'NAMES' => 1, 'NCHAR' => 1, 'NEVER' => 1, 'OWNER' => 1, 'PHASE' => 1, 'PROXY' => 1, 'QUERY' => 1, 'QUICK' => 1, 'RELAY' => 1, 'RESET' => 1, 'RTREE' => 1, 'SHARE' => 1, 'SLAVE' => 1, 'START' => 1, 'SUPER' => 1, 'SWAPS' => 1, 'TYPES' => 1, 'UNTIL' => 1, 'VALUE' => 1, 'ACTION' => 1, 'ALWAYS' => 1, 'BACKUP' => 1, 'BINLOG' => 1, 'CIPHER' => 1, 'CLIENT' => 1, 'COMMIT' => 1, 'ENABLE' => 1, 'ENGINE' => 1, 'ERRORS' => 1, 'ESCAPE' => 1, 'EVENTS' => 1, 'EXPIRE' => 1, 'EXPORT' => 1, 'FAULTS' => 1, 'FIELDS' => 1, 'FILTER' => 1, 'GLOBAL' => 1, 'GRANTS' => 1, 'IMPORT' => 1, 'ISSUER' => 1, 'LEAVES' => 1, 'MASTER' => 1, 'MEDIUM' => 1, 'MEMORY' => 1, 'MODIFY' => 1, 'NUMBER' => 1, 'OFFSET' => 1, 'PARSER' => 1, 'PLUGIN' => 1, 'RELOAD' => 1, 'REMOVE' => 1, 'REPAIR' => 1, 'RESUME' => 1, 'ROLLUP' => 1, 'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1, 'SOCKET' => 1, 'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1, 'STATUS' => 1, 'STRING' => 1, 'TABLES' => 1, 'ACCOUNT' => 1, 'ANALYSE' => 1, 'CHANGED' => 1, 'CHANNEL' => 1, 'COLUMNS' => 1, 'COMMENT' => 1, 'COMPACT' => 1, 'CONTEXT' => 1, 'CURRENT' => 1, 'DEFINER' => 1, 'DISABLE' => 1, 'DISCARD' => 1, 'DYNAMIC' => 1, 'ENGINES' => 1, 'EXECUTE' => 1, 'FOLLOWS' => 1, 'GENERAL' => 1, 'HANDLER' => 1, 'INDEXES' => 1, 'INSTALL' => 1, 'INVOKER' => 1, 'LOGFILE' => 1, 'MIGRATE' => 1, 'NO_WAIT' => 1, 'OPTIONS' => 1, 'PARTIAL' => 1, 'PERSIST' => 1, 'PLUGINS' => 1, 'PREPARE' => 1, 'PROFILE' => 1, 'REBUILD' => 1, 'RECOVER' => 1, 'RESTORE' => 1, 'RETURNS' => 1, 'ROUTINE' => 1, 'SESSION' => 1, 'STACKED' => 1, 'STORAGE' => 1, 'SUBJECT' => 1, 'SUSPEND' => 1, 'UNICODE' => 1, 'UNKNOWN' => 1, 'UPGRADE' => 1, 'USE_FRM' => 1, 'WITHOUT' => 1, 'WRAPPER' => 1, 'CASCADED' => 1, 'CHECKSUM' => 1, 'DATAFILE' => 1, 'DUMPFILE' => 1, 'EXCHANGE' => 1, 'EXTENDED' => 1, 'FUNCTION' => 1, 'LANGUAGE' => 1, 'MAX_ROWS' => 1, 'MAX_SIZE' => 1, 'MIN_ROWS' => 1, 'NATIONAL' => 1, 'NVARCHAR' => 1, 'PRECEDES' => 1, 'PRESERVE' => 1, 'PROFILES' => 1, 'REDOFILE' => 1, 'RELAYLOG' => 1, 'ROLLBACK' => 1, 'SCHEDULE' => 1, 'SECURITY' => 1, 'SEQUENCE' => 1, 'SHUTDOWN' => 1, 'SNAPSHOT' => 1, 'SWITCHES' => 1, 'TRIGGERS' => 1, 'UNDOFILE' => 1, 'WARNINGS' => 1, 'AGGREGATE' => 1, 'ALGORITHM' => 1, 'COMMITTED' => 1, 'DIRECTORY' => 1, 'DUPLICATE' => 1, 'EXPANSION' => 1, 'INVISIBLE' => 1, 'IO_THREAD' => 1, 'ISOLATION' => 1, 'NODEGROUP' => 1, 'PACK_KEYS' => 1, 'READ_ONLY' => 1, 'REDUNDANT' => 1, 'SAVEPOINT' => 1, 'SQL_CACHE' => 1, 'TEMPORARY' => 1, 'TEMPTABLE' => 1, 'UNDEFINED' => 1, 'UNINSTALL' => 1, 'VARIABLES' => 1, 'COMPLETION' => 1, 'COMPRESSED' => 1, 'CONCURRENT' => 1, 'CONNECTION' => 1, 'CONSISTENT' => 1, 'DEALLOCATE' => 1, 'IDENTIFIED' => 1, 'MASTER_SSL' => 1, 'NDBCLUSTER' => 1, 'PARTITIONS' => 1, 'PERSISTENT' => 1, 'PLUGIN_DIR' => 1, 'PRIVILEGES' => 1, 'REORGANIZE' => 1, 'REPEATABLE' => 1, 'ROW_FORMAT' => 1, 'SQL_THREAD' => 1, 'TABLESPACE' => 1, 'TABLE_NAME' => 1, 'VALIDATION' => 1, 'COLUMN_NAME' => 1, 'COMPRESSION' => 1, 'CURSOR_NAME' => 1, 'DIAGNOSTICS' => 1, 'EXTENT_SIZE' => 1, 'MASTER_HOST' => 1, 'MASTER_PORT' => 1, 'MASTER_USER' => 1, 'MYSQL_ERRNO' => 1, 'NONBLOCKING' => 1, 'PROCESSLIST' => 1, 'REPLICATION' => 1, 'SCHEMA_NAME' => 1, 'SQL_TSI_DAY' => 1, 'TRANSACTION' => 1, 'UNCOMMITTED' => 1, 'CATALOG_NAME' => 1, 'CLASS_ORIGIN' => 1, 'DEFAULT_AUTH' => 1, 'DES_KEY_FILE' => 1, 'INITIAL_SIZE' => 1, 'MASTER_DELAY' => 1, 'MESSAGE_TEXT' => 1, 'PARTITIONING' => 1, 'PERSIST_ONLY' => 1, 'RELAY_THREAD' => 1, 'SERIALIZABLE' => 1, 'SQL_NO_CACHE' => 1, 'SQL_TSI_HOUR' => 1, 'SQL_TSI_WEEK' => 1, 'SQL_TSI_YEAR' => 1, 'SUBPARTITION' => 1, 'COLUMN_FORMAT' => 1, 'INSERT_METHOD' => 1, 'MASTER_SSL_CA' => 1, 'RELAY_LOG_POS' => 1, 'SQL_TSI_MONTH' => 1, 'SUBPARTITIONS' => 1, 'AUTO_INCREMENT' => 1, 'AVG_ROW_LENGTH' => 1, 'KEY_BLOCK_SIZE' => 1, 'MASTER_LOG_POS' => 1, 'MASTER_SSL_CRL' => 1, 'MASTER_SSL_KEY' => 1, 'RELAY_LOG_FILE' => 1, 'SQL_TSI_MINUTE' => 1, 'SQL_TSI_SECOND' => 1, 'TABLE_CHECKSUM' => 1, 'USER_RESOURCES' => 1, 'AUTOEXTEND_SIZE' => 1, 'CONSTRAINT_NAME' => 1, 'DELAY_KEY_WRITE' => 1, 'FILE_BLOCK_SIZE' => 1, 'MASTER_LOG_FILE' => 1, 'MASTER_PASSWORD' => 1, 'MASTER_SSL_CERT' => 1, 'PARSE_GCOL_EXPR' => 1, 'REPLICATE_DO_DB' => 1, 'SQL_AFTER_GTIDS' => 1, 'SQL_TSI_QUARTER' => 1, 'SUBCLASS_ORIGIN' => 1, 'MASTER_SERVER_ID' => 1, 'REDO_BUFFER_SIZE' => 1, 'SQL_BEFORE_GTIDS' => 1, 'STATS_PERSISTENT' => 1, 'UNDO_BUFFER_SIZE' => 1, 'CONSTRAINT_SCHEMA' => 1, 'GROUP_REPLICATION' => 1, 'IGNORE_SERVER_IDS' => 1, 'MASTER_SSL_CAPATH' => 1, 'MASTER_SSL_CIPHER' => 1, 'RETURNED_SQLSTATE' => 1, 'SQL_BUFFER_RESULT' => 1, 'STATS_AUTO_RECALC' => 1, 'CONSTRAINT_CATALOG' => 1, 'MASTER_RETRY_COUNT' => 1, 'MASTER_SSL_CRLPATH' => 1, 'MAX_STATEMENT_TIME' => 1, 'REPLICATE_DO_TABLE' => 1, 'SQL_AFTER_MTS_GAPS' => 1, 'STATS_SAMPLE_PAGES' => 1, 'REPLICATE_IGNORE_DB' => 1, 'MASTER_AUTO_POSITION' => 1, 'MASTER_CONNECT_RETRY' => 1, 'MAX_QUERIES_PER_HOUR' => 1, 'MAX_UPDATES_PER_HOUR' => 1, 'MAX_USER_CONNECTIONS' => 1, 'REPLICATE_REWRITE_DB' => 1, 'REPLICATE_IGNORE_TABLE' => 1, 'MASTER_HEARTBEAT_PERIOD' => 1, 'REPLICATE_WILD_DO_TABLE' => 1, 'MAX_CONNECTIONS_PER_HOUR' => 1, 'REPLICATE_WILD_IGNORE_TABLE' => 1, 'AS' => 3, 'BY' => 3, 'IS' => 3, 'ON' => 3, 'OR' => 3, 'TO' => 3, 'ADD' => 3, 'ALL' => 3, 'AND' => 3, 'ASC' => 3, 'DEC' => 3, 'DIV' => 3, 'FOR' => 3, 'GET' => 3, 'NOT' => 3, 'OUT' => 3, 'SQL' => 3, 'SSL' => 3, 'USE' => 3, 'XOR' => 3, 'BOTH' => 3, 'CALL' => 3, 'CASE' => 3, 'DESC' => 3, 'DROP' => 3, 'DUAL' => 3, 'EACH' => 3, 'ELSE' => 3, 'EXIT' => 3, 'FROM' => 3, 'INT1' => 3, 'INT2' => 3, 'INT3' => 3, 'INT4' => 3, 'INT8' => 3, 'INTO' => 3, 'JOIN' => 3, 'KEYS' => 3, 'KILL' => 3, 'LIKE' => 3, 'LOAD' => 3, 'LOCK' => 3, 'LONG' => 3, 'LOOP' => 3, 'NULL' => 3, 'OVER' => 3, 'READ' => 3, 'SHOW' => 3, 'THEN' => 3, 'TRUE' => 3, 'UNDO' => 3, 'WHEN' => 3, 'WITH' => 3, 'ALTER' => 3, 'CHECK' => 3, 'CROSS' => 3, 'FALSE' => 3, 'FETCH' => 3, 'FORCE' => 3, 'GRANT' => 3, 'GROUP' => 3, 'INNER' => 3, 'INOUT' => 3, 'LEAVE' => 3, 'LIMIT' => 3, 'LINES' => 3, 'ORDER' => 3, 'OUTER' => 3, 'PURGE' => 3, 'RANGE' => 3, 'READS' => 3, 'RLIKE' => 3, 'TABLE' => 3, 'UNION' => 3, 'USAGE' => 3, 'USING' => 3, 'WHERE' => 3, 'WHILE' => 3, 'WRITE' => 3, 'BEFORE' => 3, 'CHANGE' => 3, 'COLUMN' => 3, 'CREATE' => 3, 'CURSOR' => 3, 'DELETE' => 3, 'ELSEIF' => 3, 'EXCEPT' => 3, 'FLOAT4' => 3, 'FLOAT8' => 3, 'HAVING' => 3, 'IGNORE' => 3, 'INFILE' => 3, 'LINEAR' => 3, 'OPTION' => 3, 'REGEXP' => 3, 'RENAME' => 3, 'RETURN' => 3, 'REVOKE' => 3, 'SELECT' => 3, 'SIGNAL' => 3, 'STORED' => 3, 'UNLOCK' => 3, 'UPDATE' => 3, 'ANALYZE' => 3, 'BETWEEN' => 3, 'CASCADE' => 3, 'COLLATE' => 3, 'DECLARE' => 3, 'DELAYED' => 3, 'ESCAPED' => 3, 'EXPLAIN' => 3, 'FOREIGN' => 3, 'ITERATE' => 3, 'LEADING' => 3, 'NATURAL' => 3, 'OUTFILE' => 3, 'PRIMARY' => 3, 'RELEASE' => 3, 'REQUIRE' => 3, 'SCHEMAS' => 3, 'TRIGGER' => 3, 'VARYING' => 3, 'VIRTUAL' => 3, 'CONTINUE' => 3, 'DAY_HOUR' => 3, 'DESCRIBE' => 3, 'DISTINCT' => 3, 'ENCLOSED' => 3, 'MAXVALUE' => 3, 'MODIFIES' => 3, 'OPTIMIZE' => 3, 'RESIGNAL' => 3, 'RESTRICT' => 3, 'SPECIFIC' => 3, 'SQLSTATE' => 3, 'STARTING' => 3, 'TRAILING' => 3, 'UNSIGNED' => 3, 'ZEROFILL' => 3, 'CONDITION' => 3, 'DATABASES' => 3, 'GENERATED' => 3, 'INTERSECT' => 3, 'MIDDLEINT' => 3, 'PARTITION' => 3, 'PRECISION' => 3, 'PROCEDURE' => 3, 'RECURSIVE' => 3, 'SENSITIVE' => 3, 'SEPARATOR' => 3, 'ACCESSIBLE' => 3, 'ASENSITIVE' => 3, 'CONSTRAINT' => 3, 'DAY_MINUTE' => 3, 'DAY_SECOND' => 3, 'OPTIONALLY' => 3, 'READ_WRITE' => 3, 'REFERENCES' => 3, 'SQLWARNING' => 3, 'TERMINATED' => 3, 'YEAR_MONTH' => 3, 'DISTINCTROW' => 3, 'HOUR_MINUTE' => 3, 'HOUR_SECOND' => 3, 'INSENSITIVE' => 3, 'MASTER_BIND' => 3, 'LOW_PRIORITY' => 3, 'SQLEXCEPTION' => 3, 'VARCHARACTER' => 3, 'DETERMINISTIC' => 3, 'HIGH_PRIORITY' => 3, 'MINUTE_SECOND' => 3, 'STRAIGHT_JOIN' => 3, 'IO_AFTER_GTIDS' => 3, 'SQL_BIG_RESULT' => 3, 'DAY_MICROSECOND' => 3, 'IO_BEFORE_GTIDS' => 3, 'OPTIMIZER_COSTS' => 3, 'HOUR_MICROSECOND' => 3, 'SQL_SMALL_RESULT' => 3, 'MINUTE_MICROSECOND' => 3, 'NO_WRITE_TO_BINLOG' => 3, 'SECOND_MICROSECOND' => 3, 'SQL_CALC_FOUND_ROWS' => 3, 'MASTER_SSL_VERIFY_SERVER_CERT' => 3, 'NO SQL' => 7, 'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7, 'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7, 'LESS THAN' => 7, 'LOAD DATA' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7, 'UNION ALL' => 7, 'CROSS JOIN' => 7, 'ESCAPED BY' => 7, 'FOR UPDATE' => 7, 'INNER JOIN' => 7, 'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7, 'ENCLOSED BY' => 7, 'LINEAR HASH' => 7, 'ON SCHEDULE' => 7, 'STARTING BY' => 7, 'AND NO CHAIN' => 7, 'CONTAINS SQL' => 7, 'FOR EACH ROW' => 7, 'NATURAL JOIN' => 7, 'PARTITION BY' => 7, 'SET PASSWORD' => 7, 'SQL SECURITY' => 7, 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, 'TERMINATED BY' => 7, 'DATA DIRECTORY' => 7, 'READS SQL DATA' => 7, 'UNION DISTINCT' => 7, 'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'FULL OUTER JOIN' => 7, 'INDEX DIRECTORY' => 7, 'LEFT OUTER JOIN' => 7, 'SUBPARTITION BY' => 7, 'DISABLE ON SLAVE' => 7, 'GENERATED ALWAYS' => 7, 'RIGHT OUTER JOIN' => 7, 'MODIFIES SQL DATA' => 7, 'NATURAL LEFT JOIN' => 7, 'START TRANSACTION' => 7, 'LOCK IN SHARE MODE' => 7, 'NATURAL RIGHT JOIN' => 7, 'SELECT TRANSACTION' => 7, 'DEFAULT CHARACTER SET' => 7, 'ON COMPLETION PRESERVE' => 7, 'NATURAL LEFT OUTER JOIN' => 7, 'NATURAL RIGHT OUTER JOIN' => 7, 'WITH CONSISTENT SNAPSHOT' => 7, 'ON COMPLETION NOT PRESERVE' => 7, 'BIT' => 9, 'XML' => 9, 'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9, 'ARRAY' => 9, 'SERIAL' => 9, 'BOOLEAN' => 9, 'DATETIME' => 9, 'GEOMETRY' => 9, 'MULTISET' => 9, 'MULTILINEPOINT' => 9, 'MULTILINEPOLYGON' => 9, 'INT' => 11, 'SET' => 11, 'BLOB' => 11, 'REAL' => 11, 'FLOAT' => 11, 'BIGINT' => 11, 'DOUBLE' => 11, 'DECIMAL' => 11, 'INTEGER' => 11, 'NUMERIC' => 11, 'TINYINT' => 11, 'VARCHAR' => 11, 'LONGBLOB' => 11, 'LONGTEXT' => 11, 'SMALLINT' => 11, 'TINYBLOB' => 11, 'TINYTEXT' => 11, 'CHARACTER' => 11, 'MEDIUMINT' => 11, 'VARBINARY' => 11, 'MEDIUMBLOB' => 11, 'MEDIUMTEXT' => 11, 'BINARY VARYING' => 15, 'KEY' => 19, 'INDEX' => 19, 'UNIQUE' => 19, 'SPATIAL' => 19, 'FULLTEXT' => 19, 'INDEX KEY' => 23, 'UNIQUE KEY' => 23, 'FOREIGN KEY' => 23, 'PRIMARY KEY' => 23, 'SPATIAL KEY' => 23, 'FULLTEXT KEY' => 23, 'UNIQUE INDEX' => 23, 'SPATIAL INDEX' => 23, 'FULLTEXT INDEX' => 23, 'X' => 33, 'Y' => 33, 'LN' => 33, 'PI' => 33, 'ABS' => 33, 'AVG' => 33, 'BIN' => 33, 'COS' => 33, 'COT' => 33, 'DAY' => 33, 'ELT' => 33, 'EXP' => 33, 'HEX' => 33, 'LOG' => 33, 'MAX' => 33, 'MD5' => 33, 'MID' => 33, 'MIN' => 33, 'NOW' => 33, 'OCT' => 33, 'ORD' => 33, 'POW' => 33, 'SHA' => 33, 'SIN' => 33, 'STD' => 33, 'SUM' => 33, 'TAN' => 33, 'ACOS' => 33, 'AREA' => 33, 'ASIN' => 33, 'ATAN' => 33, 'CAST' => 33, 'CEIL' => 33, 'CONV' => 33, 'HOUR' => 33, 'LOG2' => 33, 'LPAD' => 33, 'RAND' => 33, 'RPAD' => 33, 'SHA1' => 33, 'SHA2' => 33, 'SIGN' => 33, 'SQRT' => 33, 'SRID' => 33, 'ST_X' => 33, 'ST_Y' => 33, 'TRIM' => 33, 'USER' => 33, 'UUID' => 33, 'WEEK' => 33, 'ASCII' => 33, 'ASWKB' => 33, 'ASWKT' => 33, 'ATAN2' => 33, 'COUNT' => 33, 'CRC32' => 33, 'FIELD' => 33, 'FLOOR' => 33, 'INSTR' => 33, 'LCASE' => 33, 'LEAST' => 33, 'LOG10' => 33, 'LOWER' => 33, 'LTRIM' => 33, 'MONTH' => 33, 'POWER' => 33, 'QUOTE' => 33, 'ROUND' => 33, 'RTRIM' => 33, 'SLEEP' => 33, 'SPACE' => 33, 'UCASE' => 33, 'UNHEX' => 33, 'UPPER' => 33, 'ASTEXT' => 33, 'BIT_OR' => 33, 'BUFFER' => 33, 'CONCAT' => 33, 'DECODE' => 33, 'ENCODE' => 33, 'EQUALS' => 33, 'FORMAT' => 33, 'IFNULL' => 33, 'ISNULL' => 33, 'LENGTH' => 33, 'LOCATE' => 33, 'MINUTE' => 33, 'NULLIF' => 33, 'POINTN' => 33, 'SECOND' => 33, 'STDDEV' => 33, 'STRCMP' => 33, 'SUBSTR' => 33, 'WITHIN' => 33, 'ADDDATE' => 33, 'ADDTIME' => 33, 'AGAINST' => 33, 'BIT_AND' => 33, 'BIT_XOR' => 33, 'CEILING' => 33, 'CHARSET' => 33, 'CROSSES' => 33, 'CURDATE' => 33, 'CURTIME' => 33, 'DAYNAME' => 33, 'DEGREES' => 33, 'ENCRYPT' => 33, 'EXTRACT' => 33, 'GLENGTH' => 33, 'ISEMPTY' => 33, 'IS_IPV4' => 33, 'IS_IPV6' => 33, 'IS_UUID' => 33, 'QUARTER' => 33, 'RADIANS' => 33, 'REVERSE' => 33, 'SOUNDEX' => 33, 'ST_AREA' => 33, 'ST_SRID' => 33, 'SUBDATE' => 33, 'SUBTIME' => 33, 'SYSDATE' => 33, 'TOUCHES' => 33, 'TO_DAYS' => 33, 'VAR_POP' => 33, 'VERSION' => 33, 'WEEKDAY' => 33, 'ASBINARY' => 33, 'CENTROID' => 33, 'COALESCE' => 33, 'COMPRESS' => 33, 'CONTAINS' => 33, 'DATEDIFF' => 33, 'DATE_ADD' => 33, 'DATE_SUB' => 33, 'DISJOINT' => 33, 'DISTANCE' => 33, 'ENDPOINT' => 33, 'ENVELOPE' => 33, 'GET_LOCK' => 33, 'GREATEST' => 33, 'ISCLOSED' => 33, 'ISSIMPLE' => 33, 'JSON_SET' => 33, 'MAKEDATE' => 33, 'MAKETIME' => 33, 'MAKE_SET' => 33, 'MBREQUAL' => 33, 'OVERLAPS' => 33, 'PASSWORD' => 33, 'POSITION' => 33, 'ST_ASWKB' => 33, 'ST_ASWKT' => 33, 'ST_UNION' => 33, 'TIMEDIFF' => 33, 'TRUNCATE' => 33, 'VARIANCE' => 33, 'VAR_SAMP' => 33, 'YEARWEEK' => 33, 'ANY_VALUE' => 33, 'BENCHMARK' => 33, 'BIT_COUNT' => 33, 'COLLATION' => 33, 'CONCAT_WS' => 33, 'DAYOFWEEK' => 33, 'DAYOFYEAR' => 33, 'DIMENSION' => 33, 'FROM_DAYS' => 33, 'GEOMETRYN' => 33, 'INET_ATON' => 33, 'INET_NTOA' => 33, 'JSON_KEYS' => 33, 'JSON_TYPE' => 33, 'LOAD_FILE' => 33, 'MBRCOVERS' => 33, 'MBREQUALS' => 33, 'MBRWITHIN' => 33, 'MONTHNAME' => 33, 'NUMPOINTS' => 33, 'ROW_COUNT' => 33, 'ST_ASTEXT' => 33, 'ST_BUFFER' => 33, 'ST_EQUALS' => 33, 'ST_LENGTH' => 33, 'ST_POINTN' => 33, 'ST_WITHIN' => 33, 'SUBSTRING' => 33, 'TO_BASE64' => 33, 'UPDATEXML' => 33, 'BIT_LENGTH' => 33, 'CONVERT_TZ' => 33, 'CONVEXHULL' => 33, 'DAYOFMONTH' => 33, 'EXPORT_SET' => 33, 'FOUND_ROWS' => 33, 'GET_FORMAT' => 33, 'INET6_ATON' => 33, 'INET6_NTOA' => 33, 'INTERSECTS' => 33, 'JSON_ARRAY' => 33, 'JSON_DEPTH' => 33, 'JSON_MERGE' => 33, 'JSON_QUOTE' => 33, 'JSON_VALID' => 33, 'MBRTOUCHES' => 33, 'NAME_CONST' => 33, 'PERIOD_ADD' => 33, 'STARTPOINT' => 33, 'STDDEV_POP' => 33, 'ST_CROSSES' => 33, 'ST_GEOHASH' => 33, 'ST_ISEMPTY' => 33, 'ST_ISVALID' => 33, 'ST_TOUCHES' => 33, 'TO_SECONDS' => 33, 'UNCOMPRESS' => 33, 'UUID_SHORT' => 33, 'WEEKOFYEAR' => 33, 'AES_DECRYPT' => 33, 'AES_ENCRYPT' => 33, 'BIN_TO_UUID' => 33, 'CHAR_LENGTH' => 33, 'DATE_FORMAT' => 33, 'DES_DECRYPT' => 33, 'DES_ENCRYPT' => 33, 'FIND_IN_SET' => 33, 'FROM_BASE64' => 33, 'GEOMFROMWKB' => 33, 'GTID_SUBSET' => 33, 'JSON_INSERT' => 33, 'JSON_LENGTH' => 33, 'JSON_OBJECT' => 33, 'JSON_PRETTY' => 33, 'JSON_REMOVE' => 33, 'JSON_SEARCH' => 33, 'LINEFROMWKB' => 33, 'MBRCONTAINS' => 33, 'MBRDISJOINT' => 33, 'MBROVERLAPS' => 33, 'MICROSECOND' => 33, 'PERIOD_DIFF' => 33, 'POLYFROMWKB' => 33, 'SEC_TO_TIME' => 33, 'STDDEV_SAMP' => 33, 'STR_TO_DATE' => 33, 'ST_ASBINARY' => 33, 'ST_CENTROID' => 33, 'ST_CONTAINS' => 33, 'ST_DISJOINT' => 33, 'ST_DISTANCE' => 33, 'ST_ENDPOINT' => 33, 'ST_ENVELOPE' => 33, 'ST_ISCLOSED' => 33, 'ST_ISSIMPLE' => 33, 'ST_OVERLAPS' => 33, 'ST_SIMPLIFY' => 33, 'ST_VALIDATE' => 33, 'SYSTEM_USER' => 33, 'TIME_FORMAT' => 33, 'TIME_TO_SEC' => 33, 'UUID_TO_BIN' => 33, 'COERCIBILITY' => 33, 'EXTERIORRING' => 33, 'EXTRACTVALUE' => 33, 'GEOMETRYTYPE' => 33, 'GEOMFROMTEXT' => 33, 'GROUP_CONCAT' => 33, 'IS_FREE_LOCK' => 33, 'IS_USED_LOCK' => 33, 'JSON_EXTRACT' => 33, 'JSON_REPLACE' => 33, 'JSON_UNQUOTE' => 33, 'LINEFROMTEXT' => 33, 'MBRCOVEREDBY' => 33, 'MLINEFROMWKB' => 33, 'MPOLYFROMWKB' => 33, 'OCTET_LENGTH' => 33, 'OLD_PASSWORD' => 33, 'POINTFROMWKB' => 33, 'POLYFROMTEXT' => 33, 'RANDOM_BYTES' => 33, 'RELEASE_LOCK' => 33, 'SESSION_USER' => 33, 'ST_ASGEOJSON' => 33, 'ST_DIMENSION' => 33, 'ST_GEOMETRYN' => 33, 'ST_NUMPOINTS' => 33, 'TIMESTAMPADD' => 33, 'CONNECTION_ID' => 33, 'FROM_UNIXTIME' => 33, 'GTID_SUBTRACT' => 33, 'INTERIORRINGN' => 33, 'JSON_CONTAINS' => 33, 'MBRINTERSECTS' => 33, 'MLINEFROMTEXT' => 33, 'MPOINTFROMWKB' => 33, 'MPOLYFROMTEXT' => 33, 'NUMGEOMETRIES' => 33, 'POINTFROMTEXT' => 33, 'ST_CONVEXHULL' => 33, 'ST_DIFFERENCE' => 33, 'ST_INTERSECTS' => 33, 'ST_STARTPOINT' => 33, 'TIMESTAMPDIFF' => 33, 'WEIGHT_STRING' => 33, 'IS_IPV4_COMPAT' => 33, 'IS_IPV4_MAPPED' => 33, 'LAST_INSERT_ID' => 33, 'MPOINTFROMTEXT' => 33, 'POLYGONFROMWKB' => 33, 'ST_GEOMFROMWKB' => 33, 'ST_LINEFROMWKB' => 33, 'ST_POLYFROMWKB' => 33, 'UNIX_TIMESTAMP' => 33, 'GEOMCOLLFROMWKB' => 33, 'MASTER_POS_WAIT' => 33, 'POLYGONFROMTEXT' => 33, 'ST_EXTERIORRING' => 33, 'ST_GEOMETRYTYPE' => 33, 'ST_GEOMFROMTEXT' => 33, 'ST_INTERSECTION' => 33, 'ST_LINEFROMTEXT' => 33, 'ST_MAKEENVELOPE' => 33, 'ST_MLINEFROMWKB' => 33, 'ST_MPOLYFROMWKB' => 33, 'ST_POINTFROMWKB' => 33, 'ST_POLYFROMTEXT' => 33, 'SUBSTRING_INDEX' => 33, 'CHARACTER_LENGTH' => 33, 'GEOMCOLLFROMTEXT' => 33, 'GEOMETRYFROMTEXT' => 33, 'JSON_MERGE_PATCH' => 33, 'NUMINTERIORRINGS' => 33, 'ST_INTERIORRINGN' => 33, 'ST_MLINEFROMTEXT' => 33, 'ST_MPOINTFROMWKB' => 33, 'ST_MPOLYFROMTEXT' => 33, 'ST_NUMGEOMETRIES' => 33, 'ST_POINTFROMTEXT' => 33, 'ST_SYMDIFFERENCE' => 33, 'JSON_ARRAY_APPEND' => 33, 'JSON_ARRAY_INSERT' => 33, 'JSON_STORAGE_FREE' => 33, 'JSON_STORAGE_SIZE' => 33, 'LINESTRINGFROMWKB' => 33, 'MULTIPOINTFROMWKB' => 33, 'RELEASE_ALL_LOCKS' => 33, 'ST_LATFROMGEOHASH' => 33, 'ST_MPOINTFROMTEXT' => 33, 'ST_POLYGONFROMWKB' => 33, 'JSON_CONTAINS_PATH' => 33, 'MULTIPOINTFROMTEXT' => 33, 'ST_BUFFER_STRATEGY' => 33, 'ST_DISTANCE_SPHERE' => 33, 'ST_GEOMCOLLFROMTXT' => 33, 'ST_GEOMCOLLFROMWKB' => 33, 'ST_GEOMFROMGEOJSON' => 33, 'ST_LONGFROMGEOHASH' => 33, 'ST_POLYGONFROMTEXT' => 33, 'JSON_MERGE_PRESERVE' => 33, 'MULTIPOLYGONFROMWKB' => 33, 'ST_GEOMCOLLFROMTEXT' => 33, 'ST_GEOMETRYFROMTEXT' => 33, 'ST_NUMINTERIORRINGS' => 33, 'ST_POINTFROMGEOHASH' => 33, 'UNCOMPRESSED_LENGTH' => 33, 'MULTIPOLYGONFROMTEXT' => 33, 'ST_LINESTRINGFROMWKB' => 33, 'ST_MULTIPOINTFROMWKB' => 33, 'ST_MULTIPOINTFROMTEXT' => 33, 'MULTILINESTRINGFROMWKB' => 33, 'ST_MULTIPOLYGONFROMWKB' => 33, 'MULTILINESTRINGFROMTEXT' => 33, 'ST_MULTIPOLYGONFROMTEXT' => 33, 'GEOMETRYCOLLECTIONFROMWKB' => 33, 'ST_MULTILINESTRINGFROMWKB' => 33, 'GEOMETRYCOLLECTIONFROMTEXT' => 33, 'ST_MULTILINESTRINGFROMTEXT' => 33, 'VALIDATE_PASSWORD_STRENGTH' => 33, 'WAIT_FOR_EXECUTED_GTID_SET' => 33, 'ST_GEOMETRYCOLLECTIONFROMWKB' => 33, 'ST_GEOMETRYCOLLECTIONFROMTEXT' => 33, 'WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS' => 33, 'IF' => 35, 'IN' => 35, 'MOD' => 35, 'LEFT' => 35, 'MATCH' => 35, 'RIGHT' => 35, 'EXISTS' => 35, 'INSERT' => 35, 'REPEAT' => 35, 'SCHEMA' => 35, 'VALUES' => 35, 'CONVERT' => 35, 'DEFAULT' => 35, 'REPLACE' => 35, 'DATABASE' => 35, 'UTC_DATE' => 35, 'UTC_TIME' => 35, 'LOCALTIME' => 35, 'CURRENT_DATE' => 35, 'CURRENT_TIME' => 35, 'CURRENT_USER' => 35, 'UTC_TIMESTAMP' => 35, 'LOCALTIMESTAMP' => 35, 'CURRENT_TIMESTAMP' => 35, 'NOT IN' => 39, 'DATE' => 41, 'TIME' => 41, 'YEAR' => 41, 'POINT' => 41, 'POLYGON' => 41, 'TIMESTAMP' => 41, 'LINESTRING' => 41, 'MULTIPOINT' => 41, 'MULTIPOLYGON' => 41, 'MULTILINESTRING' => 41, 'GEOMETRYCOLLECTION' => 41, 'CHAR' => 43, 'BINARY' => 43, 'INTERVAL' => 43, ); /** * All data type options. * * @var array> */ public static $data_type_options = array( 'BINARY' => 1, 'CHARACTER SET' => array( 2, 'var', ), 'CHARSET' => array( 2, 'var', ), 'COLLATE' => array( 3, 'var', ), 'UNSIGNED' => 4, 'ZEROFILL' => 5, ); /** * All field options. * * @var array>> */ public static $field_options = array( /* * Tells the `OptionsArray` to not sort the options. * See the note below. */ '_UNSORTED' => true, 'NOT NULL' => 1, 'NULL' => 1, 'DEFAULT' => array( 2, 'expr', array( 'breakOnAlias' => true ), ), // Following are not according to grammar, but MySQL happily accepts these at any location. 'CHARSET' => array( 2, 'var', ), 'COLLATE' => array( 3, 'var', ), 'AUTO_INCREMENT' => 3, 'PRIMARY' => 4, 'PRIMARY KEY' => 4, 'UNIQUE' => 4, 'UNIQUE KEY' => 4, 'COMMENT' => array( 5, 'var', ), 'COLUMN_FORMAT' => array( 6, 'var', ), 'ON UPDATE' => array( 7, 'expr', ), // Generated columns options. 'GENERATED ALWAYS' => 8, 'AS' => array( 9, 'expr', array( 'parenthesesDelimited' => true ), ), 'VIRTUAL' => 10, 'PERSISTENT' => 11, 'STORED' => 11, 'CHECK' => array( 12, 'expr', array( 'parenthesesDelimited' => true ), ), 'INVISIBLE' => 13, 'ENFORCED' => 14, 'NOT' => 15, 'COMPRESSED' => 16, /* * Common entries. * * NOTE: Some of the common options are not in the same order which * causes troubles when checking if the options are in the right order. * I should find a way to define multiple sets of options and make the * parser select the right set. * * 'UNIQUE' => 4, * 'UNIQUE KEY' => 4, * 'COMMENT' => [5, 'var'], * 'NOT NULL' => 1, * 'NULL' => 1, * 'PRIMARY' => 4, * 'PRIMARY KEY' => 4, */ ); /** * Quotes mode. * * @link https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_ansi_quotes * @link https://mariadb.com/kb/en/sql-mode/#ansi_quotes */ const SQL_MODE_ANSI_QUOTES = 2; /** * The array of tokens. * * @var stdClass[] */ public $tokens = array(); /** * The count of tokens. * * @var int */ public $tokens_count = 0; /** * The index of the next token to be returned. * * @var int */ public $tokens_index = 0; /** * The object constructor. * * @param string $str The query to be lexed. * @param string $delimiter The delimiter to be used. */ public function __construct( $str, $delimiter = null ) { $this->str = $str; // `strlen` is used instead of `mb_strlen` because the lexer needs to parse each byte of the input. $this->string_length = strlen( $str ); // Setting the delimiter. $this->set_delimiter( ! empty( $delimiter ) ? $delimiter : static::$default_delimiter ); $this->lex(); } /** * Sets the delimiter. * * @param string $delimiter The new delimiter. * * @return void */ public function set_delimiter( $delimiter ) { $this->delimiter = $delimiter; $this->delimiter_length = strlen( $delimiter ); } /** * Parses the string and extracts lexemes. * * @return void */ public function lex() { /* * TODO: Sometimes, static::parse* functions make unnecessary calls to * is* functions. For a better performance, some rules can be deduced * from context. * For example, in `parse_bool` there is no need to compare the token * every time with `true` and `false`. The first step would be to * compare with 'true' only and just after that add another letter from * context and compare again with `false`. * Another example is `parse_comment`. */ /** * Last processed token. * * @var WP_SQLite_Token */ $last_token = null; for ( $this->last = 0, $last_idx = 0; $this->last < $this->string_length; $last_idx = ++$this->last ) { /** * The new token. * * @var WP_SQLite_Token */ $token = null; foreach ( self::PARSER_METHODS as $method ) { $token = $this->$method(); if ( $token ) { break; } } if ( null === $token ) { $token = new WP_SQLite_Token( $this->str[ $this->last ] ); $this->error( 'Unexpected character.', $this->str[ $this->last ], $this->last ); } elseif ( null !== $last_token && WP_SQLite_Token::TYPE_SYMBOL === $token->type && $token->flags & WP_SQLite_Token::FLAG_SYMBOL_VARIABLE && ( WP_SQLite_Token::TYPE_STRING === $last_token->type || ( WP_SQLite_Token::TYPE_SYMBOL === $last_token->type && $last_token->flags & WP_SQLite_Token::FLAG_SYMBOL_BACKTICK ) ) ) { // Handles ```... FROM 'user'@'%' ...```. $last_token->token .= $token->token; $last_token->type = WP_SQLite_Token::TYPE_SYMBOL; $last_token->flags = WP_SQLite_Token::FLAG_SYMBOL_USER; $last_token->value .= '@' . $token->value; continue; } elseif ( null !== $last_token && WP_SQLite_Token::TYPE_KEYWORD === $token->type && WP_SQLite_Token::TYPE_OPERATOR === $last_token->type && '.' === $last_token->value ) { // Handles ```... tbl.FROM ...```. In this case, FROM is not a reserved word. $token->type = WP_SQLite_Token::TYPE_NONE; $token->flags = 0; $token->value = $token->token; } $token->position = $last_idx; $this->tokens[ $this->tokens_count++ ] = $token; // Handling delimiters. if ( WP_SQLite_Token::TYPE_NONE === $token->type && 'DELIMITER' === $token->value ) { if ( $this->last + 1 >= $this->string_length ) { $this->error( 'Expected whitespace(s) before delimiter.', '', $this->last + 1 ); continue; } /* * Skipping last R (from `delimiteR`) and whitespaces between * the keyword `DELIMITER` and the actual delimiter. */ $pos = ++$this->last; $token = $this->parse_whitespace(); if ( null !== $token ) { $token->position = $pos; $this->tokens[ $this->tokens_count++ ] = $token; } // Preparing the token that holds the new delimiter. if ( $this->last + 1 >= $this->string_length ) { $this->error( 'Expected delimiter.', '', $this->last + 1 ); continue; } $pos = $this->last + 1; // Parsing the delimiter. $this->delimiter = null; $delimiter_length = 0; while ( ++$this->last < $this->string_length && ! static::is_whitespace( $this->str[ $this->last ] ) && $delimiter_length < 15 ) { $this->delimiter .= $this->str[ $this->last ]; ++$delimiter_length; } if ( empty( $this->delimiter ) ) { $this->error( 'Expected delimiter.', '', $this->last ); $this->delimiter = ';'; } --$this->last; // Saving the delimiter and its token. $this->delimiter_length = strlen( $this->delimiter ); $token = new WP_SQLite_Token( $this->delimiter, WP_SQLite_Token::TYPE_DELIMITER ); $token->position = $pos; $this->tokens[ $this->tokens_count++ ] = $token; } $last_token = $token; } // Adding a final delimiter to mark the ending. $this->tokens[ $this->tokens_count++ ] = new WP_SQLite_Token( null, WP_SQLite_Token::TYPE_DELIMITER ); $this->solve_ambiguity_on_star_operator(); $this->solve_ambiguity_on_function_keywords(); } /** * Resolves the ambiguity when dealing with the "*" operator. * * In SQL statements, the "*" operator can be an arithmetic operator (like in 2*3) or an SQL wildcard (like in * SELECT a.* FROM ...). To solve this ambiguity, the solution is to find the next token, excluding whitespaces and * comments, right after the "*" position. The "*" is for sure an SQL wildcard if the next token found is any of: * - "FROM" (the FROM keyword like in "SELECT * FROM..."); * - "USING" (the USING keyword like in "DELETE table_name.* USING..."); * - "," (a comma separator like in "SELECT *, field FROM..."); * - ")" (a closing parenthesis like in "COUNT(*)"). * This methods will change the flag of the "*" tokens when any of those condition above is true. Otherwise, the * default flag (arithmetic) will be kept. * * @return void */ private function solve_ambiguity_on_star_operator() { $i_bak = $this->tokens_index; while ( true ) { $star_token = $this->tokens_get_next_of_type_and_value( WP_SQLite_Token::TYPE_OPERATOR, '*' ); if ( null === $star_token ) { break; } // tokens_get_next() already gets rid of whitespaces and comments. $next = $this->tokens_get_next(); if ( null === $next ) { continue; } if ( ( WP_SQLite_Token::TYPE_KEYWORD !== $next->type || ! in_array( $next->value, array( 'FROM', 'USING' ), true ) ) && ( WP_SQLite_Token::TYPE_OPERATOR !== $next->type || ! in_array( $next->value, array( ',', ')' ), true ) ) ) { continue; } $star_token->flags = WP_SQLite_Token::FLAG_OPERATOR_SQL; } $this->tokens_index = $i_bak; } /** * Resolves the ambiguity when dealing with the functions keywords. * * In SQL statements, the function keywords might be used as table names or columns names. * To solve this ambiguity, the solution is to find the next token, excluding whitespaces and * comments, right after the function keyword position. The function keyword is for sure used * as column name or table name if the next token found is any of: * * - "FROM" (the FROM keyword like in "SELECT Country x, AverageSalary avg FROM..."); * - "WHERE" (the WHERE keyword like in "DELETE FROM emp x WHERE x.salary = 20"); * - "SET" (the SET keyword like in "UPDATE Country x, City y set x.Name=x.Name"); * - "," (a comma separator like 'x,' in "UPDATE Country x, City y set x.Name=x.Name"); * - "." (a dot separator like in "x.asset_id FROM (SELECT evt.asset_id FROM evt)". * - "NULL" (when used as a table alias like in "avg.col FROM (SELECT ev.col FROM ev) avg"). * * This method will change the flag of the function keyword tokens when any of those * condition above is true. Otherwise, the * default flag (function keyword) will be kept. * * @return void */ private function solve_ambiguity_on_function_keywords() { $i_bak = $this->tokens_index; $keyword_function = WP_SQLite_Token::TYPE_KEYWORD | WP_SQLite_Token::FLAG_KEYWORD_FUNCTION; while ( true ) { $keyword_token = $this->tokens_get_next_of_type_and_flag( WP_SQLite_Token::TYPE_KEYWORD, $keyword_function ); if ( null === $keyword_token ) { break; } $next = $this->tokens_get_next(); if ( ( WP_SQLite_Token::TYPE_KEYWORD !== $next->type || ! in_array( $next->value, self::KEYWORD_NAME_INDICATORS, true ) ) && ( WP_SQLite_Token::TYPE_OPERATOR !== $next->type || ! in_array( $next->value, self::OPERATOR_NAME_INDICATORS, true ) ) && ( null !== $next->value ) ) { continue; } $keyword_token->type = WP_SQLite_Token::TYPE_NONE; $keyword_token->flags = WP_SQLite_Token::TYPE_NONE; $keyword_token->keyword = $keyword_token->value; } $this->tokens_index = $i_bak; } /** * Creates a new error log. * * @param string $msg The error message. * @param string $str The character that produced the error. * @param int $pos The position of the character. * @param int $code The code of the error. * * @throws Exception The error log. * @return void */ public function error( $msg, $str = '', $pos = 0, $code = 0 ) { throw new Exception( print_r( array( 'query' => $this->str, 'message' => $msg, 'str' => $str, 'position' => $pos, 'code' => $code, ), true ) ); } /** * Parses a keyword. * * @return WP_SQLite_Token|null */ public function parse_keyword() { $token = ''; /** * Value to be returned. * * @var WP_SQLite_Token */ $ret = null; // The value of `$this->last` where `$token` ends in `$this->str`. $i_end = $this->last; // Whether last parsed character is a whitespace. $last_space = false; for ( $j = 1; $j < static::KEYWORD_MAX_LENGTH && $this->last < $this->string_length; ++$j, ++$this->last ) { $last_space = false; // Composed keywords shouldn't have more than one whitespace between keywords. if ( static::is_whitespace( $this->str[ $this->last ] ) ) { if ( $last_space ) { --$j; // The size of the keyword didn't increase. continue; } $last_space = true; } $token .= $this->str[ $this->last ]; $flags = static::is_keyword( $token ); if ( ( $this->last + 1 !== $this->string_length && ! static::is_separator( $this->str[ $this->last + 1 ] ) ) || ! $flags ) { continue; } $ret = new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_KEYWORD, $flags ); $i_end = $this->last; /* * We don't break so we find longest keyword. * For example, `OR` and `ORDER` have a common prefix `OR`. * If we stopped at `OR`, the parsing would be invalid. */ } $this->last = $i_end; return $ret; } /** * Parses a label. * * @return WP_SQLite_Token|null */ public function parse_label() { $token = ''; /** * Value to be returned. * * @var WP_SQLite_Token */ $ret = null; // The value of `$this->last` where `$token` ends in `$this->str`. $i_end = $this->last; for ( $j = 1; $j < static::LABEL_MAX_LENGTH && $this->last < $this->string_length; ++$j, ++$this->last ) { if ( ':' === $this->str[ $this->last ] && $j > 1 ) { // End of label. $token .= $this->str[ $this->last ]; $ret = new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_LABEL ); $i_end = $this->last; break; } if ( static::is_whitespace( $this->str[ $this->last ] ) && $j > 1 ) { /* * Whitespace between label and `:`. * The size of the keyword didn't increase. */ --$j; } elseif ( static::is_separator( $this->str[ $this->last ] ) ) { // Any other separator. break; } $token .= $this->str[ $this->last ]; } $this->last = $i_end; return $ret; } /** * Parses an operator. * * @return WP_SQLite_Token|null */ public function parse_operator() { $token = ''; /** * Value to be returned. * * @var WP_SQLite_Token */ $ret = null; // The value of `$this->last` where `$token` ends in `$this->str`. $i_end = $this->last; for ( $j = 1; $j < static::OPERATOR_MAX_LENGTH && $this->last < $this->string_length; ++$j, ++$this->last ) { $token .= $this->str[ $this->last ]; $flags = static::is_operator( $token ); if ( ! $flags ) { continue; } $ret = new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_OPERATOR, $flags ); $i_end = $this->last; } $this->last = $i_end; return $ret; } /** * Parses a whitespace. * * @return WP_SQLite_Token|null */ public function parse_whitespace() { $token = $this->str[ $this->last ]; if ( ! static::is_whitespace( $token ) ) { return null; } while ( ++$this->last < $this->string_length && static::is_whitespace( $this->str[ $this->last ] ) ) { $token .= $this->str[ $this->last ]; } --$this->last; return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_WHITESPACE ); } /** * Parses a comment. * * @return WP_SQLite_Token|null */ public function parse_comment() { $i_bak = $this->last; $token = $this->str[ $this->last ]; // Bash style comments (#comment\n). if ( static::is_comment( $token ) ) { while ( ++$this->last < $this->string_length && "\n" !== $this->str[ $this->last ] ) { $token .= $this->str[ $this->last ]; } // Include trailing \n as whitespace token. if ( $this->last < $this->string_length ) { --$this->last; } return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_COMMENT, WP_SQLite_Token::FLAG_COMMENT_BASH ); } // C style comments (/*comment*\/). if ( ++$this->last < $this->string_length ) { $token .= $this->str[ $this->last ]; if ( static::is_comment( $token ) ) { // There might be a conflict with "*" operator here, when string is "*/*". // This can occurs in the following statements: // - "SELECT */* comment */ FROM ..." // - "SELECT 2*/* comment */3 AS `six`;". $next = $this->last + 1; if ( ( $next < $this->string_length ) && '*' === $this->str[ $next ] ) { // Conflict in "*/*": first "*" was not for ending a comment. // Stop here and let other parsing method define the true behavior of that first star. $this->last = $i_bak; return null; } $flags = WP_SQLite_Token::FLAG_COMMENT_C; // This comment already ended. It may be a part of a previous MySQL specific command. if ( '*/' === $token ) { return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_COMMENT, $flags ); } // Checking if this is a MySQL-specific command. if ( $this->last + 1 < $this->string_length && '!' === $this->str[ $this->last + 1 ] ) { $flags |= WP_SQLite_Token::FLAG_COMMENT_MYSQL_CMD; $token .= $this->str[ ++$this->last ]; while ( ++$this->last < $this->string_length && $this->str[ $this->last ] >= '0' && $this->str[ $this->last ] <= '9' ) { $token .= $this->str[ $this->last ]; } --$this->last; // We split this comment and parse only its beginning here. return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_COMMENT, $flags ); } // Parsing the comment. while ( ++$this->last < $this->string_length && ( '*' !== $this->str[ $this->last - 1 ] || '/' !== $this->str[ $this->last ] ) ) { $token .= $this->str[ $this->last ]; } // Adding the ending. if ( $this->last < $this->string_length ) { $token .= $this->str[ $this->last ]; } return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_COMMENT, $flags ); } } // SQL style comments (-- comment\n). if ( ++$this->last < $this->string_length ) { $token .= $this->str[ $this->last ]; $end = false; } else { --$this->last; $end = true; } if ( static::is_comment( $token, $end ) ) { // Checking if this comment did not end already (```--\n```). if ( "\n" !== $this->str[ $this->last ] ) { while ( ++$this->last < $this->string_length && "\n" !== $this->str[ $this->last ] ) { $token .= $this->str[ $this->last ]; } } // Include trailing \n as whitespace token. if ( $this->last < $this->string_length ) { --$this->last; } return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_COMMENT, WP_SQLite_Token::FLAG_COMMENT_SQL ); } $this->last = $i_bak; return null; } /** * Parses a boolean. * * @return WP_SQLite_Token|null */ public function parse_bool() { if ( $this->last + 3 >= $this->string_length ) { // At least `min(strlen('TRUE'), strlen('FALSE'))` characters are required. return null; } $i_bak = $this->last; $token = $this->str[ $this->last ] . $this->str[ ++$this->last ] . $this->str[ ++$this->last ] . $this->str[ ++$this->last ]; // _TRUE_ or _FALS_e. if ( static::is_bool( $token ) ) { return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_BOOL ); } if ( ++$this->last < $this->string_length ) { $token .= $this->str[ $this->last ]; // fals_E_. if ( static::is_bool( $token ) ) { return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_BOOL, 1 ); } } $this->last = $i_bak; return null; } /** * Parses a number. * * @return WP_SQLite_Token|null */ public function parse_number() { /* * A rudimentary state machine is being used to parse numbers due to * the various forms of their notation. * * Below are the states of the machines and the conditions to change * the state. * * 1 --------------------[ + or - ]-------------------> 1 * 1 -------------------[ 0x or 0X ]------------------> 2 * 1 --------------------[ 0 to 9 ]-------------------> 3 * 1 -----------------------[ . ]---------------------> 4 * 1 -----------------------[ b ]---------------------> 7 * * 2 --------------------[ 0 to F ]-------------------> 2 * * 3 --------------------[ 0 to 9 ]-------------------> 3 * 3 -----------------------[ . ]---------------------> 4 * 3 --------------------[ e or E ]-------------------> 5 * * 4 --------------------[ 0 to 9 ]-------------------> 4 * 4 --------------------[ e or E ]-------------------> 5 * * 5 ---------------[ + or - or 0 to 9 ]--------------> 6 * * 7 -----------------------[ ' ]---------------------> 8 * * 8 --------------------[ 0 or 1 ]-------------------> 8 * 8 -----------------------[ ' ]---------------------> 9 * * State 1 may be reached by negative numbers. * State 2 is reached only by hex numbers. * State 4 is reached only by float numbers. * State 5 is reached only by numbers in approximate form. * State 7 is reached only by numbers in bit representation. * * Valid final states are: 2, 3, 4 and 6. Any parsing that finished in a * state other than these is invalid. * Also, negative states are invalid states. */ $i_bak = $this->last; $token = ''; $flags = 0; $state = 1; for ( ; $this->last < $this->string_length; ++$this->last ) { if ( 1 === $state ) { if ( '-' === $this->str[ $this->last ] ) { $flags |= WP_SQLite_Token::FLAG_NUMBER_NEGATIVE; } elseif ( $this->last + 1 < $this->string_length && '0' === $this->str[ $this->last ] && 'x' === $this->str[ $this->last + 1 ] ) { $token .= $this->str[ $this->last++ ]; $state = 2; } elseif ( $this->str[ $this->last ] >= '0' && $this->str[ $this->last ] <= '9' ) { $state = 3; } elseif ( '.' === $this->str[ $this->last ] ) { $state = 4; } elseif ( 'b' === $this->str[ $this->last ] ) { $state = 7; } elseif ( '+' !== $this->str[ $this->last ] ) { // `+` is a valid character in a number. break; } } elseif ( 2 === $state ) { $flags |= WP_SQLite_Token::FLAG_NUMBER_HEX; if ( ! ( ( $this->str[ $this->last ] >= '0' && $this->str[ $this->last ] <= '9' ) || ( $this->str[ $this->last ] >= 'A' && $this->str[ $this->last ] <= 'F' ) || ( $this->str[ $this->last ] >= 'a' && $this->str[ $this->last ] <= 'f' ) ) ) { break; } } elseif ( 3 === $state ) { if ( '.' === $this->str[ $this->last ] ) { $state = 4; } elseif ( 'e' === $this->str[ $this->last ] || 'E' === $this->str[ $this->last ] ) { $state = 5; } elseif ( ( $this->str[ $this->last ] >= 'a' && $this->str[ $this->last ] <= 'z' ) || ( $this->str[ $this->last ] >= 'A' && $this->str[ $this->last ] <= 'Z' ) ) { // A number can't be directly followed by a letter. $state = -$state; } elseif ( $this->str[ $this->last ] < '0' || $this->str[ $this->last ] > '9' ) { // Just digits and `.`, `e` and `E` are valid characters. break; } } elseif ( 4 === $state ) { $flags |= WP_SQLite_Token::FLAG_NUMBER_FLOAT; if ( 'e' === $this->str[ $this->last ] || 'E' === $this->str[ $this->last ] ) { $state = 5; } elseif ( ( $this->str[ $this->last ] >= 'a' && $this->str[ $this->last ] <= 'z' ) || ( $this->str[ $this->last ] >= 'A' && $this->str[ $this->last ] <= 'Z' ) ) { // A number can't be directly followed by a letter. $state = -$state; } elseif ( $this->str[ $this->last ] < '0' || $this->str[ $this->last ] > '9' ) { // Just digits, `e` and `E` are valid characters. break; } } elseif ( 5 === $state ) { $flags |= WP_SQLite_Token::FLAG_NUMBER_APPROXIMATE; if ( '+' === $this->str[ $this->last ] || '-' === $this->str[ $this->last ] || ( $this->str[ $this->last ] >= '0' && $this->str[ $this->last ] <= '9' ) ) { $state = 6; } elseif ( ( $this->str[ $this->last ] >= 'a' && $this->str[ $this->last ] <= 'z' ) || ( $this->str[ $this->last ] >= 'A' && $this->str[ $this->last ] <= 'Z' ) ) { // A number can't be directly followed by a letter. $state = -$state; } else { break; } } elseif ( 6 === $state ) { if ( $this->str[ $this->last ] < '0' || $this->str[ $this->last ] > '9' ) { // Just digits are valid characters. break; } } elseif ( 7 === $state ) { $flags |= WP_SQLite_Token::FLAG_NUMBER_BINARY; if ( '\'' !== $this->str[ $this->last ] ) { break; } $state = 8; } elseif ( 8 === $state ) { if ( '\'' === $this->str[ $this->last ] ) { $state = 9; } elseif ( '0' !== $this->str[ $this->last ] && '1' !== $this->str[ $this->last ] ) { break; } } elseif ( 9 === $state ) { break; } $token .= $this->str[ $this->last ]; } if ( 2 === $state || 3 === $state || ( '.' !== $token && 4 === $state ) || 6 === $state || 9 === $state ) { --$this->last; return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_NUMBER, $flags ); } $this->last = $i_bak; return null; } /** * Parses a string. * * @param string $quote Additional starting symbol. * * @return WP_SQLite_Token|null */ public function parse_string( $quote = '' ) { $token = $this->str[ $this->last ]; $flags = static::is_string( $token ); if ( ! $flags && $token !== $quote ) { return null; } $quote = $token; while ( ++$this->last < $this->string_length ) { if ( $this->last + 1 < $this->string_length && ( ( $this->str[ $this->last ] === $quote && $this->str[ $this->last + 1 ] === $quote ) || ( '\\' === $this->str[ $this->last ] && '`' !== $quote ) ) ) { $token .= $this->str[ $this->last ] . $this->str[ ++$this->last ]; } else { if ( $this->str[ $this->last ] === $quote ) { break; } $token .= $this->str[ $this->last ]; } } if ( $this->last >= $this->string_length || $this->str[ $this->last ] !== $quote ) { $this->error( sprintf( 'Ending quote %1$s was expected.', $quote ), '', $this->last ); } else { $token .= $this->str[ $this->last ]; } return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_STRING, $flags ); } /** * Parses a symbol. * * @return WP_SQLite_Token|null */ public function parse_symbol() { $token = $this->str[ $this->last ]; $flags = static::is_symbol( $token ); if ( ! $flags ) { return null; } if ( $flags & WP_SQLite_Token::FLAG_SYMBOL_VARIABLE ) { if ( $this->last + 1 < $this->string_length && '@' === $this->str[ ++$this->last ] ) { // This is a system variable (e.g. `@@hostname`). $token .= $this->str[ $this->last++ ]; $flags |= WP_SQLite_Token::FLAG_SYMBOL_SYSTEM; } } elseif ( $flags & WP_SQLite_Token::FLAG_SYMBOL_PARAMETER ) { if ( '?' !== $token && $this->last + 1 < $this->string_length ) { ++$this->last; } } else { $token = ''; } $str = null; if ( $this->last < $this->string_length ) { $str = $this->parse_string( '`' ); if ( null === $str ) { $str = $this->parse_unknown(); if ( null === $str && ! ( $flags & WP_SQLite_Token::FLAG_SYMBOL_PARAMETER ) ) { $this->error( 'Variable name was expected.', $this->str[ $this->last ], $this->last ); } } } if ( null !== $str ) { $token .= $str->token; } return new WP_SQLite_Token( $token, WP_SQLite_Token::TYPE_SYMBOL, $flags ); } /** * Parses unknown parts of the query. * * @return WP_SQLite_Token|null */ public function parse_unknown() { $token = $this->str[ $this->last ]; if ( static::is_separator( $token ) ) { return null; } while ( ++$this->last < $this->string_length && ! static::is_separator( $this->str[ $this->last ] ) ) { $token .= $this->str[ $this->last ]; // Test if end of token equals the current delimiter. If so, remove it from the token. if ( str_ends_with( $token, $this->delimiter ) ) { $token = substr( $token, 0, -$this->delimiter_length ); $this->last -= $this->delimiter_length - 1; break; } } --$this->last; return new WP_SQLite_Token( $token ); } /** * Parses the delimiter of the query. * * @return WP_SQLite_Token|null */ public function parse_delimiter() { $index = 0; while ( $index < $this->delimiter_length && $this->last + $index < $this->string_length ) { if ( $this->delimiter[ $index ] !== $this->str[ $this->last + $index ] ) { return null; } ++$index; } $this->last += $this->delimiter_length - 1; return new WP_SQLite_Token( $this->delimiter, WP_SQLite_Token::TYPE_DELIMITER ); } /** * Checks if the given string is a keyword. * * @param string $str String to be checked. * @param bool $is_reserved Checks if the keyword is reserved. * * @return int|null */ public static function is_keyword( $str, $is_reserved = false ) { $str = strtoupper( $str ); if ( isset( static::$keywords[ $str ] ) ) { if ( $is_reserved && ! ( static::$keywords[ $str ] & WP_SQLite_Token::FLAG_KEYWORD_RESERVED ) ) { return null; } return static::$keywords[ $str ]; } return null; } /** * Checks if the given string is an operator. * * @param string $str String to be checked. * * @return int|null The appropriate flag for the operator. */ public static function is_operator( $str ) { if ( ! isset( static::$operators[ $str ] ) ) { return null; } return static::$operators[ $str ]; } /** * Checks if the given character is a whitespace. * * @param string $str String to be checked. * * @return bool */ public static function is_whitespace( $str ) { return ( ' ' === $str ) || ( "\r" === $str ) || ( "\n" === $str ) || ( "\t" === $str ); } /** * Checks if the given string is the beginning of a whitespace. * * @param string $str String to be checked. * @param mixed $end Whether this is the end of the string. * * @return int|null The appropriate flag for the comment type. */ public static function is_comment( $str, $end = false ) { $string_length = strlen( $str ); if ( 0 === $string_length ) { return null; } // If comment is Bash style (#). if ( '#' === $str[0] ) { return WP_SQLite_Token::FLAG_COMMENT_BASH; } // If comment is opening C style (/*), warning, it could be a MySQL command (/*!). if ( ( $string_length > 1 ) && ( '/' === $str[0] ) && ( '*' === $str[1] ) ) { return ( $string_length > 2 ) && ( '!' === $str[2] ) ? WP_SQLite_Token::FLAG_COMMENT_MYSQL_CMD : WP_SQLite_Token::FLAG_COMMENT_C; } // If comment is closing C style (*/), warning, it could conflicts with wildcard and a real opening C style. // It would looks like the following valid SQL statement: "SELECT */* comment */ FROM...". if ( ( $string_length > 1 ) && ( '*' === $str[0] ) && ( '/' === $str[1] ) ) { return WP_SQLite_Token::FLAG_COMMENT_C; } // If comment is SQL style (--\s?). if ( ( $string_length > 2 ) && ( '-' === $str[0] ) && ( '-' === $str[1] ) && static::is_whitespace( $str[2] ) ) { return WP_SQLite_Token::FLAG_COMMENT_SQL; } if ( ( 2 === $string_length ) && $end && ( '-' === $str[0] ) && ( '-' === $str[1] ) ) { return WP_SQLite_Token::FLAG_COMMENT_SQL; } return null; } /** * Checks if the given string is a boolean value. * This actually checks only for `TRUE` and `FALSE` because `1` or `0` are * numbers and are parsed by specific methods. * * @param string $str String to be checked. * * @return bool */ public static function is_bool( $str ) { $str = strtoupper( $str ); return ( 'TRUE' === $str ) || ( 'FALSE' === $str ); } /** * Checks if the given character can be a part of a number. * * @param string $str String to be checked. * * @return bool */ public static function is_number( $str ) { return ( $str >= '0' ) && ( $str <= '9' ) || ( '.' === $str ) || ( '-' === $str ) || ( '+' === $str ) || ( 'e' === $str ) || ( 'E' === $str ); } /** * Checks if the given character is the beginning of a symbol. A symbol * can be either a variable or a field name. * * @param string $str String to be checked. * * @return int|null The appropriate flag for the symbol type. */ public static function is_symbol( $str ) { if ( 0 === strlen( $str ) ) { return null; } if ( '@' === $str[0] ) { return WP_SQLite_Token::FLAG_SYMBOL_VARIABLE; } if ( '`' === $str[0] ) { return WP_SQLite_Token::FLAG_SYMBOL_BACKTICK; } if ( ':' === $str[0] || '?' === $str[0] ) { return WP_SQLite_Token::FLAG_SYMBOL_PARAMETER; } return null; } /** * Checks if the given character is the beginning of a string. * * @param string $str String to be checked. * * @return int|null The appropriate flag for the string type. */ public static function is_string( $str ) { if ( strlen( $str ) === 0 ) { return null; } if ( '\'' === $str[0] ) { return WP_SQLite_Token::FLAG_STRING_SINGLE_QUOTES; } if ( '"' === $str[0] ) { return WP_SQLite_Token::FLAG_STRING_DOUBLE_QUOTES; } return null; } /** * Checks if the given character can be a separator for two lexeme. * * @param string $str String to be checked. * * @return bool */ public static function is_separator( $str ) { /* * NOTES: Only non alphanumeric ASCII characters may be separators. * `~` is the last printable ASCII character. */ return ( $str <= '~' ) && ( '_' !== $str ) && ( '$' !== $str ) && ( ( $str < '0' ) || ( $str > '9' ) ) && ( ( $str < 'a' ) || ( $str > 'z' ) ) && ( ( $str < 'A' ) || ( $str > 'Z' ) ); } /** * Constructor. * * @param stdClass[] $tokens The initial array of tokens. */ public function tokens( array $tokens = array() ) { $this->tokens = $tokens; $this->tokens_count = count( $tokens ); } /** * Gets the next token. * * @param int $type The type of the token. * @param int $flag The flag of the token. */ public function tokens_get_next_of_type_and_flag( $type, $flag ) { for ( ; $this->tokens_index < $this->tokens_count; ++$this->tokens_index ) { if ( ( $this->tokens[ $this->tokens_index ]->type === $type ) && ( $this->tokens[ $this->tokens_index ]->flags === $flag ) ) { return $this->tokens[ $this->tokens_index++ ]; } } return null; } /** * Gets the next token. * * @param int $type The type of the token. * @param string $value The value of the token. * * @return stdClass|null */ public function tokens_get_next_of_type_and_value( $type, $value ) { for ( ; $this->tokens_index < $this->tokens_count; ++$this->tokens_index ) { if ( ( $this->tokens[ $this->tokens_index ]->type === $type ) && ( $this->tokens[ $this->tokens_index ]->value === $value ) ) { return $this->tokens[ $this->tokens_index++ ]; } } return null; } /** * Gets the next token. Skips any irrelevant token (whitespaces and * comments). * * @return stdClass|null */ public function tokens_get_next() { for ( ; $this->tokens_index < $this->tokens_count; ++$this->tokens_index ) { if ( ( WP_SQLite_Token::TYPE_WHITESPACE !== $this->tokens[ $this->tokens_index ]->type ) && ( WP_SQLite_Token::TYPE_COMMENT !== $this->tokens[ $this->tokens_index ]->type ) ) { return $this->tokens[ $this->tokens_index++ ]; } } return null; } }