diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a6ad250..a46cd71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,6 +17,50 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: + # PHP CodeSniffer inspection + phpcs: + name: "Validate code style" + + permissions: + contents: read + + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: + - '8.1' + - '8.2' + - '8.3' + - '8.4' + - '8.5' + + steps: + - uses: actions/checkout@v6 + + - name: "Setup PHP" + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.EXTENSIONS }} + ini-values: apc.enable_cli=on, session.save_path=/tmp + tools: pecl + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PHALCON_PATH: ext + + - name: "Install development dependencies with Composer" + uses: "ramsey/composer-install@v3" + with: + composer-options: "--prefer-dist" + + - name: "PHPCS" + run: composer cs + +# - name: "PHPStan" +# run: composer analyze + tests: name: PHP ${{ matrix.php }} runs-on: ubuntu-latest diff --git a/composer.json b/composer.json index 839847d..8c19b0a 100644 --- a/composer.json +++ b/composer.json @@ -1,15 +1,26 @@ { "name": "phalcon/phql", "description": "Phalcon Query Language (PHQL)", + "type": "library", "keywords": [ "php", "framework", + "phalcon", "query", - "sql" + "sql", + "phql" ], + "homepage": "https://phalcon.io", "license": "MIT", + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalcon.io", + "homepage": "https://phalcon.io" + } + ], "require": { - "php": ">=8.1", + "php": ">=8.1 <9.0", "ext-mbstring": "*" }, "suggest": { @@ -17,7 +28,10 @@ }, "require-dev": { "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^10.5", + "pds/skeleton": "^1.0", + "pds/composer-script-names": "^1.0", + "squizlabs/php_codesniffer": "^4.0" }, "autoload": { "psr-4": { @@ -29,12 +43,20 @@ }, "autoload-dev": { "psr-4": { - "Phalcon\\Phql\\Tests\\": "tests/", - "Phalcon\\Phql\\Tests\\Unit\\": "tests/unit/" + "Phalcon\\Phql\\Tests\\": "tests/" } }, + "minimum-stability": "stable", + "prefer-stable": true, "scripts": { + "cs": "vendor/bin/phpcs --standard=phpcs.xml", + "cs-fix": "vendor/bin/phpcbf --standard=phpcs.xml", + "analyze": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 1024M", "test": "vendor/bin/phpunit -c phpunit.xml --fail-on-all-issues", "test-coverage": "vendor/bin/phpunit -c phpunit.xml --coverage-html tests/_output/coverage/" + }, + "support": { + "issues": "https://github.com/phalcon/phql/issues", + "source": "https://github.com/phalcon/phql" } } diff --git a/composer.lock b/composer.lock index 6a56b5f..8545330 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3449d9f432f7ba89aa4794432fe52259", + "content-hash": "e76f5860ff8d0931244d183985f3a478", "packages": [], "packages-dev": [ { @@ -125,6 +125,68 @@ }, "time": "2025-12-06T11:56:16+00:00" }, + { + "name": "pds/composer-script-names", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-pds/composer-script-names.git", + "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-pds/composer-script-names/zipball/e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "shasum": "" + }, + "type": "standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC-BY-SA-4.0" + ], + "description": "Standard for Composer script names.", + "homepage": "https://github.com/php-pds/composer-script-names", + "support": { + "issues": "https://github.com/php-pds/composer-script-names/issues", + "source": "https://github.com/php-pds/composer-script-names/tree/1.0.0" + }, + "time": "2023-04-06T13:42:16+00:00" + }, + { + "name": "pds/skeleton", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-pds/skeleton.git", + "reference": "95e476e5d629eadacbd721c5a9553e537514a231" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-pds/skeleton/zipball/95e476e5d629eadacbd721c5a9553e537514a231", + "reference": "95e476e5d629eadacbd721c5a9553e537514a231", + "shasum": "" + }, + "bin": [ + "bin/pds-skeleton" + ], + "type": "standard", + "autoload": { + "psr-4": { + "Pds\\Skeleton\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC-BY-SA-4.0" + ], + "description": "Standard for PHP package skeletons.", + "homepage": "https://github.com/php-pds/skeleton", + "support": { + "issues": "https://github.com/php-pds/skeleton/issues", + "source": "https://github.com/php-pds/skeleton/tree/1.x" + }, + "time": "2017-01-25T23:30:41+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -1679,6 +1741,85 @@ ], "time": "2023-02-07T11:34:05+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0525c73950de35ded110cffafb9892946d7771b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-10T16:43:36+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", @@ -1733,10 +1874,10 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": {}, - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=8.1", + "php": ">=8.1 <9.0", "ext-mbstring": "*" }, "platform-dev": {}, diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..8828afe --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,12 @@ + + + Phalcon Coding Standards + + + + + + + src + tests/unit + diff --git a/resources/files/parser.php b/resources/files/parser.php index bac010d..1da7db0 100644 --- a/resources/files/parser.php +++ b/resources/files/parser.php @@ -31,9 +31,9 @@ class phql_Parser { - protected array $output = []; + protected mixed $output = []; - public function getOutput(): array + public function getOutput(): mixed { return $this->output; } @@ -3913,7 +3913,7 @@ private function yy_reduce(int $yyruleno): void */ case 0: $yygotominor = $this->yystack[$this->yyidx + 0]->minor; - //ZVAL_ZVAL($status->ret, $this->yystack[$this->yyidx + 0]->minor, 1, 1); + $this->status->setRet($this->yystack[$this->yyidx + 0]->minor); break; case 1: case 2: @@ -3971,9 +3971,13 @@ private function yy_reduce(int $yyruleno): void $this->yy_destructor(30, $this->yystack[$this->yyidx + 0]->minor); break; case 9: - case 20: - case 27: - case 38: + // distinct_all ::= (no DISTINCT or ALL keyword) - no distinct + $yygotominor = null; + break; + case 135: + // distinct_or_null ::= (no DISTINCT keyword in function call) + $yygotominor = null; + break; case 69: case 71: case 78: @@ -3981,8 +3985,14 @@ private function yy_reduce(int $yyruleno): void case 85: case 89: case 91: - case 135: case 137: + // Empty optional clause (where, limit, order, group, having, for_update, etc.) + // Matches cphalcon ZVAL_UNDEF — must be null so phql_ret_*_statement skips adding the key + $yygotominor = null; + break; + case 20: + case 27: + case 38: $yygotominor = []; break; case 10: @@ -4669,7 +4679,8 @@ private function yy_reduce(int $yyruleno): void $this->yy_destructor(46, $this->yystack[$this->yyidx + 0]->minor); break; case 134: - $yygotominor = 0; + // distinct_or_null ::= DISTINCT (DISTINCT keyword present in function call) + $yygotominor = true; $this->yy_destructor(29, $this->yystack[$this->yyidx + 0]->minor); break; case 142: @@ -5101,7 +5112,7 @@ function phql_ret_qualified_name( function phql_ret_update_statement(array &$ret, $update, $where = null, $limit = null): void { $ret = []; - $ret['type'] = defined('PHQL_T_UPDATE') ? PHQL_T_UPDATE : 0; + $ret['type'] = Opcode::PHQL_T_UPDATE; $ret['update'] = $update; if ($where !== null) { @@ -5130,7 +5141,7 @@ function phql_ret_update_item(array &$ret, $column, $expr): void function phql_ret_delete_statement(array &$ret, $delete, $where = null, $limit = null): void { $ret = []; - $ret['type'] = defined('PHQL_T_DELETE') ? PHQL_T_DELETE : 0; + $ret['type'] = Opcode::PHQL_T_DELETE; $ret['delete'] = $delete; if ($where !== null) { @@ -5194,7 +5205,7 @@ function phql_ret_placeholder_zval(array &$ret, int $type, ?Token $value = null) function phql_ret_raw_qualified_name(array &$ret, string $tokenA, ?string $tokenB = null): void { $ret = []; - $ret['type'] = defined('PHQL_T_RAW_QUALIFIED') ? PHQL_T_RAW_QUALIFIED : 0; + $ret['type'] = Opcode::PHQL_T_RAW_QUALIFIED; if ($tokenB !== null) { /* Two-part qualified name: domain + name */ @@ -5209,7 +5220,7 @@ function phql_ret_raw_qualified_name(array &$ret, string $tokenA, ?string $token function phql_ret_func_call(array &$ret, $name, $arguments = null, $distinct = null): void { $ret = []; - $ret['type'] = defined('PHQL_T_FCALL') ? PHQL_T_FCALL : 0; + $ret['type'] = Opcode::PHQL_T_FCALL; $ret['name'] = $name instanceof Token ? $name->getValue() : $name; if ($arguments !== null) { diff --git a/src/Parser.php b/src/Parser.php index 233e752..1950a53 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -4,12 +4,12 @@ namespace Phalcon\Phql; +use Phalcon\Phql\Parser\Status; use Phalcon\Phql\Scanner\Opcode; use Phalcon\Phql\Scanner\Scanner; use Phalcon\Phql\Scanner\State; use Phalcon\Phql\Scanner\Token; use RuntimeException; -use stdClass; /** * Orchestrates the PHQL lexer and parser, equivalent to @@ -36,32 +36,22 @@ public function parse(string $phql): array } $state = new State($phql); - $token = new Token(); - $scanner = new Scanner($state, $token); - - $parserObject = new \phql_Parser(); + $scanner = new Scanner($state); + $token = $scanner->getToken(); // Status object mirrors phql_parser_status in C - $status = new stdClass(); - $status->status = \phql_Parser::PHQL_PARSING_OK; - $status->scanner_state = $state; - $status->ret = null; - $status->syntax_error = null; - $status->token = $token; - $status->enable_literals = $this->enableLiterals; - $status->phql = $phql; - $status->phql_length = mb_strlen($phql); - - $parserObject->status = $status; + $status = new Status($state); + $status->setToken($token); + $status->setEnableLiterals($this->enableLiterals); + + $parserObject = new \phql_Parser($status); $errorMsg = null; $failed = false; while (($scannerStatus = $scanner->scanForToken()) >= 0) { - // Equivalent to: state->start_length = (phql + phql_length - state->start) - $state->startLength = $status->phql_length - $state->getCursor(); - - $state->activeToken = $token->opcode; + $state->setStartLength(mb_strlen($phql) - $state->getCursor()); + $state->setActiveToken($token->opcode); switch ($token->opcode) { case Opcode::PHQL_T_IGNORE: @@ -176,7 +166,7 @@ public function parse(string $phql): array $parserObject->phql_(\phql_Parser::PHQL_INTEGER, $this->makeParserToken($token)); } else { $errorMsg = 'Literals are disabled in PHQL statements'; - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); } break; case Opcode::PHQL_T_DOUBLE: @@ -184,7 +174,7 @@ public function parse(string $phql): array $parserObject->phql_(\phql_Parser::PHQL_DOUBLE, $this->makeParserToken($token)); } else { $errorMsg = 'Literals are disabled in PHQL statements'; - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); } break; case Opcode::PHQL_T_STRING: @@ -192,7 +182,7 @@ public function parse(string $phql): array $parserObject->phql_(\phql_Parser::PHQL_STRING, $this->makeParserToken($token)); } else { $errorMsg = 'Literals are disabled in PHQL statements'; - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); } break; case Opcode::PHQL_T_TRUE: @@ -200,7 +190,7 @@ public function parse(string $phql): array $parserObject->phql_(\phql_Parser::PHQL_TRUE); } else { $errorMsg = 'Literals are disabled in PHQL statements'; - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); } break; case Opcode::PHQL_T_FALSE: @@ -208,7 +198,7 @@ public function parse(string $phql): array $parserObject->phql_(\phql_Parser::PHQL_FALSE); } else { $errorMsg = 'Literals are disabled in PHQL statements'; - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); } break; case Opcode::PHQL_T_HINTEGER: @@ -216,7 +206,7 @@ public function parse(string $phql): array $parserObject->phql_(\phql_Parser::PHQL_HINTEGER, $this->makeParserToken($token)); } else { $errorMsg = 'Literals are disabled in PHQL statements'; - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); } break; @@ -343,12 +333,12 @@ public function parse(string $phql): array break; default: - $status->status = \phql_Parser::PHQL_PARSING_FAILED; + $status->setStatus(Status::PHQL_PARSING_FAILED); $errorMsg = sprintf('Scanner: Unknown opcode %d', $token->opcode); break; } - if ($status->status !== \phql_Parser::PHQL_PARSING_OK) { + if ($status->getStatus() !== Status::PHQL_PARSING_OK) { $failed = true; break; } @@ -369,12 +359,12 @@ public function parse(string $phql): array } } - $state->activeToken = 0; + $state->setActiveToken(0); - if ($status->status !== \phql_Parser::PHQL_PARSING_OK) { + if ($status->getStatus() !== Status::PHQL_PARSING_OK) { $failed = true; - if ($status->syntax_error !== null && $errorMsg === null) { - $errorMsg = $status->syntax_error; + if ($status->getSyntaxError() !== null && $errorMsg === null) { + $errorMsg = $status->getSyntaxError(); } } @@ -382,24 +372,25 @@ public function parse(string $phql): array throw new RuntimeException($errorMsg ?? 'Unknown PHQL parsing error'); } - if (!is_array($status->ret)) { + $ret = $status->getRet(); + if (!is_array($ret)) { throw new RuntimeException('PHQL parsing produced no result'); } - return $status->ret; + return $ret; } /** - * Wrap a scanner Token into the lightweight token object the parser expects. + * Snapshot the current scanner token into a new Token instance so the + * parser stack holds stable values (the scanner reuses its token object). * Mirrors phql_parse_with_token() in base.c. */ - private function makeParserToken(Token $token): stdClass + private function makeParserToken(Token $token): Token { - $pt = new stdClass(); - $pt->opcode = $token->opcode; - $pt->token = $token->value; - $pt->token_len = $token->len; - $pt->free_flag = 1; + $pt = new Token(); + $pt->setOpcode($token->opcode); + $pt->setValue($token->value); + $pt->setLength($token->len); return $pt; } @@ -407,20 +398,21 @@ private function makeParserToken(Token $token): stdClass /** * Mirrors phql_scanner_error_msg() in base.c. */ - private function buildScannerErrorMsg(stdClass $status, string $phql): string + private function buildScannerErrorMsg(Status $status, string $phql): string { - $state = $status->scanner_state; + $state = $status->getState(); + $phqlLength = mb_strlen($phql); - if ($state->getStart() !== null && $state->startLength > 0) { + if ($state->getStart() !== null && $state->getStartLength() > 0) { $startStr = substr($phql, $state->getCursor()); - if ($state->startLength > 16) { + if ($state->getStartLength() > 16) { $errorPart = substr($startStr, 0, 16); return sprintf( "Scanning error before '%s...' when parsing: %s (%d)", $errorPart, $phql, - $status->phql_length + $phqlLength ); } @@ -428,7 +420,7 @@ private function buildScannerErrorMsg(stdClass $status, string $phql): string "Scanning error before '%s' when parsing: %s (%d)", $startStr, $phql, - $status->phql_length + $phqlLength ); } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 1e3add2..8644a0c 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -185,7 +185,7 @@ public function parse(string $phql): array } else { $parserStatus->setSyntaxError("Literals are disabled in PHQL statements"); $parserStatus->setStatus(Status::PHQL_PARSING_FAILED); - } + } break; case Opcode::PHQL_T_TRUE: if ($parserStatus->getEnableLiterals()) { @@ -193,7 +193,7 @@ public function parse(string $phql): array } else { $parserStatus->setSyntaxError("Literals are disabled in PHQL statements"); $parserStatus->setStatus(Status::PHQL_PARSING_FAILED); - } + } break; case Opcode::PHQL_T_FALSE: if ($parserStatus->getEnableLiterals()) { @@ -337,7 +337,7 @@ public function parse(string $phql): array default: $parserStatus->setStatus(Status::PHQL_PARSING_FAILED); $parserStatus->setSyntaxError("Scanner: Unknown opcode %d" . $opcode); - break; + break; } if ($parserStatus->getStatus() === Status::PHQL_PARSING_FAILED) { @@ -347,7 +347,10 @@ public function parse(string $phql): array $state->setEnd($state->getStart()); } - if ($scannerStatus === Scanner::PHQL_SCANNER_RETCODE_ERR || $scannerStatus === Scanner::PHQL_SCANNER_RETCODE_IMPOSSIBLE) { + if ( + $scannerStatus === Scanner::PHQL_SCANNER_RETCODE_ERR + || $scannerStatus === Scanner::PHQL_SCANNER_RETCODE_IMPOSSIBLE + ) { throw new Exception($parserStatus->getSyntaxError()); } elseif ($scannerStatus === Scanner::PHQL_SCANNER_RETCODE_EOF) { $parser->phql_(0); diff --git a/src/Parser/Status.php b/src/Parser/Status.php index a2f1c09..062d5ff 100644 --- a/src/Parser/Status.php +++ b/src/Parser/Status.php @@ -12,6 +12,8 @@ class Status public const PHQL_PARSING_FAILED = 0; public const PHQL_PARSING_OK = 1; + protected mixed $ret = null; + protected ?string $syntaxError = null; protected ?Token $token = null; @@ -29,6 +31,18 @@ public function getState(): State return $this->scannerState; } + public function getRet(): mixed + { + return $this->ret; + } + + public function setRet(mixed $ret): self + { + $this->ret = $ret; + + return $this; + } + public function setEnableLiterals(bool $enable): self { $this->enableLiterals = $enable; @@ -77,4 +91,4 @@ public function setToken(Token $token): self return $this; } -} \ No newline at end of file +} diff --git a/src/Scanner/Opcode.php b/src/Scanner/Opcode.php index 648b666..fcdb592 100644 --- a/src/Scanner/Opcode.php +++ b/src/Scanner/Opcode.php @@ -41,7 +41,7 @@ class Opcode public const PHQL_T_ELSE = 411; public const PHQL_T_ENCLOSED = 356; public const PHQL_T_END = 412; - public const PHQL_T_EQUALS = '='; + public const PHQL_T_EQUALS = 61; // ord('=') public const PHQL_T_EXISTS = 408; public const PHQL_T_EXPR = 354; public const PHQL_T_FALSE = 335; @@ -71,7 +71,7 @@ class Opcode public const PHQL_T_JOIN = 318; public const PHQL_T_LEFT = 319; public const PHQL_T_LEFTJOIN = 361; - public const PHQL_T_LESS = '<'; + public const PHQL_T_LESS = 60; // ord('<') public const PHQL_T_LESSEQUAL = 271; public const PHQL_T_LIKE = 268; public const PHQL_T_LIMIT = 312; diff --git a/src/Scanner/Scanner.php b/src/Scanner/Scanner.php index 6d30d85..18f758c 100644 --- a/src/Scanner/Scanner.php +++ b/src/Scanner/Scanner.php @@ -55,7 +55,7 @@ public function scanForToken(): int $yych = $yyinput[$yycursor]; $yycursor += 1; switch ($yych) { - case 0x00: + case "\x00": $yystate = 1; break 2; case "\t": @@ -251,15 +251,13 @@ public function scanForToken(): int break 2; } case 1: - $status = self::PHQL_SCANNER_RETCODE_EOF; break; case 2: $yystate = 3; - break 2; + break; case 3: - $status = self::PHQL_SCANNER_RETCODE_ERR; break; @@ -278,7 +276,6 @@ public function scanForToken(): int break 2; } case 5: - $token->opcode = Opcode::PHQL_T_IGNORE; $this->state->setCursor($yycursor); return 0; @@ -299,7 +296,6 @@ public function scanForToken(): int break 2; } case 7: - $token->opcode = Opcode::PHQL_T_NOT; $this->state->setCursor($yycursor); return 0; @@ -308,14 +304,13 @@ public function scanForToken(): int $yyaccept = 0; $yymarker = $yycursor; $yych = $yyinput[$yycursor]; - if ($yych <= 0x00) { + if ($yych === "\x00") { $yystate = 3; - break 2; + break; } $yystate = 68; - break 2; + break; case 9: - $token->opcode = Opcode::PHQL_T_MOD; $this->state->setCursor($yycursor); return 0; @@ -332,7 +327,6 @@ public function scanForToken(): int break 2; } case 11: - $token->opcode = Opcode::PHQL_T_BITWISE_AND; $this->state->setCursor($yycursor); return 0; @@ -341,44 +335,38 @@ public function scanForToken(): int $yyaccept = 0; $yymarker = $yycursor; $yych = $yyinput[$yycursor]; - if ($yych <= 0x00) { + if ($yych === "\x00") { $yystate = 3; - break 2; + break; } $yystate = 74; - break 2; + break; case 13: - $token->opcode = Opcode::PHQL_T_PARENTHESES_OPEN; $this->state->setCursor($yycursor); return 0; case 14: - $token->opcode = Opcode::PHQL_T_PARENTHESES_CLOSE; $this->state->setCursor($yycursor); return 0; case 15: - $token->opcode = Opcode::PHQL_T_MUL; $this->state->setCursor($yycursor); return 0; case 16: - $token->opcode = Opcode::PHQL_T_ADD; $this->state->setCursor($yycursor); return 0; case 17: - $token->opcode = Opcode::PHQL_T_COMMA; $this->state->setCursor($yycursor); return 0; case 18: - $token->opcode = Opcode::PHQL_T_SUB; $this->state->setCursor($yycursor); return 0; @@ -404,13 +392,11 @@ public function scanForToken(): int break 2; } case 20: - $token->opcode = Opcode::PHQL_T_DOT; $this->state->setCursor($yycursor); return 0; case 21: - $token->opcode = Opcode::PHQL_T_DIV; $this->state->setCursor($yycursor); return 0; @@ -456,7 +442,6 @@ public function scanForToken(): int break 2; } case 23: - $token->opcode = Opcode::PHQL_T_INTEGER; $token->value = substr($yyinput, $q, $yycursor - $q); $token->len = $yycursor - $q; @@ -541,7 +526,6 @@ public function scanForToken(): int break 2; } case 25: - $token->opcode = Opcode::PHQL_T_COLON; $this->state->setCursor($yycursor); return 0; @@ -562,13 +546,11 @@ public function scanForToken(): int break 2; } case 27: - $token->opcode = Opcode::PHQL_T_LESS; $this->state->setCursor($yycursor); return 0; case 28: - $token->opcode = Opcode::PHQL_T_EQUALS; $this->state->setCursor($yycursor); return 0; @@ -585,7 +567,6 @@ public function scanForToken(): int break 2; } case 30: - $token->opcode = Opcode::PHQL_T_GREATER; $this->state->setCursor($yycursor); return 0; @@ -653,7 +634,6 @@ public function scanForToken(): int break 2; } case 34: - $token->value = substr($yyinput, $q, $yycursor - $q); $token->len = $yycursor - $q; if ($token->len > 2 && str_starts_with($token->value, "0x")) { @@ -800,7 +780,6 @@ public function scanForToken(): int break 2; } case 41: - $token->opcode = Opcode::PHQL_T_IDENTIFIER; if (($yycursor - $q) > 1) { if ($yyinput[$q] === '\\') { @@ -1094,15 +1073,15 @@ public function scanForToken(): int $yymarker = $yycursor; $yych = $yyinput[$yycursor]; switch ($yych) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: + case "\x00": + case "\x01": + case "\x02": + case "\x03": + case "\x04": + case "\x05": + case "\x06": + case "\x07": + case "\x08": case "\t": case "\n": case "\v": @@ -1198,7 +1177,6 @@ public function scanForToken(): int break 2; } case 58: - $token->opcode = Opcode::PHQL_T_BITWISE_XOR; $this->state->setCursor($yycursor); return 0; @@ -1372,25 +1350,21 @@ public function scanForToken(): int break 2; } case 63: - $token->opcode = Opcode::PHQL_T_BITWISE_OR; $this->state->setCursor($yycursor); return 0; case 64: - $token->opcode = Opcode::PHQL_T_BITWISE_NOT; $this->state->setCursor($yycursor); return 0; case 65: - $token->opcode = Opcode::PHQL_T_TS_NEGATE; $this->state->setCursor($yycursor); return 0; case 66: - $token->opcode = Opcode::PHQL_T_NOTEQUALS; $this->state->setCursor($yycursor); return 0; @@ -1400,7 +1374,7 @@ public function scanForToken(): int // fall through case 68: switch ($yych) { - case 0x00: + case "\x00": $yystate = 69; break 2; case '"': @@ -1433,10 +1407,11 @@ public function scanForToken(): int break 2; } case 70: - $token->opcode = Opcode::PHQL_T_STRING; - $token->value = substr($yyinput, $q, $yycursor - $q - 1); - $token->len = $yycursor - $q - 1; + // $yymarker points to position after the opening quote (set in state 8/12) + // $yycursor is past the closing quote; subtract 1 to exclude it + $token->value = substr($yyinput, $yymarker, $yycursor - $yymarker - 1); + $token->len = $yycursor - $yymarker - 1; $q = $yycursor; $this->state->setCursor($yycursor); return 0; @@ -1453,7 +1428,6 @@ public function scanForToken(): int break 2; } case 72: - $token->opcode = Opcode::PHQL_T_TS_AND; $this->state->setCursor($yycursor); return 0; @@ -1463,7 +1437,7 @@ public function scanForToken(): int // fall through case 74: switch ($yych) { - case 0x00: + case "\x00": $yystate = 69; break 2; case '\'': @@ -1511,7 +1485,6 @@ public function scanForToken(): int break 2; } case 77: - $token->opcode = Opcode::PHQL_T_DOUBLE; $token->value = substr($yyinput, $q, $yycursor - $q); $token->len = $yycursor - $q; @@ -1792,7 +1765,6 @@ public function scanForToken(): int break 2; } case 91: - $token->opcode = Opcode::PHQL_T_AS; $this->state->setCursor($yycursor); return 0; @@ -1884,7 +1856,6 @@ public function scanForToken(): int break 2; } case 94: - $token->opcode = Opcode::PHQL_T_BY; $this->state->setCursor($yycursor); return 0; @@ -2170,7 +2141,6 @@ public function scanForToken(): int break 2; } case 112: - $token->opcode = Opcode::PHQL_T_IN; $this->state->setCursor($yycursor); return 0; @@ -2251,7 +2221,6 @@ public function scanForToken(): int break 2; } case 114: - $token->opcode = Opcode::PHQL_T_IS; $this->state->setCursor($yycursor); return 0; @@ -2409,7 +2378,6 @@ public function scanForToken(): int break 2; } case 122: - $token->opcode = Opcode::PHQL_T_ON; $this->state->setCursor($yycursor); return 0; @@ -2493,7 +2461,6 @@ public function scanForToken(): int break 2; } case 124: - $token->opcode = Opcode::PHQL_T_OR; $this->state->setCursor($yycursor); return 0; @@ -2628,15 +2595,15 @@ public function scanForToken(): int // fall through case 136: switch ($yych) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: + case "\x00": + case "\x01": + case "\x02": + case "\x03": + case "\x04": + case "\x05": + case "\x06": + case "\x07": + case "\x08": case "\t": case "\n": case "\v": @@ -2680,15 +2647,15 @@ public function scanForToken(): int case 137: $yych = $yyinput[$yycursor]; switch ($yych) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: + case "\x00": + case "\x01": + case "\x02": + case "\x03": + case "\x04": + case "\x05": + case "\x06": + case "\x07": + case "\x08": case "\t": case "\n": case "\v": @@ -2729,9 +2696,15 @@ public function scanForToken(): int break 2; } case 138: - // fall through + // Bracket-enclosed identifier: [name] or [First Name] + // Strip the opening [ and closing ] from the value + $token->opcode = Opcode::PHQL_T_IDENTIFIER; + $token->value = substr($yyinput, $q + 1, $yycursor - $q - 2); + $token->len = $yycursor - $q - 2; + $q = $yycursor; + $this->state->setCursor($yycursor); + return 0; case 139: - $token->opcode = Opcode::PHQL_T_IDENTIFIER; $token->value = substr($yyinput, $q, $yycursor - $q); $token->len = $yycursor - $q; @@ -2819,16 +2792,15 @@ public function scanForToken(): int break 2; } case 141: - $token->opcode = Opcode::PHQL_T_TS_OR; $this->state->setCursor($yycursor); return 0; case 142: - $token->opcode = Opcode::PHQL_T_SPLACEHOLDER; - $token->value = substr($yyinput, $q, $yycursor - $q - 1); - $token->len = $yycursor - $q - 1; + // Strip leading ':' — Query.php prepends ':' when building the placeholder + $token->value = substr($yyinput, $q + 1, $yycursor - $q - 2); + $token->len = $yycursor - $q - 2; $q = $yycursor; $this->state->setCursor($yycursor); return 0; @@ -2921,7 +2893,6 @@ public function scanForToken(): int break 2; } case 145: - $token->opcode = Opcode::PHQL_T_ALL; $this->state->setCursor($yycursor); return 0; @@ -3002,7 +2973,6 @@ public function scanForToken(): int break 2; } case 147: - $token->opcode = Opcode::PHQL_T_AND; $this->state->setCursor($yycursor); return 0; @@ -3083,7 +3053,6 @@ public function scanForToken(): int break 2; } case 149: - $token->opcode = Opcode::PHQL_T_ASC; $this->state->setCursor($yycursor); return 0; @@ -3265,7 +3234,6 @@ public function scanForToken(): int break 2; } case 159: - $token->opcode = Opcode::PHQL_T_END; $this->state->setCursor($yycursor); return 0; @@ -3370,7 +3338,6 @@ public function scanForToken(): int break 2; } case 163: - $token->opcode = Opcode::PHQL_T_FOR; $this->state->setCursor($yycursor); return 0; @@ -3601,7 +3568,6 @@ public function scanForToken(): int break 2; } case 177: - $token->opcode = Opcode::PHQL_T_NOT; $this->state->setCursor($yycursor); return 0; @@ -3754,7 +3720,6 @@ public function scanForToken(): int break 2; } case 185: - $token->opcode = Opcode::PHQL_T_SET; $this->state->setCursor($yycursor); return 0; @@ -3853,15 +3818,15 @@ public function scanForToken(): int $yymarker = $yycursor; $yych = $yyinput[$yycursor]; switch ($yych) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: + case "\x00": + case "\x01": + case "\x02": + case "\x03": + case "\x04": + case "\x05": + case "\x06": + case "\x07": + case "\x08": case "\t": case "\n": case "\v": @@ -3903,10 +3868,10 @@ public function scanForToken(): int break 2; } case 194: - $token->opcode = Opcode::PHQL_T_BPLACEHOLDER; - $token->value = substr($yyinput, $q, $yycursor - $q - 1); - $token->len = $yycursor - $q - 1; + // Strip leading ':' — Query.php handles the ':' prefix separately + $token->value = substr($yyinput, $q + 1, $yycursor - $q - 2); + $token->len = $yycursor - $q - 2; $q = $yycursor; $this->state->setCursor($yycursor); return 0; @@ -4011,7 +3976,6 @@ public function scanForToken(): int break 2; } case 198: - $token->opcode = Opcode::PHQL_T_CASE; $this->state->setCursor($yycursor); return 0; @@ -4092,7 +4056,6 @@ public function scanForToken(): int break 2; } case 200: - $token->opcode = Opcode::PHQL_T_CAST; $this->state->setCursor($yycursor); return 0; @@ -4209,7 +4172,6 @@ public function scanForToken(): int break 2; } case 205: - $token->opcode = Opcode::PHQL_T_DESC; $this->state->setCursor($yycursor); return 0; @@ -4302,7 +4264,6 @@ public function scanForToken(): int break 2; } case 208: - $token->opcode = Opcode::PHQL_T_ELSE; $this->state->setCursor($yycursor); return 0; @@ -4407,7 +4368,6 @@ public function scanForToken(): int break 2; } case 212: - $token->opcode = Opcode::PHQL_T_FROM; $this->state->setCursor($yycursor); return 0; @@ -4488,7 +4448,6 @@ public function scanForToken(): int break 2; } case 214: - $token->opcode = Opcode::PHQL_T_FULL; $this->state->setCursor($yycursor); return 0; @@ -4629,7 +4588,6 @@ public function scanForToken(): int break 2; } case 221: - $token->opcode = Opcode::PHQL_T_INTO; $this->state->setCursor($yycursor); return 0; @@ -4710,7 +4668,6 @@ public function scanForToken(): int break 2; } case 223: - $token->opcode = Opcode::PHQL_T_JOIN; $this->state->setCursor($yycursor); return 0; @@ -4791,7 +4748,6 @@ public function scanForToken(): int break 2; } case 225: - $token->opcode = Opcode::PHQL_T_LEFT; $this->state->setCursor($yycursor); return 0; @@ -4872,7 +4828,6 @@ public function scanForToken(): int break 2; } case 227: - $token->opcode = Opcode::PHQL_T_LIKE; $this->state->setCursor($yycursor); return 0; @@ -4977,7 +4932,6 @@ public function scanForToken(): int break 2; } case 231: - $token->opcode = Opcode::PHQL_T_NULL; $this->state->setCursor($yycursor); return 0; @@ -5118,7 +5072,6 @@ public function scanForToken(): int break 2; } case 238: - $token->opcode = Opcode::PHQL_T_THEN; $this->state->setCursor($yycursor); return 0; @@ -5199,7 +5152,6 @@ public function scanForToken(): int break 2; } case 240: - $token->opcode = Opcode::PHQL_T_TRUE; $this->state->setCursor($yycursor); return 0; @@ -5316,7 +5268,6 @@ public function scanForToken(): int break 2; } case 245: - $token->opcode = Opcode::PHQL_T_WHEN; $this->state->setCursor($yycursor); return 0; @@ -5409,7 +5360,6 @@ public function scanForToken(): int break 2; } case 248: - $token->opcode = Opcode::PHQL_T_WITH; $this->state->setCursor($yycursor); return 0; @@ -5526,7 +5476,6 @@ public function scanForToken(): int break 2; } case 253: - $token->opcode = Opcode::PHQL_T_CROSS; $this->state->setCursor($yycursor); return 0; @@ -5643,7 +5592,6 @@ public function scanForToken(): int break 2; } case 258: - $token->opcode = Opcode::PHQL_T_FALSE; $this->state->setCursor($yycursor); return 0; @@ -5724,7 +5672,6 @@ public function scanForToken(): int break 2; } case 260: - $token->opcode = Opcode::PHQL_T_GROUP; $this->state->setCursor($yycursor); return 0; @@ -5817,7 +5764,6 @@ public function scanForToken(): int break 2; } case 263: - $token->opcode = Opcode::PHQL_T_ILIKE; $this->state->setCursor($yycursor); return 0; @@ -5898,7 +5844,6 @@ public function scanForToken(): int break 2; } case 265: - $token->opcode = Opcode::PHQL_T_INNER; $this->state->setCursor($yycursor); return 0; @@ -5991,7 +5936,6 @@ public function scanForToken(): int break 2; } case 268: - $token->opcode = Opcode::PHQL_T_LIMIT; $this->state->setCursor($yycursor); return 0; @@ -6096,7 +6040,6 @@ public function scanForToken(): int break 2; } case 272: - $token->opcode = Opcode::PHQL_T_ORDER; $this->state->setCursor($yycursor); return 0; @@ -6177,7 +6120,6 @@ public function scanForToken(): int break 2; } case 274: - $token->opcode = Opcode::PHQL_T_OUTER; $this->state->setCursor($yycursor); return 0; @@ -6258,7 +6200,6 @@ public function scanForToken(): int break 2; } case 276: - $token->opcode = Opcode::PHQL_T_RIGHT; $this->state->setCursor($yycursor); return 0; @@ -6363,7 +6304,6 @@ public function scanForToken(): int break 2; } case 280: - $token->opcode = Opcode::PHQL_T_USING; $this->state->setCursor($yycursor); return 0; @@ -6456,7 +6396,6 @@ public function scanForToken(): int break 2; } case 283: - $token->opcode = Opcode::PHQL_T_WHERE; $this->state->setCursor($yycursor); return 0; @@ -6573,7 +6512,6 @@ public function scanForToken(): int break 2; } case 288: - $token->opcode = Opcode::PHQL_T_DELETE; $this->state->setCursor($yycursor); return 0; @@ -6666,7 +6604,6 @@ public function scanForToken(): int break 2; } case 291: - $token->opcode = Opcode::PHQL_T_EXISTS; $this->state->setCursor($yycursor); return 0; @@ -6747,7 +6684,6 @@ public function scanForToken(): int break 2; } case 293: - $token->opcode = Opcode::PHQL_T_HAVING; $this->state->setCursor($yycursor); return 0; @@ -6828,7 +6764,6 @@ public function scanForToken(): int break 2; } case 295: - $token->opcode = Opcode::PHQL_T_INSERT; $this->state->setCursor($yycursor); return 0; @@ -6921,7 +6856,6 @@ public function scanForToken(): int break 2; } case 298: - $token->opcode = Opcode::PHQL_T_OFFSET; $this->state->setCursor($yycursor); return 0; @@ -7002,7 +6936,6 @@ public function scanForToken(): int break 2; } case 300: - $token->opcode = Opcode::PHQL_T_SELECT; $this->state->setCursor($yycursor); return 0; @@ -7083,7 +7016,6 @@ public function scanForToken(): int break 2; } case 302: - $token->opcode = Opcode::PHQL_T_UPDATE; $this->state->setCursor($yycursor); return 0; @@ -7164,7 +7096,6 @@ public function scanForToken(): int break 2; } case 304: - $token->opcode = Opcode::PHQL_T_VALUES; $this->state->setCursor($yycursor); return 0; @@ -7245,7 +7176,6 @@ public function scanForToken(): int break 2; } case 306: - $token->opcode = Opcode::PHQL_T_AGAINST; $this->state->setCursor($yycursor); return 0; @@ -7326,7 +7256,6 @@ public function scanForToken(): int break 2; } case 308: - $token->opcode = Opcode::PHQL_T_BETWEEN; $this->state->setCursor($yycursor); return 0; @@ -7407,7 +7336,6 @@ public function scanForToken(): int break 2; } case 310: - $token->opcode = Opcode::PHQL_T_CONVERT; $this->state->setCursor($yycursor); return 0; @@ -7512,7 +7440,6 @@ public function scanForToken(): int break 2; } case 314: - $token->opcode = Opcode::PHQL_T_DISTINCT; $this->state->setCursor($yycursor); return 0; @@ -7554,7 +7481,6 @@ public function scanForToken(): int break 2; } case 318: - $token->opcode = Opcode::PHQL_T_BETWEEN_NOT; $this->state->setCursor($yycursor); return 0; diff --git a/tests/unit/Parser/PhqlParserTest.php b/tests/unit/Parser/PhqlParserTest.php index b7b479f..1e1cd99 100644 --- a/tests/unit/Parser/PhqlParserTest.php +++ b/tests/unit/Parser/PhqlParserTest.php @@ -102,7 +102,7 @@ public function testUsingFQCNForSourceModel(): void $this->assertEquals('inv_total', $result['select']['columns'][0]['column']['arguments'][0]['name']); $this->assertEquals('average', $result['select']['columns'][0]['alias']); - $this->assertEquals('[Phalcon\\Tests\\Models\\Invoices]', $result['select']['tables']['qualifiedName']['name']); + $this->assertEquals('Phalcon\\Tests\\Models\\Invoices', $result['select']['tables']['qualifiedName']['name']); } /** @@ -159,11 +159,11 @@ public function testUsingSpacesInColumnAlias(): void $this->assertCount(2, $result['select']['columns']); $this->assertEquals('People', $result['select']['columns'][0]['column']['domain']); $this->assertEquals('firstName', $result['select']['columns'][0]['column']['name']); - $this->assertEquals('[First Name]', $result['select']['columns'][0]['alias']); + $this->assertEquals('First Name', $result['select']['columns'][0]['alias']); $this->assertEquals('People', $result['select']['columns'][1]['column']['domain']); $this->assertEquals('lastName', $result['select']['columns'][1]['column']['name']); - $this->assertEquals('[Last Name]', $result['select']['columns'][1]['alias']); + $this->assertEquals('Last Name', $result['select']['columns'][1]['alias']); $this->assertEquals('People', $result['select']['tables']['qualifiedName']['name']); } @@ -190,4 +190,4 @@ public function testDeleteWithWhereAndOr(): void $this->assertArrayHasKey('where', $result); } -} \ No newline at end of file +}