Files
php_assessment_2/wp-content/mu-plugins/sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-lexer.php
T

2576 lines
86 KiB
PHP
Raw Normal View History

2025-02-05 23:15:46 +01:00
<?php
/**
* This file is a port of the Lexer & TokensList classes from the PHPMyAdmin/sql-parser library.
*
* @package wp-sqlite-integration
* @see https://github.com/phpmyadmin/sql-parser
*/
/**
* Performs lexical analysis over a SQL statement and splits it in multiple tokens.
*/
class WP_SQLite_Lexer {
/**
* The maximum length of a keyword.
*/
const KEYWORD_MAX_LENGTH = 30;
/**
* The maximum length of a label.
*
* Ref: https://dev.mysql.com/doc/refman/5.7/en/statement-labels.html
*/
const LABEL_MAX_LENGTH = 16;
/**
* The maximum length of an operator.
*/
const OPERATOR_MAX_LENGTH = 4;
/**
* A list of methods that are used in lexing the SQL query.
*
* @var string[]
*/
const PARSER_METHODS = array(
// It is best to put the parsers in order of their complexity
// (ascending) and their occurrence rate (descending).
//
// Conflicts:
//
// 1. `parse_delimiter`, `parse_unknown`, `parse_keyword`, `parse_number`
// They fight over delimiter. The delimiter may be a keyword, a
// number or almost any character which makes the delimiter one of
// the first tokens that must be parsed.
//
// 1. `parse_number` and `parse_operator`
// They fight over `+` and `-`.
//
// 2. `parse_comment` and `parse_operator`
// They fight over `/` (as in ```/*comment*/``` or ```a / b```)
//
// 3. `parse_bool` and `parse_keyword`
// They fight over `TRUE` and `FALSE`.
//
// 4. `parse_keyword` and `parse_unknown`
// They fight over words. `parse_unknown` does not know about
// keywords.
'parse_delimiter',
'parse_whitespace',
'parse_number',
'parse_comment',
'parse_operator',
'parse_bool',
'parse_string',
'parse_symbol',
'parse_keyword',
'parse_label',
'parse_unknown',
);
/**
* A list of keywords that indicate that the function keyword
* is not used as a function.
*
* @var string[]
*/
const KEYWORD_NAME_INDICATORS = array(
'FROM',
'SET',
'WHERE',
);
/**
* A list of operators that indicate that the function keyword
* is not used as a function.
*
* @var string[]
*/
const OPERATOR_NAME_INDICATORS = array(
',',
'.',
);
/**
* The string to be parsed.
*
* @var string
*/
public $str = '';
/**
* The length of `$str`.
*
* By storing its length, a lot of time is saved, because parsing methods
* would call `strlen` everytime.
*
* @var int
*/
public $string_length = 0;
/**
* The index of the last parsed character.
*
* @var int
*/
public $last = 0;
/**
* The default delimiter. This is used, by default, in all new instances.
*
* @var string
*/
public static $default_delimiter = ';';
/**
* Statements delimiter.
* This may change during lexing.
*
* @var string
*/
public $delimiter;
/**
* The length of the delimiter.
*
* Because `parse_delimiter` can be called a lot, it would perform a lot of
* calls to `strlen`, which might affect performance when the delimiter is
* big.
*
* @var int
*/
public $delimiter_length;
/**
* List of operators and their flags.
*
* @var array<string, int>
*/
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<string,int>
*/
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<string, int|array<int, int|string>>
*/
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<string, bool|int|array<int, int|string|array<string, bool>>>
*/
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;
}
}