├── .github └── workflows │ └── continuous-integration.yml ├── .gitignore ├── README.md ├── composer.json ├── database.png ├── doc ├── discard_unused_parameters.md ├── images │ ├── schema1.png │ ├── shortest_path.png │ └── shortest_path.pptx ├── magic_join.md └── magic_twig.md ├── phpstan-baseline.neon ├── phpstan.neon ├── phpunit.xml.dist ├── src ├── Mouf │ └── Database │ │ ├── MagicQuery.php │ │ ├── MagicQuery │ │ └── Twig │ │ │ ├── ForbiddenTwigParameterInSqlException.php │ │ │ ├── SqlTwigEnvironmentFactory.php │ │ │ └── StringLoader.php │ │ ├── MagicQueryException.php │ │ ├── MagicQueryMissingConnectionException.php │ │ ├── MagicQueryParserException.php │ │ └── QueryWriter │ │ ├── Condition │ │ ├── ParamAvailableCondition.php │ │ ├── ParamEqualsCondition.php │ │ └── ParamNotAvailableCondition.php │ │ ├── CountNbResult.php │ │ ├── QueryResult.php │ │ ├── ResultSet.php │ │ └── Utils │ │ ├── DbHelper.php │ │ └── FindParametersService.php └── SQLParser │ ├── Node │ ├── AbstractInListOperator.php │ ├── AbstractManyInstancesOperator.php │ ├── AbstractTwoOperandsOperator.php │ ├── AggregateFunction.php │ ├── AndOp.php │ ├── Between.php │ ├── BitwiseAnd.php │ ├── BitwiseOr.php │ ├── BitwiseXor.php │ ├── BypassableInterface.php │ ├── CaseOperation.php │ ├── ColRef.php │ ├── ConstNode.php │ ├── Different.php │ ├── Div.php │ ├── Divide.php │ ├── ElseOperation.php │ ├── Equal.php │ ├── Expression.php │ ├── Greater.php │ ├── GreaterOrEqual.php │ ├── Hint.php │ ├── In.php │ ├── Is.php │ ├── IsNot.php │ ├── Less.php │ ├── LessOrEqual.php │ ├── Like.php │ ├── LimitNode.php │ ├── Minus.php │ ├── Modulo.php │ ├── Multiply.php │ ├── NodeFactory.php │ ├── NodeInterface.php │ ├── NotIn.php │ ├── NotLike.php │ ├── NullCompatibleEqual.php │ ├── Operation.php │ ├── Operator.php │ ├── OrOp.php │ ├── Parameter.php │ ├── Plus.php │ ├── Regexp.php │ ├── Reserved.php │ ├── ShiftLeft.php │ ├── ShiftRight.php │ ├── SimpleFunction.php │ ├── SubQuery.php │ ├── Table.php │ ├── Then.php │ ├── Traverser │ │ ├── CompositeVisitor.php │ │ ├── DetectMagicJoinSelectVisitor.php │ │ ├── DetectTablesVisitor.php │ │ ├── MagicJoinSelect.php │ │ ├── NodeTraverser.php │ │ ├── TraverserException.php │ │ └── VisitorInterface.php │ ├── UnquotedParameter.php │ ├── WhenConditions.php │ └── XorOp.php │ ├── Query │ ├── Select.php │ ├── StatementFactory.php │ ├── StatementInterface.php │ └── Union.php │ └── SqlRenderInterface.php └── tests ├── Mouf └── Database │ ├── MagicQuery │ └── Twig │ │ └── SqlTwigEnvironmentFactoryTest.php │ ├── MagicQueryTest.php │ └── StubSchemaManager.php ├── SQLParser └── Node │ └── Traverser │ ├── DetectTableVisitorTest.php │ └── NodeTraverserTest.php ├── bootstrap.php └── phpunit-mysql8.sh /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: "Continuous Integration" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "1.4" 8 | 9 | jobs: 10 | coding-quality: 11 | name: "Code Quality" 12 | runs-on: "ubuntu-latest" 13 | 14 | strategy: 15 | matrix: 16 | php-version: 17 | - "7.4" 18 | 19 | steps: 20 | - name: "Checkout" 21 | uses: "actions/checkout@v2" 22 | 23 | - name: "Install PHP" 24 | uses: "shivammathur/setup-php@v2" 25 | with: 26 | coverage: "none" 27 | php-version: "${{ matrix.php-version }}" 28 | tools: "cs2pr" 29 | 30 | - name: "Cache dependencies installed with composer" 31 | uses: "actions/cache@v1" 32 | with: 33 | path: "~/.composer/cache" 34 | key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" 35 | restore-keys: "php-${{ matrix.php-version }}-composer-locked-" 36 | 37 | - name: "Install dependencies with composer" 38 | run: "composer install --no-interaction --no-progress --no-suggest" 39 | 40 | - name: "Run a static analysis with phpstan/phpstan" 41 | run: "composer phpstan -- --error-format=checkstyle | cs2pr" 42 | 43 | phpunit-prefer-lowest: 44 | name: "PHPUnit with prefer-lowest" 45 | runs-on: "ubuntu-latest" 46 | 47 | strategy: 48 | matrix: 49 | php-version: 50 | - "7.4" 51 | 52 | services: 53 | mysql: 54 | image: "mysql:8" 55 | env: 56 | MYSQL_ALLOW_EMPTY_PASSWORD: true 57 | MYSQL_ROOT_PASSWORD: 58 | ports: 59 | - "3306:3306" 60 | 61 | steps: 62 | - name: "Checkout" 63 | uses: "actions/checkout@v2" 64 | with: 65 | fetch-depth: 2 66 | 67 | - name: "Install PHP" 68 | uses: "shivammathur/setup-php@v2" 69 | with: 70 | php-version: "${{ matrix.php-version }}" 71 | extensions: "" 72 | 73 | - name: "Cache dependencies installed with composer" 74 | uses: "actions/cache@v1" 75 | with: 76 | path: "~/.composer/cache" 77 | key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" 78 | restore-keys: "php-${{ matrix.php-version }}-composer-locked-" 79 | 80 | - name: "Install dependencies with composer" 81 | run: "composer update --no-interaction --no-progress --no-suggest --prefer-lowest" 82 | 83 | - name: Wait for MySQL 84 | run: | 85 | while ! mysqladmin ping --host=127.0.0.1 --silent; do 86 | sleep 1 87 | done 88 | 89 | - name: "Run PHPUnit" 90 | run: "vendor/bin/phpunit --no-coverage" 91 | 92 | phpunit-code-coverage: 93 | name: "PHPUnit with Code Coverage" 94 | runs-on: "ubuntu-latest" 95 | 96 | strategy: 97 | matrix: 98 | php-version: 99 | - "7.4" 100 | 101 | services: 102 | mysql: 103 | image: "mysql:8" 104 | env: 105 | MYSQL_ALLOW_EMPTY_PASSWORD: true 106 | MYSQL_ROOT_PASSWORD: 107 | ports: 108 | - "3306:3306" 109 | 110 | steps: 111 | - name: "Checkout" 112 | uses: "actions/checkout@v2" 113 | with: 114 | fetch-depth: 2 115 | 116 | - name: "Install PHP" 117 | uses: "shivammathur/setup-php@v2" 118 | with: 119 | php-version: "${{ matrix.php-version }}" 120 | extensions: "" 121 | coverage: "pcov" 122 | 123 | - name: "Cache dependencies installed with composer" 124 | uses: "actions/cache@v1" 125 | with: 126 | path: "~/.composer/cache" 127 | key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" 128 | restore-keys: "php-${{ matrix.php-version }}-composer-locked-" 129 | 130 | - name: "Install dependencies with composer" 131 | run: "composer install --no-interaction --no-progress --no-suggest" 132 | 133 | - name: Wait for MySQL 134 | run: | 135 | while ! mysqladmin ping --host=127.0.0.1 --silent; do 136 | sleep 1 137 | done 138 | 139 | - name: "Run PHPUnit" 140 | run: "vendor/bin/phpunit" 141 | 142 | - name: "Run Coveralls" 143 | run: "vendor/bin/php-coveralls -v" 144 | env: 145 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 146 | 147 | phpunit: 148 | name: "PHPUnit" 149 | runs-on: "ubuntu-latest" 150 | 151 | strategy: 152 | matrix: 153 | php-version: 154 | - "8.0" 155 | 156 | services: 157 | mysql: 158 | image: "mysql:8" 159 | env: 160 | MYSQL_ALLOW_EMPTY_PASSWORD: true 161 | MYSQL_ROOT_PASSWORD: 162 | ports: 163 | - "3306:3306" 164 | 165 | steps: 166 | - name: "Checkout" 167 | uses: "actions/checkout@v2" 168 | with: 169 | fetch-depth: 2 170 | 171 | - name: "Install PHP" 172 | uses: "shivammathur/setup-php@v2" 173 | with: 174 | php-version: "${{ matrix.php-version }}" 175 | extensions: "" 176 | 177 | - name: "Cache dependencies installed with composer" 178 | uses: "actions/cache@v1" 179 | with: 180 | path: "~/.composer/cache" 181 | key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" 182 | restore-keys: "php-${{ matrix.php-version }}-composer-locked-" 183 | 184 | - name: "Install dependencies with composer" 185 | run: "composer install --no-interaction --no-progress --no-suggest" 186 | 187 | - name: Wait for MySQL 188 | run: | 189 | while ! mysqladmin ping --host=127.0.0.1 --silent; do 190 | sleep 1 191 | done 192 | 193 | - name: "Run PHPUnit" 194 | run: "vendor/bin/phpunit --no-coverage" 195 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | phpunit.xml 4 | build/ 5 | .phpunit.result.cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/mouf/magic-query/v/stable)](https://packagist.org/packages/mouf/magic-query) 2 | [![Latest Unstable Version](https://poser.pugx.org/mouf/magic-query/v/unstable)](https://packagist.org/packages/mouf/magic-query) 3 | [![License](https://poser.pugx.org/mouf/magic-query/license)](https://packagist.org/packages/mouf/magic-query) 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/thecodingmachine/magic-query/badges/quality-score.png?b=1.2)](https://scrutinizer-ci.com/g/thecodingmachine/magic-query/?branch=1.2) 5 | [![Build Status](https://travis-ci.org/thecodingmachine/magic-query.svg?branch=1.2)](https://travis-ci.org/thecodingmachine/magic-query) 6 | [![Coverage Status](https://coveralls.io/repos/thecodingmachine/magic-query/badge.svg?branch=1.2)](https://coveralls.io/r/thecodingmachine/magic-query?branch=1.2) 7 | 8 | What is Magic-query? 9 | ==================== 10 | 11 | Magic-query is a PHP library that helps you work with complex SQL queries. 12 | 13 | It comes with 3 great features: 14 | 15 | - [**MagicParameters**: it helps you work with SQL queries that require a variable number of parameters.](#parameters) 16 | - [**MagicJoin**: it writes JOINs for you!](#joins) 17 | - [**MagicTwig**: use Twig templating in your SQL queries](#twig) 18 | 19 | Installation 20 | ------------ 21 | 22 | Simply use the composer package: 23 | 24 | ```json 25 | { 26 | "require": { 27 | "mouf/magic-query": "^1.2" 28 | }, 29 | "minimum-stability": "dev", 30 | "prefer-stable": true 31 | } 32 | ``` 33 | 34 | 35 | Automatically discard unused parameters with MagicParameters 36 | ------------------------------------------------------------ 37 | 38 | Just write the query with all possible parameters. 39 | 40 | ```php 41 | use Mouf\Database\MagicQuery; 42 | 43 | $sql = "SELECT * FROM users WHERE name LIKE :name AND country LIKE :country"; 44 | 45 | // Get a MagicQuery object. 46 | $magicQuery = new MagicQuery(); 47 | 48 | // Let's pass only the "name" parameter 49 | $result = $magicQuery->build($sql, [ "name" => "%John%" ]); 50 | // $result = SELECT * FROM users WHERE name LIKE '%John%' 51 | // Did you notice how the bit about the country simply vanished? 52 | 53 | // Let's pass no parameter at all! 54 | $result2 = $magicQuery->build($sql, []); 55 | // $result2 = SELECT * FROM users 56 | // The whole WHERE condition disappeared because it is not needed anymore! 57 | ``` 58 | 59 | Curious to know how this work? Check out the parameters guide! 60 | 61 | 62 | Automatically guess JOINs with MagicJoin! 63 | ----------------------------------------- 64 | 65 | Fed up of writing joins in SQL? Let MagicQuery do the work for you! 66 | 67 | Seriously? Yes! All you have to do is: 68 | 69 | - Pass a **[Doctrine DBAL connection](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/)** to MagicQuery's constructor. MagicQuery will analyze your schema. 70 | - In your SQL query, replace the tables with `magicjoin(start_table)` 71 | - For each column of your query, use the complete name ([table_name].[column_name] instead of [column_name] alone) 72 | 73 | Let's assume your database schema is: 74 | 75 | ![Sample database schema](doc/images/schema1.png) 76 | 77 | Using MagicJoin, you can write this SQL query: 78 | 79 | ```sql 80 | SELECT users.* FROM MAGICJOIN(users) WHERE groups.name = 'Admins' AND country.name='France'; 81 | ``` 82 | 83 | and it will automatically be transformed into this: 84 | 85 | ```sql 86 | SELECT users.* FROM users 87 | LEFT JOIN users_groups ON users.user_id = users_groups.user_id 88 | LEFT JOIN groups ON groups.group_id = users_groups.group_id 89 | LEFT JOIN country ON country.country_id = users.country_id 90 | WHERE groups.name = 'Admins' AND country.name='France'; 91 | ``` 92 | 93 | And the code is so simple! 94 | 95 | ```php 96 | use Mouf\Database\MagicQuery; 97 | 98 | $sql = "SELECT users.* FROM MAGICJOIN(users) WHERE groups.name = 'Admins' AND country.name='France'"; 99 | 100 | // Get a MagicQuery object. 101 | // $conn is a Doctrine DBAL connection. 102 | $magicQuery = new MagicQuery($conn); 103 | 104 | $completeSql = $magicQuery->build($sql); 105 | // $completeSql contains the complete SQL request, with all joins. 106 | ``` 107 | 108 | Want to know more? Check out the MagicJoin guide! 109 | 110 | 111 | Use Twig templating in your SQL queries! 112 | ---------------------------------------- 113 | 114 | Discarding unused parameters and auto-joining keys is not enough? You have very specific needs? Say hello to 115 | Twig integration! 116 | 117 | Using Twig integration, you can directly add Twig conditions right into your SQL. 118 | 119 | ```php 120 | use Mouf\Database\MagicQuery; 121 | 122 | $sql = "SELECT users.* FROM users {% if isAdmin %} WHERE users.admin = 1 {% endif %}"; 123 | 124 | $magicQuery = new MagicQuery(); 125 | // By default, Twig integration is disabled. You need to enable it. 126 | $magicQuery->setEnableTwig(true); 127 | 128 | $completeSql = $magicQuery->build($sql, ['isAdmin' => true]); 129 | // Parameters are passed to the Twig SQL query, and the SQL query is returned. 130 | ``` 131 | 132 |
Heads up! The Twig integration cannot be used to insert parameters 133 | into the SQL query. You should use classic SQL parameters for this. This means that instead if writing 134 | {{ id }}, you should write :id.
135 | 136 | Want to know more? Check out the MagicTwig guide! 137 | 138 | Is it a MySQL only tool? 139 | ------------------------ 140 | 141 | No. By default, your SQL is parsed and then rewritten using the MySQL dialect, but you use any kind of dialect 142 | known by [Doctrine DBAL](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/). Magic-query optionally uses Doctrine DBAL. You can pass a `Connection` object 143 | as the first parameter of the `MagicQuery` constructor. Magic-query will then use the matching dialect. 144 | 145 | For instance: 146 | 147 | ```php 148 | $config = new \Doctrine\DBAL\Configuration(); 149 | $connectionParams = array( 150 | 'url' => 'sqlite:///somedb.sqlite', 151 | ); 152 | $conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config); 153 | 154 | $magicQuery = new \Mouf\Database\MagicQuery($conn); 155 | ``` 156 | 157 | Also, if you have no connection to your database configured but you want to generate SQL in some specific dialect, you can 158 | instead set the DBAL database platform used: 159 | 160 | ```php 161 | $magicQuery->setOutputDialect(new \Doctrine\DBAL\Platforms\PostgreSqlPlatform()); 162 | $magicQuery = new \Mouf\Database\MagicQuery(); 163 | ``` 164 | 165 | 166 | What about performances? 167 | ------------------------ 168 | 169 | MagicQuery does a lot to your query. It will parse it, render it internally as a tree of SQL nodes, etc... 170 | This processing is time consuming. So you should definitely consider using a cache system. MagicQuery is compatible 171 | with Doctrine Cache. You simply have to pass a Doctrine Cache instance has the second parameter of the constructor. 172 | 173 | ```php 174 | use Mouf\Database\MagicQuery; 175 | use Doctrine\Common\Cache\ApcCache; 176 | 177 | // $conn is a Doctrine connection 178 | $magicQuery = new MagicQuery($conn, new ApcCache()); 179 | ``` 180 | 181 | Any problem? 182 | ------------ 183 | 184 | With MagicQuery, a lot happens to your SQL query. In particular, it is parsed using a modified version 185 | of the php-sql-parser library. If you face any issues with a complex query, it is likely there is a bug 186 | in the parser. Please open [an issue on Github](https://github.com/thecodingmachine/magic-query/issues) and we'll try to fix it. 187 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mouf/magic-query", 3 | "description": "A very clever library to help you with SQL: generate prepared statements with a variable number of parameters, automatically writes joins... and much more!", 4 | "keywords": ["database", "query", "mouf"], 5 | "homepage": "http://mouf-php.com/packages/mouf/magic-query", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "David Négrier", 11 | "email": "d.negrier@thecodingmachine.com", 12 | "homepage": "http://mouf-php.com" 13 | } 14 | ], 15 | "require": { 16 | "php": "^7.4 || ^8.0", 17 | "mouf/utils.common.conditioninterface": "~2.0", 18 | "mouf/utils.value.value-interface": "~1.0", 19 | "mouf/utils.common.paginable-interface": "~1.0", 20 | "mouf/utils.common.sortable-interface": "~1.0", 21 | "mouf/schema-analyzer": "^2.0", 22 | "twig/twig": "^2.11 || ^3", 23 | "greenlion/php-sql-parser": "^4.3", 24 | "doctrine/cache": "^1.5" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^9.5", 28 | "php-coveralls/php-coveralls": "^2.0.0", 29 | "doctrine/dbal": "^3.0", 30 | "phpstan/phpstan": "^0.12.82" 31 | }, 32 | "suggest": { 33 | "doctrine/dbal": "To support more databases than just MySQL and to use MagicJoin feature", 34 | "mouf/database.querywriter": "To get a nice user interface to edit your SQL queries", 35 | "mouf/mouf": "To get a nice user interface to edit your SQL queries" 36 | }, 37 | "conflict": { 38 | "mouf/database.querywriter": "< 4.0" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Mouf\\Database\\": "src/Mouf/Database/", 43 | "SQLParser\\": "src/SQLParser/" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Mouf\\Database\\": "tests/Mouf/Database/", 49 | "SQLParser\\": "tests/SQLParser/" 50 | } 51 | }, 52 | "extra": { 53 | "mouf": { 54 | "logo": "database.png", 55 | "doc": [ 56 | { 57 | "title": "MagicParameters: automatically discard unused parameters", 58 | "url": "doc/discard_unused_parameters.md" 59 | }, 60 | { 61 | "title": "MagicJoin: let MagicQuery write JOINs for you", 62 | "url": "doc/magic_join.md" 63 | } 64 | ] 65 | } 66 | }, 67 | "minimum-stability": "dev", 68 | "prefer-stable": true, 69 | "scripts": { 70 | "phpstan": "vendor/bin/phpstan analyse -c phpstan.neon" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodingmachine/magic-query/b813bec1534e96d4ffe97a1a8d633df874d721a5/database.png -------------------------------------------------------------------------------- /doc/discard_unused_parameters.md: -------------------------------------------------------------------------------- 1 | Automatically discard unused parameters 2 | --------------------------------------- 3 | 4 | ###How does it work? 5 | 6 | Just write the query with all possible parameters. 7 | 8 | ```php 9 | use Mouf\Database\MagicQuery; 10 | 11 | $sql = "SELECT * FROM users WHERE name LIKE :name AND country LIKE :country"; 12 | 13 | // Get a MagicQuery object. 14 | $magicQuery = new MagicQuery(); 15 | 16 | // Let's pass only the "name" parameter 17 | $result = $magicQuery->build($sql, [ "name" => "%John%" ]); 18 | // $result = SELECT * FROM users WHERE name LIKE '%John%' 19 | // Did you notice how the bit about the country simply vanished? 20 | 21 | // Let's pass no parameter at all! 22 | $result2 = $magicQuery->build($sql, []); 23 | // $result2 = SELECT * FROM users 24 | // The whole WHERE condition disappeared because it is not needed anymore! 25 | ``` 26 | 27 | Magic-parameters can also collapse a BETWEEN filter into a simple `>=` or `<=` filter: 28 | 29 | ```php 30 | $sql = "SELECT * FROM products WHERE status BETWEEN :lowerStatus AND :upperStatus"; 31 | 32 | // Let's pass only the "lowerStatus" parameter 33 | $result = $magicQuery->build($sql, [ "lowerStatus" => 2 ]); 34 | // $result = SELECT * FROM products WHERE status >= 2 35 | // See? The BETWEEN filter was transformed in a >= filter because we did not provide a higher limit. 36 | ``` 37 | 38 | ###Why should I care? 39 | 40 | Because it is **the most efficient way to deal with queries that can have a variable number of parameters**! 41 | Think about a typical datagrid with a bunch of filter (for instance a list of products filtered by name, company, price, ...). 42 | If you have the very common idea to generate the SQL query using no PHP library, your code will look like this: 43 | 44 | ####Without Magic-query 45 |
You should not do this!
46 | 47 | ```php 48 | // People usually write queries like this: 49 | $sql = "SELECT * FROM products p JOIN companies c ON p.company_id = c.id WHERE 1=1 "; 50 | // They keep testing for parameters, and concatenating strings.... 51 | if (isset($params['name'])) { 52 | $sql .= "AND (p.name LIKE '".addslashes($params['name'])."%' OR p.altname LIKE '".addslashes($params['name'])."%')"; 53 | } 54 | if (isset($params['company'])) { 55 | $sql .= "AND c.name LIKE '".addslashes($params['company'])."%'"; 56 | } 57 | if (isset($params['country'])) { 58 | $sql .= "AND c.country LIKE '".addslashes($params['country'])."%'"; 59 | } 60 | // And so on... for each parameter, we have a "if" statement 61 | ``` 62 | 63 | Concatenating SQL queries is **dangerous** (especially if you forget to protect parameters). 64 | You can always use parametrized SQL queries, but you will still have to concatenate the filters. 65 | 66 | ####With Magic-Query 67 | 68 | ```php 69 | // One query with all parameters 70 | $sql = "SELECT * FROM products p JOIN companies c ON p.company_id = c.id WHERE 71 | (p.name LIKE :name OR p.altname LIKE :name) 72 | AND c.name LIKE :company 73 | AND c.country LIKE :country"; 74 | 75 | $magicQuery = new MagicQuery(); 76 | $sql = $magicQuery->build($sql, $params); 77 | ``` 78 | 79 | ####Forcing some parameters to be null 80 | 81 | MagicQuery assumes that if a parameter is null, you want to completely remove the code related to this 82 | parameter from the SQL. 83 | 84 | But sometimes, you actually want to test parameters against the NULL value. 85 | In those cases, you need to disable the MagicParameter feature of MagicQuery for those parameters. 86 | 87 |
To disable MagicParameter for a given parameter, simply add an exclamation 88 | mark (!) after the parameter name.
89 | 90 | ```php 91 | // This query uses twice the "status" parameter. Once with ! and once without 92 | $sql = "SELECT * FROM products p WHERE status1 = :status AND status2 = :status!"; 93 | 94 | $magicQuery = new MagicQuery(); 95 | // We don't pass the "status" parameter 96 | $sql = $magicQuery->build($sql, []); 97 | // The part "status1 = :status" is discarded, but the part "status2 = :status!" is kept 98 | // $sql == "SELECT * FROM products p WHERE status2 = null" 99 | ``` 100 | 101 | ####Working with prepared statements 102 | 103 | The `MagicQuery::build` method will remove unused parameters AND replace placeholders with the values. 104 | If you are looking for the best possible performance, you can use the alternative `MagicQuery::buildPreparedStatement` method. 105 | 106 | The "buildPreparedStatement" method is removing unused parameters BUT it does not replace placeholders. 107 | As a result, MagicQuery can perform some more internal caching and if you have many similar requests, 108 | you will get some performance gains. 109 | 110 | ###Other alternatives 111 | 112 | To avoid concatenating strings, frameworks and libraries have used different strategies. Using a full ORM (like 113 | Doctrine or Propel) is a good idea, but it makes writing complex queries even more complex. Other frameworks like 114 | Zend are building queries using function calls. These are valid strategies, but you are no more typing SQL queries 115 | directly, and let's face it, it is always useful to use a query directly. 116 | 117 | How does it work under the hood? 118 | -------------------------------- 119 | 120 | A lot happens to your SQL query. It is actually parsed (thanks to a modified 121 | version of the php-sql-parser library) and then changed into a tree. 122 | The magic happens on the tree where the node containing unused parameters 123 | are simply discarded. When it's done, the tree is changed back to SQL and 124 | "shazam!", your SQL query is purged of useless parameters! -------------------------------------------------------------------------------- /doc/images/schema1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodingmachine/magic-query/b813bec1534e96d4ffe97a1a8d633df874d721a5/doc/images/schema1.png -------------------------------------------------------------------------------- /doc/images/shortest_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodingmachine/magic-query/b813bec1534e96d4ffe97a1a8d633df874d721a5/doc/images/shortest_path.png -------------------------------------------------------------------------------- /doc/images/shortest_path.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodingmachine/magic-query/b813bec1534e96d4ffe97a1a8d633df874d721a5/doc/images/shortest_path.pptx -------------------------------------------------------------------------------- /doc/magic_join.md: -------------------------------------------------------------------------------- 1 | Automatically guess JOINs with MagicJoin! 2 | ----------------------------------------- 3 | 4 | Fed up of writing joins in SQL? Let MagicQuery do the work for you! 5 | 6 | Seriously? Yes! All you have to do is: 7 | 8 | - Pass a **Doctrine DBAL connection** to MagicQuery's constructor. MagicQuery will analyze your schema. 9 | - In your SQL query, replace the tables with `magicjoin(start_table)` 10 | - For each column of your query, use the complete name ([table_name].[column_name] instead of [column_name] alone) 11 | 12 | Let's assume your database schema is: 13 | 14 | ![Sample database schema](images/schema1.png) 15 | 16 | Using MagicJoin, you can write this SQL query: 17 | 18 | ```sql 19 | SELECT users.* FROM MAGICJOIN(users) WHERE groups.name = 'Admins' AND country.name='France'; 20 | ``` 21 | 22 | and it will automatically be transformed into this: 23 | 24 | ```sql 25 | SELECT users.* FROM users 26 | LEFT JOIN users_groups ON users.user_id = users_groups.user_id 27 | LEFT JOIN groups ON groups.group_id = users_groups.group_id 28 | LEFT JOIN country ON country.country_id = users.country_id 29 | WHERE groups.name = 'Admins' AND country.name='France'; 30 | ``` 31 | 32 | And the code is so simple! 33 | 34 | ```php 35 | use Mouf\Database\MagicQuery; 36 | 37 | $sql = "SELECT users.* FROM MAGICJOIN(users) WHERE groups.name = 'Admins' AND country.name='France'"; 38 | 39 | // Get a MagicQuery object. 40 | // $conn is a Doctrine DBAL connection. 41 | $magicQuery = new MagicQuery($conn); 42 | 43 | $completeSql = $magicQuery->build($sql); 44 | // $completeSql contains the complete SQL request, with all joins. 45 | ``` 46 | 47 | ###How does it work? 48 | 49 | When you reference a column, you have to give its full name in the form **[table_name].[column_name]**. 50 | Because each column is referenced by its full name, MagicJoin is able to build a list of tables that 51 | need to be joined. 52 | 53 | Then, MagicJoin scans your data model. Using **foreign keys**, it discovers the relationship between 54 | tables by itself. Then, it finds the **shortest path** between you "main" table and all the other tables that are 55 | needed. 56 | 57 | From this shortest path, it can build the correct JOINs. 58 | 59 | Now that you understand the concept, you should understand the power and the limits of MagicJoin. 60 | 61 | - MagicJoin **cannot generate queries with recursive relationships** (like a parent/child relationship). This is 62 | because parent/child relationships are represented by loops in the dependency graph, and a loop is never the 63 | shortest path. 64 | - MagicJoin assumes you are looking for the shortest path between 2 tables 65 | - This is 80% of the case true 66 | - If you are in the remaining 20%, do not use MagicJoin 67 | 68 |
MagicJoin is meant to be used on the 80% of the cases where writing joins is trivial 69 | and boring. If you have complex joins, do not try to use MagicJoin. Go back to pure SQL instead.
70 | 71 | If you are looking for details on how this shortest path is computed, have a look at 72 | [mouf/schema-analyzer](http://mouf-php.com/packages/mouf/schema-analyzer/README.md) which is the package in charge 73 | of computing that shortest path. 74 | 75 | ###Ambiguity exceptions and fine-tuning 76 | 77 | Sometimes, there can be 2 possible paths that link 2 tables and that are equally short. 78 | In this case, rather than choosing a path at random, MagicQuery will let you know there is a problem by issuing 79 | a `ShortestPathAmbiguityException`. 80 | 81 | ![Ambiguity in paths](images/shortest_path.png) 82 | 83 | Let's have a look at the example above. We have *products* that are part of a *store*. Stores are part of a *district*. 84 | Both *districts* and *products* have a *status*. 85 | 86 | Now, let's have a look at this query: 87 | 88 | ```sql 89 | SELECT products.* FROM MAGICJOIN(products) WHERE district.name = 'NY'; 90 | ``` 91 | 92 | Very obviously, we want all the products from the district. Bu for MagicJoin, this is far from obvious. 93 | There are really 2 paths from products to district. One is going through the "store" table and one through the "status" 94 | table. Those paths are equally short. 95 | 96 | We need to *hint* MagicJoin into understanding that the "status" table is irrelevant. 97 | 98 | To do this, we must first provide to MagicJoin an instance of `SchemaAnalyzer`. [`SchemaAnalyzer` is the underlying 99 | package that is in charge of computing the shortest path.](http://mouf-php.com/packages/mouf/schema-analyzer/README.md) 100 | 101 | ```php 102 | use Mouf\Database\MagicQuery; 103 | use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer; 104 | 105 | // $conn is a Doctrine DBAL connection. 106 | // $cache is a Doctrine Cache 107 | 108 | // Get a SchemaAnalyzer object. 109 | $schemaAnalyzer = new SchemaAnalyzer($conn->getSchemaManager(), $cache, "some_unique_key"); 110 | 111 | // On SchemaAnalyzer, let's modify the cost of the "status" table to make it unlikely to be used: 112 | $schemaAnalyzer->setTableCostModifier("status", SchemaAnalyzer::WEIGHT_IRRELEVANT); 113 | 114 | // Get a MagicQuery object. 115 | $magicQuery = new MagicQuery($conn, $cache, $schemaAnalyzer); 116 | 117 | $completeSql = $magicQuery->build("SELECT products.* FROM MAGICJOIN(products) WHERE district.name = 'NY'"); 118 | ``` 119 | 120 | The call to `setTableCostModifier` above will explain to MagicJoin that the *status* table is mostly irrelevant, 121 | and that it should most of the time be avoided. 122 | 123 | You can also set a *cost* on a single foreign key rather than on a table. For instance: 124 | 125 | ```php 126 | // Avoid using the "status_id" foreign_key from the "products" table. 127 | $schemaAnalyzer->setForeignKeyCost("products", "status_id", SchemaAnalyzer::WEIGHT_IRRELEVANT); 128 | ``` 129 | 130 | ###With great power comes great responsibility 131 | 132 | MagicJoin is a powerful feature because it can **guess** what you want to do. Please be wise while using it. 133 | A change in your model could modify the shortest path computed by MagicJoin and therefore the returned SQL query. 134 | 135 | If you database model is complex enough and is rapidly changing, generated queries could suddenly change in 136 | your application. 137 | 138 | Please consider one of these 2 options: 139 | 140 | - write unit tests (you have unit tests, don't you?) 141 | - use MagicJoin in development mode only. While developing, dump the SQL generated by MagicJoin and put it back 142 | in your code. 143 | -------------------------------------------------------------------------------- /doc/magic_twig.md: -------------------------------------------------------------------------------- 1 | Use Twig templating in your SQL queries! 2 | ---------------------------------------- 3 | 4 | Using Twig integration, you can directly add Twig conditions right into your SQL. 5 | 6 | ```php 7 | use Mouf\Database\MagicQuery; 8 | 9 | $sql = "SELECT users.* FROM users {% if isAdmin %} WHERE users.admin = 1 {% endif %}"; 10 | 11 | $magicQuery = new MagicQuery(); 12 | // By default, Twig integration is disabled. You need to enable it. 13 | $magicQuery->setEnableTwig(true); 14 | 15 | $completeSql = $magicQuery->build($sql, ['isAdmin' => true]); 16 | // Parameters are passed to the Twig SQL query, and the SQL query is returned. 17 | ``` 18 | 19 | Limitations 20 | ----------- 21 | 22 |
Heads up! The Twig integration cannot be used to insert parameters 23 | into the SQL query. You should use classic SQL parameters for this. This means that instead if writing 24 | {{ id }}, you should write :id.
25 | 26 | You cannot directly use Twig parameters because Twig transformation is applied before SQL parsing. If parameters 27 | where replaced by Twig before SQL is parsed, the caching of the transformation *SQL => parsed SQL* would become 28 | inefficient. 29 | 30 | For this reason, if you try to use `{{ parameter }}` instead of `:parameter` in your SQL query, an exception will 31 | be thrown. 32 | 33 | Usage 34 | ----- 35 | 36 | For most queries, we found out that MagicJoin combined to MagicParameters is enough. 37 | For this reason, MagicTwig is disabled by default. If you want to enable it, you can simply call 38 | `$magicQuery->setEnableTwig(true)` before calling the `build` method. 39 | 40 | MagicTwig can typically be useful when you have parts of a query that needs to be added conditionally, depending 41 | on provided parameters. 42 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Method Mouf\\\\Database\\\\QueryWriter\\\\QueryResult\\:\\:val\\(\\) should return array but returns Mouf\\\\Database\\\\QueryWriter\\\\ResultSet\\.$#" 5 | count: 1 6 | path: src/Mouf/Database/QueryWriter/QueryResult.php 7 | 8 | - 9 | message: "#^Method Mouf\\\\Database\\\\QueryWriter\\\\Utils\\\\DbHelper\\:\\:getAll\\(\\) has no return typehint specified\\.$#" 10 | count: 1 11 | path: src/Mouf/Database/QueryWriter/Utils/DbHelper.php 12 | 13 | - 14 | message: "#^Parameter \\#1 \\$rightOperand of method SQLParser\\\\Node\\\\AbstractInListOperator\\:\\:refactorParameterToExpression\\(\\) expects SQLParser\\\\Node\\\\NodeInterface, array\\\\|SQLParser\\\\Node\\\\NodeInterface\\|string given\\.$#" 15 | count: 1 16 | path: src/SQLParser/Node/AbstractInListOperator.php 17 | 18 | - 19 | message: "#^Cannot access offset 0 on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\.$#" 20 | count: 1 21 | path: src/SQLParser/Node/AbstractInListOperator.php 22 | 23 | - 24 | message: "#^Method SQLParser\\\\Node\\\\AbstractTwoOperandsOperator\\:\\:setLeftOperand\\(\\) has no return typehint specified\\.$#" 25 | count: 1 26 | path: src/SQLParser/Node/AbstractTwoOperandsOperator.php 27 | 28 | - 29 | message: "#^Method SQLParser\\\\Node\\\\AbstractTwoOperandsOperator\\:\\:setRightOperand\\(\\) has no return typehint specified\\.$#" 30 | count: 1 31 | path: src/SQLParser/Node/AbstractTwoOperandsOperator.php 32 | 33 | - 34 | message: "#^Cannot call method walk\\(\\) on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\|string\\.$#" 35 | count: 2 36 | path: src/SQLParser/Node/AbstractTwoOperandsOperator.php 37 | 38 | - 39 | message: "#^Method SQLParser\\\\Node\\\\Between\\:\\:setLeftOperand\\(\\) has no return typehint specified\\.$#" 40 | count: 1 41 | path: src/SQLParser/Node/Between.php 42 | 43 | - 44 | message: "#^Method SQLParser\\\\Node\\\\Between\\:\\:setMinValueOperand\\(\\) has no return typehint specified\\.$#" 45 | count: 1 46 | path: src/SQLParser/Node/Between.php 47 | 48 | - 49 | message: "#^Cannot call method walk\\(\\) on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\|string\\.$#" 50 | count: 1 51 | path: src/SQLParser/Node/Between.php 52 | 53 | - 54 | message: "#^Cannot call method walk\\(\\) on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\|string\\.$#" 55 | count: 1 56 | path: src/SQLParser/Node/CaseOperation.php 57 | 58 | - 59 | message: "#^Cannot access offset mixed on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\.$#" 60 | count: 2 61 | path: src/SQLParser/Node/Expression.php 62 | 63 | - 64 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:toObject\\(\\) has no return typehint specified\\.$#" 65 | count: 1 66 | path: src/SQLParser/Node/NodeFactory.php 67 | 68 | - 69 | message: "#^Parameter \\#1 \\$refClause of method SQLParser\\\\Node\\\\SubQuery\\:\\:setRefClause\\(\\) expects array\\, array\\\\|SQLParser\\\\Node\\\\NodeInterface given\\.$#" 70 | count: 1 71 | path: src/SQLParser/Node/NodeFactory.php 72 | 73 | - 74 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:buildFromSubtree\\(\\) has no return typehint specified\\.$#" 75 | count: 1 76 | path: src/SQLParser/Node/NodeFactory.php 77 | 78 | - 79 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:buildFromSubtree\\(\\) has parameter \\$subTree with no typehint specified\\.$#" 80 | count: 1 81 | path: src/SQLParser/Node/NodeFactory.php 82 | 83 | - 84 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:mapArrayToNodeObjectList\\(\\) has no return typehint specified\\.$#" 85 | count: 1 86 | path: src/SQLParser/Node/NodeFactory.php 87 | 88 | - 89 | message: "#^Property SQLParser\\\\Node\\\\NodeFactory\\:\\:\\$PRECEDENCE has no typehint specified\\.$#" 90 | count: 1 91 | path: src/SQLParser/Node/NodeFactory.php 92 | 93 | - 94 | message: "#^Property SQLParser\\\\Node\\\\NodeFactory\\:\\:\\$OPERATOR_TO_CLASS has no typehint specified\\.$#" 95 | count: 1 96 | path: src/SQLParser/Node/NodeFactory.php 97 | 98 | - 99 | message: "#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, array\\\\|SQLParser\\\\Node\\\\NodeInterface given\\.$#" 100 | count: 1 101 | path: src/SQLParser/Node/NodeFactory.php 102 | 103 | - 104 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:array_map_deep\\(\\) has no return typehint specified\\.$#" 105 | count: 1 106 | path: src/SQLParser/Node/NodeFactory.php 107 | 108 | - 109 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:array_map_deep\\(\\) has parameter \\$array with no typehint specified\\.$#" 110 | count: 1 111 | path: src/SQLParser/Node/NodeFactory.php 112 | 113 | - 114 | message: "#^Method SQLParser\\\\Node\\\\NodeFactory\\:\\:array_map_deep\\(\\) has parameter \\$callback with no typehint specified\\.$#" 115 | count: 1 116 | path: src/SQLParser/Node/NodeFactory.php 117 | 118 | - 119 | message: "#^Property SQLParser\\\\Node\\\\Operator\\:\\:\\$value has no typehint specified\\.$#" 120 | count: 1 121 | path: src/SQLParser/Node/Operator.php 122 | 123 | - 124 | message: "#^Method SQLParser\\\\Node\\\\Operator\\:\\:getValue\\(\\) has no return typehint specified\\.$#" 125 | count: 1 126 | path: src/SQLParser/Node/Operator.php 127 | 128 | - 129 | message: "#^Method SQLParser\\\\Node\\\\Operator\\:\\:setValue\\(\\) has no return typehint specified\\.$#" 130 | count: 1 131 | path: src/SQLParser/Node/Operator.php 132 | 133 | - 134 | message: "#^Strict comparison using \\=\\=\\= between mixed and null will always evaluate to false\\.$#" 135 | count: 1 136 | path: src/SQLParser/Node/Parameter.php 137 | 138 | - 139 | message: "#^Argument of an invalid type array\\\\|SQLParser\\\\Node\\\\NodeInterface supplied for foreach, only iterables are supported\\.$#" 140 | count: 1 141 | path: src/SQLParser/Node/SimpleFunction.php 142 | 143 | - 144 | message: "#^Cannot access offset mixed on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\.$#" 145 | count: 2 146 | path: src/SQLParser/Node/SimpleFunction.php 147 | 148 | - 149 | message: "#^Cannot access offset mixed on array\\\\|SQLParser\\\\Node\\\\NodeInterface\\.$#" 150 | count: 2 151 | path: src/SQLParser/Node/Table.php 152 | 153 | - 154 | message: "#^Elseif condition is always true\\.$#" 155 | count: 1 156 | path: src/SQLParser/Node/Table.php 157 | 158 | - 159 | message: "#^Property SQLParser\\\\Node\\\\Table\\:\\:\\$refClause \\(array\\\\|SQLParser\\\\Node\\\\NodeInterface\\) does not accept null\\.$#" 160 | count: 1 161 | path: src/SQLParser/Node/Table.php 162 | 163 | - 164 | message: "#^Method SQLParser\\\\Query\\\\Select\\:\\:overwriteInstanceDescriptor\\(\\) has parameter \\$name with no typehint specified\\.$#" 165 | count: 1 166 | path: src/SQLParser/Query/Select.php 167 | 168 | - 169 | message: "#^Method SQLParser\\\\Query\\\\Select\\:\\:walkChildren\\(\\) has parameter \\$children with no typehint specified\\.$#" 170 | count: 1 171 | path: src/SQLParser/Query/Select.php 172 | 173 | - 174 | message: "#^Argument of an invalid type array\\\\|SQLParser\\\\Node\\\\NodeInterface supplied for foreach, only iterables are supported\\.$#" 175 | count: 1 176 | path: src/SQLParser/Query/StatementFactory.php 177 | 178 | - 179 | message: "#^Cannot access offset mixed on \\(array\\&nonEmpty\\)\\|SQLParser\\\\Node\\\\NodeInterface\\.$#" 180 | count: 1 181 | path: src/SQLParser/Query/StatementFactory.php 182 | 183 | - 184 | message: "#^Parameter \\#1 \\$columns of method SQLParser\\\\Query\\\\Select\\:\\:setColumns\\(\\) expects array\\, array\\\\|SQLParser\\\\Node\\\\NodeInterface given\\.$#" 185 | count: 1 186 | path: src/SQLParser/Query/StatementFactory.php 187 | 188 | - 189 | message: "#^Method SQLParser\\\\Query\\\\Union\\:\\:overwriteInstanceDescriptor\\(\\) has parameter \\$name with no typehint specified\\.$#" 190 | count: 1 191 | path: src/SQLParser/Query/Union.php 192 | 193 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 7 6 | paths: 7 | - src 8 | inferPrivatePropertyTypeFromConstructor: true 9 | checkMissingIterableValueType: false 10 | checkGenericClassInNonGenericObjectType: false 11 | reportUnmatchedIgnoredErrors: false 12 | ignoreErrors: 13 | - "#Mouf\\\\MoufManager#" 14 | - "#Mouf\\\\MoufInstanceDescriptor#" 15 | - '#Method Mouf\\Database\\QueryWriter\\Utils\\FindParametersService::findParameters\(\) should return array but returns array#' 16 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | src/ 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ./tests/ 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Mouf/Database/MagicQuery/Twig/ForbiddenTwigParameterInSqlException.php: -------------------------------------------------------------------------------- 1 | self::getCacheDirectory(), 29 | 'strict_variables' => true, 30 | 'autoescape' => 'sql' // Default autoescape mode: sql 31 | ); 32 | 33 | $twig = new Environment($stringLoader, $options); 34 | 35 | // Default escaper will throw an exception. This is because we want to use SQL parameters instead of Twig. 36 | // This has a number of advantages, especially in terms of caching. 37 | 38 | /** @var EscaperExtension $twigExtensionEscaper */ 39 | $twigExtensionEscaper = $twig->getExtension(EscaperExtension::class); 40 | $twigExtensionEscaper->setEscaper('sql', function () { 41 | throw new ForbiddenTwigParameterInSqlException('You cannot use Twig expressions (like "{{ id }}"). Instead, you should use SQL parameters (like ":id"). Twig integration is limited to Twig statements (like "{% for .... %}"'); 42 | }); 43 | 44 | self::$twig = $twig; 45 | 46 | return $twig; 47 | } 48 | 49 | private static function getCacheDirectory(): string 50 | { 51 | // If we are running on a Unix environment, let's prepend the cache with the user id of the PHP process. 52 | // This way, we can avoid rights conflicts. 53 | 54 | // @codeCoverageIgnoreStart 55 | if (function_exists('posix_geteuid')) { 56 | $posixGetuid = '_'.posix_geteuid(); 57 | } else { 58 | $posixGetuid = ''; 59 | } 60 | // @codeCoverageIgnoreEnd 61 | $cacheDirectory = rtrim(sys_get_temp_dir(), '/\\').'/magicquerysqltwigtemplate'.$posixGetuid.str_replace(':', '', __DIR__); 62 | 63 | return $cacheDirectory; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Mouf/Database/MagicQuery/Twig/StringLoader.php: -------------------------------------------------------------------------------- 1 | parameterName = $parameterName; 24 | } 25 | 26 | public function isOk($parameters = null) 27 | { 28 | return isset($parameters[$this->parameterName]) && !empty($parameters[$this->parameterName]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/Condition/ParamEqualsCondition.php: -------------------------------------------------------------------------------- 1 | parameterName = $parameterName; 28 | $this->value = $value; 29 | } 30 | 31 | public function isOk($parameters = null) 32 | { 33 | return isset($parameters[$this->parameterName]) && $parameters[$this->parameterName] == ValueUtils::val($this->value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/Condition/ParamNotAvailableCondition.php: -------------------------------------------------------------------------------- 1 | parameterName = $parameterName; 24 | } 25 | 26 | public function isOk($parameters = null) 27 | { 28 | return !isset($parameters[$this->parameterName]) || empty($parameters[$this->parameterName]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/CountNbResult.php: -------------------------------------------------------------------------------- 1 | queryResult = $queryResult; 41 | $this->connection = $connection; 42 | } 43 | 44 | /** 45 | * (non-PHPdoc). 46 | * 47 | * @see \Mouf\Utils\Value\ArrayValueInterface::val() 48 | */ 49 | public function val() 50 | { 51 | $sql = 'SELECT count(*) as cnt FROM ('.$this->queryResult->toSql().') tmp'; 52 | 53 | return $this->connection->fetchOne($sql); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/QueryResult.php: -------------------------------------------------------------------------------- 1 | |array|ArrayValueInterface 44 | */ 45 | private $parameters = array(); 46 | 47 | /** @var int|null */ 48 | private $offset; 49 | /** @var int|null */ 50 | private $limit; 51 | 52 | /** 53 | * @param Select $select 54 | * @param Connection $connection 55 | */ 56 | public function __construct(Select $select, Connection $connection) 57 | { 58 | $this->select = $select; 59 | $this->connection = $connection; 60 | } 61 | 62 | /** 63 | * The list of parameters to apply to the SQL request. 64 | * 65 | * @param array|array|ArrayValueInterface $parameters 66 | */ 67 | public function setParameters($parameters): void 68 | { 69 | $this->parameters = $parameters; 70 | } 71 | 72 | /** 73 | * (non-PHPdoc). 74 | * 75 | * @see \Mouf\Utils\Value\ArrayValueInterface::val() 76 | */ 77 | public function val() 78 | { 79 | $parameters = ValueUtils::val($this->parameters); 80 | $pdoStatement = $this->connection->prepare($this->select->toSql($parameters, $this->connection->getDatabasePlatform()).DbHelper::getFromLimitString($this->offset, $this->limit)); 81 | 82 | return new ResultSet($pdoStatement->getWrappedStatement()); 83 | } 84 | 85 | /** 86 | * Returns the SQL for this query-result (without pagination, but with parameters accounted for). 87 | * 88 | * @return string|null 89 | */ 90 | public function toSql() 91 | { 92 | $parameters = ValueUtils::val($this->parameters); 93 | 94 | return $this->select->toSql($parameters, $this->connection->getDatabasePlatform()); 95 | } 96 | 97 | /** 98 | * Paginates the result set. 99 | * 100 | * @param int $limit 101 | * @param int $offset 102 | */ 103 | public function paginate($limit, $offset = 0): void 104 | { 105 | $this->limit = $limit; 106 | $this->offset = $offset; 107 | } 108 | 109 | /* (non-PHPdoc) 110 | * @see \Mouf\Utils\Common\SortableInterface::sort() 111 | */ 112 | public function sort($key, $direction = SortableInterface::ASC): void 113 | { 114 | $result = $this->findColumnByKey($key); 115 | if ($result != null) { 116 | $columnObj = clone($result); 117 | if (method_exists($columnObj, 'setAlias')) { 118 | $columnObj->setAlias(null); 119 | } 120 | if (method_exists($columnObj, 'setDirection')) { 121 | $columnObj->setDirection($direction); 122 | } 123 | } else { 124 | $columnObj = new ColRef(); 125 | $columnObj->setColumn($key); 126 | $columnObj->setDirection($direction); 127 | } 128 | $this->select->setOrder(array($columnObj)); 129 | } 130 | 131 | /** 132 | * Returns the object representing a column from the key passed in parameter. 133 | * It will first scan the column aliases to find if an alias match the key, then the column names, etc... 134 | * It will throw an exception if the column cannot be found. 135 | * 136 | * @param string $key 137 | * 138 | * @return NodeInterface|null 139 | */ 140 | private function findColumnByKey($key): ?NodeInterface 141 | { 142 | $columns = $this->select->getColumns(); 143 | foreach ($columns as $column) { 144 | if (method_exists($column, 'getAlias')) { 145 | $alias = $column->getAlias(); 146 | if ($alias === $key) { 147 | return $column; 148 | } 149 | } 150 | if ($column instanceof ColRef) { 151 | if ($column->getColumn() === $key) { 152 | return $column; 153 | } 154 | } 155 | } 156 | 157 | return null; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/ResultSet.php: -------------------------------------------------------------------------------- 1 | statement = $statement; 28 | } 29 | 30 | public function rewind(): void 31 | { 32 | ++$this->rewindCalls; 33 | if ($this->rewindCalls == 2) { 34 | throw new \Exception("Error: rewind is not possible in a database rowset. You can call 'foreach' on the rowset only once. Use CachedResultSet to be able to call the result several times. TODO: develop CachedResultSet"); 35 | } 36 | } 37 | 38 | public function current(): array 39 | { 40 | if (!$this->fetched) { 41 | $this->fetch(); 42 | } 43 | 44 | return $this->result; 45 | } 46 | 47 | public function key(): int 48 | { 49 | return $this->key; 50 | } 51 | 52 | public function next(): void 53 | { 54 | ++$this->key; 55 | $this->fetched = false; 56 | $this->fetch(); 57 | } 58 | 59 | private function fetch(): void 60 | { 61 | $this->result = $this->statement->execute()->fetchAllAssociative(); 62 | $this->fetched = true; 63 | } 64 | 65 | public function valid(): bool 66 | { 67 | if (!$this->fetched) { 68 | $this->fetch(); 69 | } 70 | 71 | return $this->result !== false; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/Utils/DbHelper.php: -------------------------------------------------------------------------------- 1 | get('dbalConnection'); 14 | $sql .= self::getFromLimitString($offset, $limit); 15 | $statement = $dbalConnection->executeQuery($sql); 16 | $results = $statement->fetchAll(); 17 | 18 | $array = []; 19 | 20 | foreach ($results as $result) { 21 | $array[] = $result; 22 | } 23 | 24 | return $array; 25 | } 26 | 27 | public static function getFromLimitString(?int $from = null, ?int $limit = null): string 28 | { 29 | if ($limit !== null) { 30 | $queryStr = ' LIMIT '.$limit; 31 | 32 | if ($from !== null) { 33 | $queryStr .= ' OFFSET '.$from; 34 | } 35 | 36 | return $queryStr; 37 | } else { 38 | return ''; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Mouf/Database/QueryWriter/Utils/FindParametersService.php: -------------------------------------------------------------------------------- 1 | $visitedInstances The list of names of visited instances. 27 | */ 28 | private static function recursiveFindParameters(MoufInstanceDescriptor $instanceDescriptor, array $visitedInstances = array(), array $foundParameters = array()): array 29 | { 30 | if (isset($visitedInstances[$instanceDescriptor->getIdentifierName()])) { 31 | return array($visitedInstances, $foundParameters); 32 | } 33 | $visitedInstances[$instanceDescriptor->getIdentifierName()] = true; 34 | 35 | if ($instanceDescriptor->getClassDescriptor()->getName() == 'SQLParser\\Node\\Parameter') { 36 | $foundParameters[$instanceDescriptor->getProperty('name')->getValue()] = true; 37 | 38 | return array($visitedInstances, $foundParameters); 39 | } 40 | if ($instanceDescriptor->getClassDescriptor()->getName() == 'Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition' 41 | || $instanceDescriptor->getClassDescriptor()->getName() == 'Mouf\\Database\\QueryWriter\\Condition\\ParamEqualsCondition' 42 | || $instanceDescriptor->getClassDescriptor()->getName() == 'Mouf\\Database\\QueryWriter\\Condition\\ParamNotAvailableCondition') { 43 | $foundParameters[$instanceDescriptor->getProperty('parameterName')->getValue()] = true; 44 | 45 | return array($visitedInstances, $foundParameters); 46 | } 47 | 48 | $classDescriptor = $instanceDescriptor->getClassDescriptor(); 49 | /* @var $classDescriptor MoufReflectionClass */ 50 | foreach ($classDescriptor->getInjectablePropertiesByConstructor() as $moufPropertyDescriptor) { 51 | /* @var $moufPropertyDescriptor MoufPropertyDescriptor */ 52 | $name = $moufPropertyDescriptor->getName(); 53 | 54 | $value = $instanceDescriptor->getConstructorArgumentProperty($name)->getValue(); 55 | if ($value instanceof MoufInstanceDescriptor) { 56 | list($visitedInstances, $foundParameters) = self::recursiveFindParameters($value, $visitedInstances, $foundParameters); 57 | } elseif (is_array($value)) { 58 | foreach ($value as $val) { 59 | if ($val instanceof MoufInstanceDescriptor) { 60 | list($visitedInstances, $foundParameters) = self::recursiveFindParameters($val, $visitedInstances, $foundParameters); 61 | } 62 | } 63 | } 64 | } 65 | 66 | foreach ($classDescriptor->getInjectablePropertiesBySetter() as $moufPropertyDescriptor) { 67 | /* @var $moufPropertyDescriptor MoufPropertyDescriptor */ 68 | $name = $moufPropertyDescriptor->getName(); 69 | 70 | $value = $instanceDescriptor->getSetterProperty($name)->getValue(); 71 | if ($value instanceof MoufInstanceDescriptor) { 72 | list($visitedInstances, $foundParameters) = self::recursiveFindParameters($value, $visitedInstances, $foundParameters); 73 | } elseif (is_array($value)) { 74 | foreach ($value as $val) { 75 | if ($val instanceof MoufInstanceDescriptor) { 76 | list($visitedInstances, $foundParameters) = self::recursiveFindParameters($val, $visitedInstances, $foundParameters); 77 | } 78 | } 79 | } 80 | } 81 | 82 | foreach ($classDescriptor->getInjectablePropertiesByPublicProperty() as $moufPropertyDescriptor) { 83 | /* @var $moufPropertyDescriptor MoufPropertyDescriptor */ 84 | $name = $moufPropertyDescriptor->getName(); 85 | 86 | $value = $instanceDescriptor->getInjectablePropertiesByPublicProperty($name)->getValue(); 87 | if ($value instanceof MoufInstanceDescriptor) { 88 | list($visitedInstances, $foundParameters) = self::recursiveFindParameters($value, $visitedInstances, $foundParameters); 89 | } elseif (is_array($value)) { 90 | foreach ($value as $val) { 91 | if ($val instanceof MoufInstanceDescriptor) { 92 | list($visitedInstances, $foundParameters) = self::recursiveFindParameters($val, $visitedInstances, $foundParameters); 93 | } 94 | } 95 | } 96 | } 97 | 98 | return array($visitedInstances, $foundParameters); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/SQLParser/Node/AbstractInListOperator.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | abstract class AbstractInListOperator extends AbstractTwoOperandsOperator 14 | { 15 | protected function getSql(array $parameters, AbstractPlatform $platform, int $indent = 0, int $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): string 16 | { 17 | $rightOperand = $this->getRightOperand(); 18 | 19 | $rightOperand = $this->refactorParameterToExpression($rightOperand); 20 | 21 | $this->setRightOperand($rightOperand); 22 | 23 | $parameterNode = $this->getParameter($rightOperand); 24 | 25 | if ($parameterNode !== null) { 26 | if (!isset($parameters[$parameterNode->getName()])) { 27 | throw new MagicQueryException("Missing parameter '" . $parameterNode->getName() . "' for 'IN' operand."); 28 | } 29 | if ($parameters[$parameterNode->getName()] === []) { 30 | return "0 <> 0"; 31 | } 32 | } 33 | 34 | return parent::getSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); 35 | } 36 | 37 | protected function refactorParameterToExpression(NodeInterface $rightOperand): NodeInterface 38 | { 39 | if ($rightOperand instanceof Parameter) { 40 | $expression = new Expression(); 41 | $expression->setSubTree([$rightOperand]); 42 | $expression->setBrackets(true); 43 | return $expression; 44 | } 45 | return $rightOperand; 46 | } 47 | 48 | protected function getParameter(NodeInterface $operand): ?Parameter 49 | { 50 | if (!$operand instanceof Expression) { 51 | return null; 52 | } 53 | $subtree = $operand->getSubTree(); 54 | if (!isset($subtree[0])) { 55 | return null; 56 | } 57 | if ($subtree[0] instanceof Parameter) { 58 | return $subtree[0]; 59 | } 60 | return null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SQLParser/Node/AbstractManyInstancesOperator.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | abstract class AbstractManyInstancesOperator implements NodeInterface 17 | { 18 | /** @var array */ 19 | private $operands; 20 | 21 | public function getOperands(): array 22 | { 23 | return $this->operands; 24 | } 25 | 26 | /** 27 | * Sets the operands. 28 | * 29 | * @Important 30 | * //@param array> $operands 31 | * 32 | * @param array $operands 33 | */ 34 | public function setOperands($operands): void 35 | { 36 | if (!is_array($operands)) { 37 | $operands = array($operands); 38 | } 39 | $this->operands = $operands; 40 | } 41 | 42 | /** 43 | * Returns a Mouf instance descriptor describing this object. 44 | * 45 | * @param MoufManager $moufManager 46 | * 47 | * @return MoufInstanceDescriptor 48 | */ 49 | public function toInstanceDescriptor(MoufManager $moufManager) 50 | { 51 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 52 | $instanceDescriptor->getProperty('operands')->setValue(NodeFactory::nodeToInstanceDescriptor($this->operands, $moufManager)); 53 | 54 | return $instanceDescriptor; 55 | } 56 | 57 | /** 58 | * Renders the object as a SQL string. 59 | * 60 | * @param array $parameters 61 | * @param AbstractPlatform $platform 62 | * @param int $indent 63 | * @param int $conditionsMode 64 | * 65 | * @param bool $extrapolateParameters 66 | * @return string 67 | */ 68 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 69 | { 70 | $sqlOperands = array(); 71 | foreach ($this->operands as $operand) { 72 | $sql = NodeFactory::toSql($operand, $platform, $parameters, ' ', true, $indent, $conditionsMode, $extrapolateParameters); 73 | if ($sql != null) { 74 | $sqlOperands[] = $sql; 75 | } 76 | } 77 | 78 | return implode("\n".str_repeat(' ', $indent).$this->getOperatorSymbol().' ', $sqlOperands); 79 | } 80 | 81 | /** 82 | * Walks the tree of nodes, calling the visitor passed in parameter. 83 | * 84 | * @param VisitorInterface $visitor 85 | */ 86 | public function walk(VisitorInterface $visitor) 87 | { 88 | $node = $this; 89 | $result = $visitor->enterNode($node); 90 | if ($result instanceof NodeInterface) { 91 | $node = $result; 92 | } 93 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 94 | foreach ($this->operands as $key => $operand) { 95 | $result2 = $operand->walk($visitor); 96 | if ($result2 === NodeTraverser::REMOVE_NODE) { 97 | unset($this->operands[$key]); 98 | } elseif ($result2 instanceof NodeInterface) { 99 | $this->operands[$key] = $result2; 100 | } 101 | } 102 | } 103 | 104 | return $visitor->leaveNode($node); 105 | } 106 | 107 | /** 108 | * Returns the symbol for this operator. 109 | * 110 | * @return string 111 | */ 112 | abstract protected function getOperatorSymbol(): string; 113 | } 114 | -------------------------------------------------------------------------------- /src/SQLParser/Node/AbstractTwoOperandsOperator.php: -------------------------------------------------------------------------------- 1 | , etc...) in an SQL expression. 14 | * 15 | * @author David Négrier 16 | */ 17 | abstract class AbstractTwoOperandsOperator implements NodeInterface 18 | { 19 | use ConditionTrait; 20 | 21 | /** @var NodeInterface|NodeInterface[]|string */ 22 | private $leftOperand; 23 | 24 | /** 25 | * @return NodeInterface|NodeInterface[]|string 26 | */ 27 | public function getLeftOperand() 28 | { 29 | return $this->leftOperand; 30 | } 31 | 32 | /** 33 | * Sets the leftOperand. 34 | * 35 | * @Important 36 | * 37 | * @param NodeInterface|NodeInterface[]|string $leftOperand 38 | */ 39 | public function setLeftOperand($leftOperand) 40 | { 41 | $this->leftOperand = $leftOperand; 42 | } 43 | 44 | /** @var NodeInterface|NodeInterface[]|string */ 45 | private $rightOperand; 46 | 47 | /** 48 | * @return NodeInterface|NodeInterface[]|string 49 | */ 50 | public function getRightOperand() 51 | { 52 | return $this->rightOperand; 53 | } 54 | 55 | /** 56 | * Sets the rightOperand. 57 | * 58 | * @Important 59 | * 60 | * @param string|NodeInterface|NodeInterface[] $rightOperand 61 | */ 62 | public function setRightOperand($rightOperand) 63 | { 64 | $this->rightOperand = $rightOperand; 65 | } 66 | 67 | /** 68 | * Returns a Mouf instance descriptor describing this object. 69 | * 70 | * @param MoufManager $moufManager 71 | * 72 | * @return MoufInstanceDescriptor 73 | */ 74 | public function toInstanceDescriptor(MoufManager $moufManager) 75 | { 76 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 77 | $instanceDescriptor->getProperty('leftOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->leftOperand, $moufManager)); 78 | $instanceDescriptor->getProperty('rightOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->rightOperand, $moufManager)); 79 | 80 | if ($this->leftOperand instanceof Parameter) { 81 | // Let's add a condition on the parameter. 82 | $conditionDescriptor = $moufManager->createInstance('Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition'); 83 | $conditionDescriptor->getProperty('parameterName')->setValue($this->leftOperand->getName()); 84 | $instanceDescriptor->getProperty('condition')->setValue($conditionDescriptor); 85 | } 86 | // TODO: manage cases where both leftOperand and rightOperand are parameters. 87 | if ($this->rightOperand instanceof Parameter) { 88 | // Let's add a condition on the parameter. 89 | $conditionDescriptor = $moufManager->createInstance('Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition'); 90 | $conditionDescriptor->getProperty('parameterName')->setValue($this->rightOperand->getName()); 91 | $instanceDescriptor->getProperty('condition')->setValue($conditionDescriptor); 92 | } 93 | 94 | return $instanceDescriptor; 95 | } 96 | 97 | /** 98 | * Renders the object as a SQL string. 99 | * 100 | * @param array $parameters 101 | * @param AbstractPlatform $platform 102 | * @param int $indent 103 | * @param int $conditionsMode 104 | * 105 | * @param bool $extrapolateParameters 106 | * @return string 107 | */ 108 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 109 | { 110 | if ($conditionsMode == self::CONDITION_GUESS) { 111 | $bypass = false; 112 | if ($this->leftOperand instanceof BypassableInterface && $this->leftOperand->canBeBypassed($parameters)) { 113 | $bypass = true; 114 | } 115 | if ($this->rightOperand instanceof BypassableInterface && $this->rightOperand->canBeBypassed($parameters)) { 116 | $bypass = true; 117 | } 118 | if ($bypass === true) { 119 | return null; 120 | } else { 121 | $conditionsMode = self::CONDITION_IGNORE; 122 | } 123 | } 124 | if ($conditionsMode == self::CONDITION_IGNORE || !$this->condition || $this->condition->isOk($parameters)) { 125 | $sql = $this->getSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); 126 | } else { 127 | $sql = null; 128 | } 129 | 130 | return $sql; 131 | } 132 | 133 | protected function getSql(array $parameters, AbstractPlatform $platform, int $indent = 0, int $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): string 134 | { 135 | $sql = NodeFactory::toSql($this->leftOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 136 | $sql .= ' '.$this->getOperatorSymbol().' '; 137 | $sql .= NodeFactory::toSql($this->rightOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 138 | 139 | return $sql; 140 | } 141 | 142 | /** 143 | * Walks the tree of nodes, calling the visitor passed in parameter. 144 | * 145 | * @param VisitorInterface $visitor 146 | */ 147 | public function walk(VisitorInterface $visitor) 148 | { 149 | $node = $this; 150 | $result = $visitor->enterNode($node); 151 | if ($result instanceof NodeInterface) { 152 | $node = $result; 153 | } 154 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 155 | $result2 = $this->leftOperand->walk($visitor); 156 | if ($result2 === NodeTraverser::REMOVE_NODE) { 157 | return NodeTraverser::REMOVE_NODE; 158 | } elseif ($result2 instanceof NodeInterface) { 159 | $this->leftOperand = $result2; 160 | } 161 | 162 | $result2 = $this->rightOperand->walk($visitor); 163 | if ($result2 === NodeTraverser::REMOVE_NODE) { 164 | return NodeTraverser::REMOVE_NODE; 165 | } elseif ($result2 instanceof NodeInterface) { 166 | $this->rightOperand = $result2; 167 | } 168 | } 169 | 170 | return $visitor->leaveNode($node); 171 | } 172 | 173 | /** 174 | * Returns the symbol for this operator. 175 | */ 176 | abstract protected function getOperatorSymbol(): string; 177 | } 178 | -------------------------------------------------------------------------------- /src/SQLParser/Node/AggregateFunction.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufManager; 37 | use Mouf\MoufInstanceDescriptor; 38 | use SQLParser\Node\Traverser\NodeTraverser; 39 | use SQLParser\Node\Traverser\VisitorInterface; 40 | 41 | /** 42 | * This class represents an aggregation expression (like COUNT, SUM...) that is an SQL expression. 43 | * 44 | * @author David Négrier 45 | */ 46 | class AggregateFunction implements NodeInterface 47 | { 48 | /** @var string */ 49 | private $functionName; 50 | 51 | /** 52 | * Returns the base expression (the string that generated this expression). 53 | * 54 | * @return string 55 | */ 56 | public function getFunctionName() 57 | { 58 | return $this->functionName; 59 | } 60 | 61 | /** 62 | * Sets the base expression (the string that generated this expression). 63 | * 64 | * @Important 65 | * 66 | * @param string $functionName 67 | */ 68 | public function setFunctionName($functionName): void 69 | { 70 | $this->functionName = $functionName; 71 | } 72 | 73 | /** @var NodeInterface[] */ 74 | private $subTree; 75 | 76 | /** 77 | * @return NodeInterface[] 78 | */ 79 | public function getSubTree() 80 | { 81 | return $this->subTree; 82 | } 83 | 84 | /** 85 | * Sets the subtree. 86 | * 87 | * @Important 88 | * 89 | * @param array $subTree 90 | */ 91 | public function setSubTree($subTree): void 92 | { 93 | $this->subTree = $subTree; 94 | } 95 | 96 | /** @var string */ 97 | private $alias; 98 | 99 | /** 100 | * @return string 101 | */ 102 | public function getAlias() 103 | { 104 | return $this->alias; 105 | } 106 | 107 | /** 108 | * Sets the alias. 109 | * 110 | * @param string|array $alias 111 | */ 112 | public function setAlias($alias): void 113 | { 114 | $this->alias = is_array($alias) ? $alias['name'] : $alias; 115 | } 116 | 117 | /** @var string */ 118 | private $direction; 119 | 120 | /** 121 | * @return string 122 | */ 123 | public function getDirection() 124 | { 125 | return $this->direction; 126 | } 127 | 128 | /** 129 | * Sets the direction. 130 | * 131 | * @Important 132 | * 133 | * @param string $direction 134 | */ 135 | public function setDirection($direction): void 136 | { 137 | $this->direction = $direction; 138 | } 139 | 140 | /** 141 | * Returns a Mouf instance descriptor describing this object. 142 | * 143 | * @param MoufManager $moufManager 144 | * 145 | * @return MoufInstanceDescriptor 146 | */ 147 | public function toInstanceDescriptor(MoufManager $moufManager) 148 | { 149 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 150 | $instanceDescriptor->getProperty('functionName')->setValue($this->functionName, $moufManager); 151 | $instanceDescriptor->getProperty('subTree')->setValue(NodeFactory::nodeToInstanceDescriptor($this->subTree, $moufManager)); 152 | $instanceDescriptor->getProperty('alias')->setValue($this->alias, $moufManager); 153 | 154 | return $instanceDescriptor; 155 | } 156 | 157 | /** 158 | * Renders the object as a SQL string. 159 | * 160 | * @param array $parameters 161 | * @param AbstractPlatform $platform 162 | * @param int $indent 163 | * @param int $conditionsMode 164 | * 165 | * @param bool $extrapolateParameters 166 | * @return string 167 | */ 168 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 169 | { 170 | $subTreeSql = NodeFactory::toSql($this->subTree, $platform, $parameters, ', ', false, $indent, $conditionsMode, $extrapolateParameters); 171 | if ($subTreeSql !== null) { 172 | $sql = $this->functionName.'('; 173 | $sql .= $subTreeSql; 174 | $sql .= ')'; 175 | if ($this->alias) { 176 | $sql .= ' AS '.$this->alias; 177 | } 178 | if ($this->direction) { 179 | $sql .= ' '.$this->direction; 180 | } 181 | } else { 182 | $sql = null; 183 | } 184 | 185 | return $sql; 186 | } 187 | 188 | /** 189 | * Walks the tree of nodes, calling the visitor passed in parameter. 190 | * 191 | * @param VisitorInterface $visitor 192 | */ 193 | public function walk(VisitorInterface $visitor) 194 | { 195 | $node = $this; 196 | $result = $visitor->enterNode($node); 197 | if ($result instanceof NodeInterface) { 198 | $node = $result; 199 | } 200 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 201 | foreach ($this->subTree as $key => $operand) { 202 | $result2 = $operand->walk($visitor); 203 | if ($result2 === NodeTraverser::REMOVE_NODE) { 204 | unset($this->subTree[$key]); 205 | } elseif ($result2 instanceof NodeInterface) { 206 | $this->subTree[$key] = $result2; 207 | } 208 | } 209 | } 210 | 211 | return $visitor->leaveNode($node); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/SQLParser/Node/AndOp.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | /** 36 | * This class represents an AND in an SQL expression. 37 | * 38 | * @author David Négrier 39 | */ 40 | class AndOp extends AbstractManyInstancesOperator 41 | { 42 | /** 43 | * Returns the symbol for this operator. 44 | * 45 | * @return string 46 | */ 47 | protected function getOperatorSymbol(): string 48 | { 49 | return 'AND'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Between.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Between implements NodeInterface 19 | { 20 | /** @var NodeInterface|NodeInterface[]|string */ 21 | private $leftOperand; 22 | 23 | /** 24 | * @return NodeInterface|NodeInterface[]|string 25 | */ 26 | public function getLeftOperand() 27 | { 28 | return $this->leftOperand; 29 | } 30 | 31 | /** 32 | * Sets the leftOperand. 33 | * 34 | * @Important 35 | * 36 | * @param NodeInterface|NodeInterface[]|string $leftOperand 37 | */ 38 | public function setLeftOperand($leftOperand) 39 | { 40 | $this->leftOperand = $leftOperand; 41 | } 42 | 43 | /** 44 | * @var NodeInterface 45 | */ 46 | private $minValueOperand; 47 | 48 | /** 49 | * @var NodeInterface 50 | */ 51 | private $maxValueOperand; 52 | 53 | /** 54 | * @return NodeInterface 55 | */ 56 | public function getMinValueOperand() 57 | { 58 | return $this->minValueOperand; 59 | } 60 | 61 | /** 62 | * @param NodeInterface $minValueOperand 63 | */ 64 | public function setMinValueOperand(NodeInterface $minValueOperand) 65 | { 66 | $this->minValueOperand = $minValueOperand; 67 | } 68 | 69 | /** 70 | * @return NodeInterface 71 | */ 72 | public function getMaxValueOperand() 73 | { 74 | return $this->maxValueOperand; 75 | } 76 | 77 | /** 78 | * @param NodeInterface $maxValueOperand 79 | */ 80 | public function setMaxValueOperand(NodeInterface $maxValueOperand): void 81 | { 82 | $this->maxValueOperand = $maxValueOperand; 83 | } 84 | 85 | /** 86 | * @var ConditionInterface|null 87 | */ 88 | protected $minValueCondition; 89 | 90 | /** 91 | * Sets the condition. 92 | * 93 | * @Important IfSet 94 | * 95 | * @param ConditionInterface|null $minValueCondition 96 | */ 97 | public function setMinValueCondition(ConditionInterface $minValueCondition = null): void 98 | { 99 | $this->minValueCondition = $minValueCondition; 100 | } 101 | 102 | /** 103 | * @var ConditionInterface|null 104 | */ 105 | protected $maxValueCondition; 106 | 107 | /** 108 | * Sets the condition. 109 | * 110 | * @Important IfSet 111 | * 112 | * @param ConditionInterface|null $maxValueCondition 113 | */ 114 | public function setMaxValueCondition(ConditionInterface $maxValueCondition = null): void 115 | { 116 | $this->maxValueCondition = $maxValueCondition; 117 | } 118 | 119 | /** 120 | * Returns a Mouf instance descriptor describing this object. 121 | * 122 | * @param MoufManager $moufManager 123 | * 124 | * @return MoufInstanceDescriptor 125 | */ 126 | public function toInstanceDescriptor(MoufManager $moufManager) 127 | { 128 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 129 | $instanceDescriptor->getProperty('leftOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->leftOperand, $moufManager)); 130 | $instanceDescriptor->getProperty('minValueOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->minValueOperand, $moufManager)); 131 | $instanceDescriptor->getProperty('maxValueOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->maxValueOperand, $moufManager)); 132 | 133 | if ($this->minValueOperand instanceof Parameter) { 134 | // Let's add a condition on the parameter. 135 | $conditionDescriptor = $moufManager->createInstance('Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition'); 136 | $conditionDescriptor->getProperty('parameterName')->setValue($this->minValueOperand->getName()); 137 | $instanceDescriptor->getProperty('minValueCondition')->setValue($conditionDescriptor); 138 | } 139 | 140 | if ($this->maxValueOperand instanceof Parameter) { 141 | // Let's add a condition on the parameter. 142 | $conditionDescriptor = $moufManager->createInstance('Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition'); 143 | $conditionDescriptor->getProperty('parameterName')->setValue($this->maxValueOperand->getName()); 144 | $instanceDescriptor->getProperty('maxValueCondition')->setValue($conditionDescriptor); 145 | } 146 | 147 | return $instanceDescriptor; 148 | } 149 | 150 | /** 151 | * Renders the object as an SQL string. 152 | * 153 | * @param array $parameters 154 | * @param AbstractPlatform $platform 155 | * @param int $indent 156 | * @param int $conditionsMode 157 | * 158 | * @param bool $extrapolateParameters 159 | * @return string 160 | */ 161 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 162 | { 163 | switch ($conditionsMode) { 164 | case self::CONDITION_APPLY: 165 | $minBypass = $this->minValueCondition && !$this->minValueCondition->isOk($parameters); 166 | $maxBypass = $this->maxValueCondition && !$this->maxValueCondition->isOk($parameters); 167 | break; 168 | case self::CONDITION_GUESS: 169 | $minBypass = $this->minValueOperand instanceof Parameter && $this->minValueOperand->isDiscardedOnNull() && !isset($parameters[$this->minValueOperand->getName()]); 170 | $maxBypass = $this->maxValueOperand instanceof Parameter && $this->maxValueOperand->isDiscardedOnNull() && !isset($parameters[$this->maxValueOperand->getName()]); 171 | break; 172 | case self::CONDITION_IGNORE: 173 | $minBypass = false; 174 | $maxBypass = false; 175 | break; 176 | default: 177 | throw new \InvalidArgumentException('Invalid `$conditionsMode`: "' . $conditionsMode. '"'); 178 | } 179 | 180 | if ($maxBypass && $minBypass) { 181 | return null; 182 | } 183 | 184 | if ($minBypass) { 185 | return sprintf('%s <= %s', 186 | NodeFactory::toSql($this->leftOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters), 187 | NodeFactory::toSql($this->maxValueOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters) 188 | ); 189 | } 190 | 191 | if ($maxBypass) { 192 | return sprintf('%s >= %s', 193 | NodeFactory::toSql($this->leftOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters), 194 | NodeFactory::toSql($this->minValueOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters)); 195 | } 196 | 197 | return sprintf('%s BETWEEN %s AND %s', 198 | NodeFactory::toSql($this->leftOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters), 199 | NodeFactory::toSql($this->minValueOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters), 200 | NodeFactory::toSql($this->maxValueOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters) 201 | ); 202 | } 203 | 204 | /** 205 | * Walks the tree of nodes, calling the visitor passed in parameter. 206 | * 207 | * @param VisitorInterface $visitor 208 | */ 209 | public function walk(VisitorInterface $visitor) 210 | { 211 | $node = $this; 212 | $result = $visitor->enterNode($node); 213 | if ($result instanceof NodeInterface) { 214 | $node = $result; 215 | } 216 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 217 | $result2 = $this->leftOperand->walk($visitor); 218 | if ($result2 === NodeTraverser::REMOVE_NODE) { 219 | return NodeTraverser::REMOVE_NODE; 220 | } elseif ($result2 instanceof NodeInterface) { 221 | $this->leftOperand = $result2; 222 | } 223 | 224 | $result2 = $this->minValueOperand->walk($visitor); 225 | if ($result2 === NodeTraverser::REMOVE_NODE) { 226 | return NodeTraverser::REMOVE_NODE; 227 | } elseif ($result2 instanceof NodeInterface) { 228 | $this->minValueOperand = $result2; 229 | } 230 | 231 | $result2 = $this->maxValueOperand->walk($visitor); 232 | if ($result2 === NodeTraverser::REMOVE_NODE) { 233 | return NodeTraverser::REMOVE_NODE; 234 | } elseif ($result2 instanceof NodeInterface) { 235 | $this->maxValueOperand = $result2; 236 | } 237 | } 238 | 239 | return $visitor->leaveNode($node); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/SQLParser/Node/BitwiseAnd.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class BitwiseAnd extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '&'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/BitwiseOr.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class BitwiseOr extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '|'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/BitwiseXor.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class BitwiseXor extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '^'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/BypassableInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class CaseOperation implements NodeInterface 17 | { 18 | /** @var NodeInterface|NodeInterface[]|string */ 19 | private $operation; 20 | 21 | /** 22 | * @return NodeInterface|NodeInterface[]|string 23 | */ 24 | public function getOperation() 25 | { 26 | return $this->operation; 27 | } 28 | 29 | /** 30 | * Sets the operation. 31 | * 32 | * @Important 33 | * 34 | * @param NodeInterface|NodeInterface[]|string $operation 35 | */ 36 | public function setOperation($operation): void 37 | { 38 | $this->operation = $operation; 39 | } 40 | 41 | /** 42 | * Returns a Mouf instance descriptor describing this object. 43 | * 44 | * @param MoufManager $moufManager 45 | * 46 | * @return MoufInstanceDescriptor 47 | */ 48 | public function toInstanceDescriptor(MoufManager $moufManager) 49 | { 50 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 51 | $instanceDescriptor->getProperty('operation')->setValue(NodeFactory::nodeToInstanceDescriptor($this->operation, $moufManager)); 52 | 53 | return $instanceDescriptor; 54 | } 55 | 56 | /** 57 | * Renders the object as a SQL string. 58 | * 59 | * @param array $parameters 60 | * @param AbstractPlatform $platform 61 | * @param int $indent 62 | * @param int $conditionsMode 63 | * 64 | * @param bool $extrapolateParameters 65 | * @return string 66 | */ 67 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 68 | { 69 | $sql = 'CASE '.NodeFactory::toSql($this->operation, $platform, $parameters, ' ', false, $indent, $conditionsMode).' END'; 70 | 71 | return $sql; 72 | } 73 | 74 | /** 75 | * Walks the tree of nodes, calling the visitor passed in parameter. 76 | * 77 | * @param VisitorInterface $visitor 78 | */ 79 | public function walk(VisitorInterface $visitor) 80 | { 81 | $node = $this; 82 | $result = $visitor->enterNode($node); 83 | if ($result instanceof NodeInterface) { 84 | $node = $result; 85 | } 86 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 87 | $result2 = $this->operation->walk($visitor); 88 | if ($result2 === NodeTraverser::REMOVE_NODE) { 89 | return NodeTraverser::REMOVE_NODE; 90 | } elseif ($result2 instanceof NodeInterface) { 91 | $this->operation = $result2; 92 | } 93 | } 94 | 95 | return $visitor->leaveNode($node); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/SQLParser/Node/ColRef.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufManager; 37 | use Mouf\MoufInstanceDescriptor; 38 | use SQLParser\Node\Traverser\VisitorInterface; 39 | 40 | /** 41 | * This class represents an column in an SQL expression. 42 | * 43 | * @author David Négrier 44 | */ 45 | class ColRef implements NodeInterface 46 | { 47 | /** @var mixed */ 48 | private $database; 49 | 50 | /** 51 | * Returns the name of the database. 52 | * 53 | * @return mixed 54 | */ 55 | public function getDatabase() 56 | { 57 | return $this->database; 58 | } 59 | 60 | /** 61 | * Sets the name of the database 62 | * 63 | * @param mixed $database 64 | */ 65 | public function setDatabase($database): void 66 | { 67 | $this->database = $database; 68 | } 69 | 70 | /** @var string */ 71 | private $table; 72 | 73 | /** 74 | * Returns the table name. 75 | * 76 | * @return string 77 | */ 78 | public function getTable() 79 | { 80 | return $this->table; 81 | } 82 | 83 | /** 84 | * Sets the table name. 85 | * 86 | * @Important 87 | * 88 | * @param string $table 89 | */ 90 | public function setTable($table): void 91 | { 92 | $this->table = $table; 93 | } 94 | 95 | /** @var string */ 96 | private $column; 97 | 98 | /** 99 | * Returns the column name. 100 | * 101 | * @return string 102 | */ 103 | public function getColumn() 104 | { 105 | return $this->column; 106 | } 107 | 108 | /** 109 | * Sets the column name. 110 | * 111 | * @Important 112 | * 113 | * @param string $column 114 | */ 115 | public function setColumn($column): void 116 | { 117 | $this->column = $column; 118 | } 119 | 120 | /** @var string */ 121 | private $alias; 122 | 123 | /** 124 | * Returns the alias. 125 | * 126 | * @return string 127 | */ 128 | public function getAlias() 129 | { 130 | return $this->alias; 131 | } 132 | 133 | /** 134 | * Sets the alias. 135 | * 136 | * @Important 137 | * 138 | * @param string $alias 139 | */ 140 | public function setAlias($alias): void 141 | { 142 | $this->alias = $alias; 143 | } 144 | 145 | /** @var string */ 146 | private $direction; 147 | 148 | /** 149 | * Returns the direction. 150 | * 151 | * @return string 152 | */ 153 | public function getDirection() 154 | { 155 | return $this->direction; 156 | } 157 | 158 | /** 159 | * Sets the direction. 160 | * 161 | * @Important 162 | * 163 | * @param string $direction 164 | */ 165 | public function setDirection($direction): void 166 | { 167 | $this->direction = $direction; 168 | } 169 | 170 | /** 171 | * Returns a Mouf instance descriptor describing this object. 172 | * 173 | * @param MoufManager $moufManager 174 | * 175 | * @return MoufInstanceDescriptor 176 | */ 177 | public function toInstanceDescriptor(MoufManager $moufManager) 178 | { 179 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 180 | $instanceDescriptor->getProperty('database')->setValue($this->database); 181 | $instanceDescriptor->getProperty('table')->setValue($this->table); 182 | $instanceDescriptor->getProperty('column')->setValue($this->column); 183 | $instanceDescriptor->getProperty('alias')->setValue($this->alias); 184 | $instanceDescriptor->getProperty('direction')->setValue($this->direction); 185 | 186 | return $instanceDescriptor; 187 | } 188 | 189 | /** 190 | * Renders the object as a SQL string. 191 | * 192 | * @param array $parameters 193 | * @param AbstractPlatform $platform 194 | * @param int $indent 195 | * @param int $conditionsMode 196 | * 197 | * @param bool $extrapolateParameters 198 | * @return string 199 | */ 200 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 201 | { 202 | $sql = ''; 203 | if ($this->database) { 204 | $sql .= $platform->quoteSingleIdentifier($this->database).'.'; 205 | } 206 | if ($this->table) { 207 | $sql .= $platform->quoteSingleIdentifier($this->table).'.'; 208 | } 209 | if ($this->column !== '*') { 210 | $sql .= $platform->quoteSingleIdentifier($this->column); 211 | } else { 212 | $sql .= '*'; 213 | } 214 | if ($this->alias) { 215 | $sql .= ' AS '.$this->alias; 216 | } 217 | if ($this->direction) { 218 | $sql .= ' '.$this->direction; 219 | } 220 | 221 | return $sql; 222 | } 223 | 224 | /** 225 | * Walks the tree of nodes, calling the visitor passed in parameter. 226 | * 227 | * @param VisitorInterface $visitor 228 | */ 229 | public function walk(VisitorInterface $visitor) 230 | { 231 | $node = $this; 232 | $result = $visitor->enterNode($node); 233 | if ($result instanceof NodeInterface) { 234 | $node = $result; 235 | } 236 | 237 | return $visitor->leaveNode($node); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/SQLParser/Node/ConstNode.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufManager; 37 | use Mouf\MoufInstanceDescriptor; 38 | use SQLParser\Node\Traverser\VisitorInterface; 39 | 40 | /** 41 | * This class represents a constant in an SQL expression. 42 | * 43 | * @author David Négrier 44 | */ 45 | class ConstNode implements NodeInterface 46 | { 47 | /** @var string */ 48 | private $value; 49 | /** @var bool */ 50 | private $isString = true; 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function getValue() 56 | { 57 | return $this->value; 58 | } 59 | 60 | /** 61 | * Sets the value. 62 | * 63 | * @Important 64 | * 65 | * @param string $value 66 | */ 67 | public function setValue($value): void 68 | { 69 | $this->value = $value; 70 | } 71 | 72 | /** 73 | * @return bool 74 | */ 75 | public function getIsString() 76 | { 77 | return $this->isString; 78 | } 79 | 80 | /** 81 | * @param bool $isString 82 | */ 83 | public function setIsString($isString): void 84 | { 85 | $this->isString = $isString; 86 | } 87 | 88 | /** 89 | * Returns a Mouf instance descriptor describing this object. 90 | * 91 | * @param MoufManager $moufManager 92 | * 93 | * @return MoufInstanceDescriptor 94 | */ 95 | public function toInstanceDescriptor(MoufManager $moufManager) 96 | { 97 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 98 | $instanceDescriptor->getProperty('value')->setValue(NodeFactory::nodeToInstanceDescriptor($this->value, $moufManager)); 99 | 100 | return $instanceDescriptor; 101 | } 102 | 103 | /** 104 | * Renders the object as a SQL string. 105 | * 106 | * @param array $parameters 107 | * @param AbstractPlatform $platform 108 | * @param int $indent 109 | * @param int $conditionsMode 110 | * 111 | * @param bool $extrapolateParameters 112 | * @return string 113 | */ 114 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 115 | { 116 | if ($this->value === null) { 117 | return 'NULL'; 118 | } elseif (!$this->isString) { 119 | return $this->value; 120 | } else { 121 | return $platform->quoteStringLiteral($this->value); 122 | } 123 | } 124 | 125 | /** 126 | * Walks the tree of nodes, calling the visitor passed in parameter. 127 | * 128 | * @param VisitorInterface $visitor 129 | * 130 | * @return NodeInterface|null|string Can return null if nothing is to be done or a node that should replace this node, or NodeTraverser::REMOVE_NODE to remove the node 131 | */ 132 | public function walk(VisitorInterface $visitor) 133 | { 134 | $node = $this; 135 | $result = $visitor->enterNode($node); 136 | if ($result instanceof NodeInterface) { 137 | $node = $result; 138 | } 139 | 140 | return $visitor->leaveNode($node); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Different.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Different extends AbstractTwoOperandsOperator 13 | { 14 | /** 15 | * Returns the symbol for this operator. 16 | * 17 | * @return string 18 | */ 19 | protected function getOperatorSymbol(): string 20 | { 21 | return '<>'; 22 | } 23 | 24 | protected function getSql(array $parameters, AbstractPlatform $platform, int $indent = 0, int $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): string 25 | { 26 | $rightOperand = $this->getRightOperand(); 27 | if ($rightOperand instanceof Parameter && !isset($parameters[$rightOperand->getName()])) { 28 | $isNull = true; 29 | } else { 30 | $isNull = false; 31 | } 32 | 33 | $sql = NodeFactory::toSql($this->getLeftOperand(), $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 34 | if ($isNull) { 35 | $sql .= ' IS NOT null'; 36 | } else { 37 | $sql .= ' '.$this->getOperatorSymbol().' '; 38 | $sql .= NodeFactory::toSql($rightOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 39 | } 40 | 41 | return $sql; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Div.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Div extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'DIV'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Divide.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Divide extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '/'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/ElseOperation.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ElseOperation extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'ELSE'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Equal.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Equal extends AbstractTwoOperandsOperator 13 | { 14 | /** 15 | * Returns the symbol for this operator. 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '='; 20 | } 21 | 22 | protected function getSql(array $parameters, AbstractPlatform $platform, int $indent = 0, int $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): string 23 | { 24 | $rightOperand = $this->getRightOperand(); 25 | if ($rightOperand instanceof Parameter && !isset($parameters[$rightOperand->getName()])) { 26 | $isNull = true; 27 | } else { 28 | $isNull = false; 29 | } 30 | 31 | $sql = NodeFactory::toSql($this->getLeftOperand(), $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 32 | if ($isNull) { 33 | $sql .= ' IS null'; 34 | } else { 35 | $sql .= ' '.$this->getOperatorSymbol().' '; 36 | $sql .= NodeFactory::toSql($rightOperand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 37 | } 38 | 39 | return $sql; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Greater.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Greater extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '>'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/GreaterOrEqual.php: -------------------------------------------------------------------------------- 1 | = operation in an SQL expression. 7 | * 8 | * @author David Négrier 9 | */ 10 | class GreaterOrEqual extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '>='; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Hint.php: -------------------------------------------------------------------------------- 1 | type = $type; 24 | $this->list = $list; 25 | } 26 | 27 | public function getType(): string 28 | { 29 | return $this->type; 30 | } 31 | 32 | public function getList(): string 33 | { 34 | return $this->list; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SQLParser/Node/In.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class In extends AbstractInListOperator 13 | { 14 | /** 15 | * Returns the symbol for this operator. 16 | * 17 | * @return string 18 | */ 19 | protected function getOperatorSymbol(): string 20 | { 21 | return 'IN'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Is.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Is extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'IS'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/IsNot.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class IsNot extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'IS NOT'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Less.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Less extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '<'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/LessOrEqual.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class LessOrEqual extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '<='; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Like.php: -------------------------------------------------------------------------------- 1 | operation in an SQL expression. 7 | * 8 | * @author David Négrier 9 | */ 10 | class Like extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'LIKE'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/LimitNode.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufManager; 37 | use Mouf\MoufInstanceDescriptor; 38 | use SQLParser\Node\Traverser\VisitorInterface; 39 | 40 | /** 41 | * This class represents a constant of a LIMIT in an SQL expression (so either a limit or an offset) 42 | * 43 | * @author David MAECHLER 44 | */ 45 | class LimitNode implements NodeInterface 46 | { 47 | /** @var string */ 48 | private $value; 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getValue() 54 | { 55 | return $this->value; 56 | } 57 | 58 | /** 59 | * Sets the value. 60 | * 61 | * @Important 62 | * 63 | * @param string $value 64 | */ 65 | public function setValue($value): void 66 | { 67 | $this->value = $value; 68 | } 69 | 70 | /** 71 | * Returns a Mouf instance descriptor describing this object. 72 | * 73 | * @param MoufManager $moufManager 74 | * 75 | * @return MoufInstanceDescriptor 76 | */ 77 | public function toInstanceDescriptor(MoufManager $moufManager) 78 | { 79 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 80 | $instanceDescriptor->getProperty('value')->setValue(NodeFactory::nodeToInstanceDescriptor($this->value, $moufManager)); 81 | 82 | return $instanceDescriptor; 83 | } 84 | 85 | /** 86 | * Renders the object as a SQL string. 87 | * 88 | * @param array $parameters 89 | * @param AbstractPlatform $platform 90 | * @param int $indent 91 | * @param int $conditionsMode 92 | * 93 | * @param bool $extrapolateParameters 94 | * @return string 95 | * 96 | * @throws \Exception 97 | */ 98 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 99 | { 100 | if ($this->value === null) { 101 | throw new \Exception('A limit parameter must be an integer'); 102 | } 103 | 104 | if (is_numeric($this->value)) { 105 | return (string) ((int) $this->value); 106 | } elseif (empty($this->value)) { 107 | return null; 108 | } else { 109 | return $platform->quoteStringLiteral($this->value); 110 | } 111 | } 112 | 113 | /** 114 | * Walks the tree of nodes, calling the visitor passed in parameter. 115 | * 116 | * @param VisitorInterface $visitor 117 | * 118 | * @return NodeInterface|null|string Can return null if nothing is to be done or a node that should replace this node, or NodeTraverser::REMOVE_NODE to remove the node 119 | */ 120 | public function walk(VisitorInterface $visitor) 121 | { 122 | $node = $this; 123 | $result = $visitor->enterNode($node); 124 | if ($result instanceof NodeInterface) { 125 | $node = $result; 126 | } 127 | 128 | return $visitor->leaveNode($node); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Minus.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Minus extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '-'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Modulo.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Modulo extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '%'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Multiply.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Multiply extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '*'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/NodeInterface.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface NodeInterface extends SqlRenderInterface 16 | { 17 | /** 18 | * Returns a Mouf instance descriptor describing this object. 19 | * 20 | * @param MoufManager $moufManager 21 | * 22 | * @return MoufInstanceDescriptor 23 | */ 24 | public function toInstanceDescriptor(MoufManager $moufManager); 25 | 26 | /** 27 | * Walks the tree of nodes, calling the visitor passed in parameter. 28 | * 29 | * @param VisitorInterface $visitor 30 | * 31 | * @return NodeInterface|null|string Can return null if nothing is to be done or a node that should replace this node, or NodeTraverser::REMOVE_NODE to remove the node 32 | */ 33 | public function walk(VisitorInterface $visitor); 34 | } 35 | -------------------------------------------------------------------------------- /src/SQLParser/Node/NotIn.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NotIn extends AbstractInListOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'NOT IN'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/NotLike.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NotLike extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'NOT LIKE'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/NullCompatibleEqual.php: -------------------------------------------------------------------------------- 1 | ' operation in an SQL expression. 7 | * 8 | * @author David Négrier 9 | */ 10 | class NullCompatibleEqual extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '<=>'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Operation.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | /** 36 | * This class represents an operation (AND, OR, ...) in an SQL expression. 37 | * 38 | * @author David Négrier 39 | */ 40 | class Operation extends AbstractManyInstancesOperator 41 | { 42 | /** @var string */ 43 | private $operator; 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getOperatorSymbol(): string 49 | { 50 | return $this->operator; 51 | } 52 | 53 | /** 54 | * Sets the operator. 55 | * 56 | * @param string $operator 57 | */ 58 | public function setOperatorSymbol($operator): void 59 | { 60 | $this->operator = $operator; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Operator.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufInstanceDescriptor; 37 | use Mouf\MoufManager; 38 | use SQLParser\Node\Traverser\VisitorInterface; 39 | 40 | /** 41 | * This class represents an operator (=, <, AND, OR, ...) in an SQL expression. 42 | * 43 | * @author David Négrier 44 | */ 45 | class Operator implements NodeInterface 46 | { 47 | private $value; 48 | 49 | public function getValue() 50 | { 51 | return $this->value; 52 | } 53 | 54 | /** 55 | * Sets the value. 56 | * 57 | * @Important 58 | * 59 | * @param string $value 60 | */ 61 | public function setValue($value) 62 | { 63 | $this->value = $value; 64 | } 65 | 66 | /** 67 | * Returns a Mouf instance descriptor describing this object. 68 | * 69 | * @param MoufManager $moufManager 70 | * 71 | * @return MoufInstanceDescriptor 72 | */ 73 | public function toInstanceDescriptor(MoufManager $moufManager) 74 | { 75 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 76 | $instanceDescriptor->getProperty('value')->setValue(NodeFactory::nodeToInstanceDescriptor($this->value, $moufManager)); 77 | 78 | return $instanceDescriptor; 79 | } 80 | 81 | /** 82 | * Renders the object as a SQL string. 83 | * 84 | * @param array $parameters 85 | * @param AbstractPlatform $platform 86 | * @param int $indent 87 | * @param int $conditionsMode 88 | * 89 | * @param bool $extrapolateParameters 90 | * @return string 91 | */ 92 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 93 | { 94 | return $this->value; 95 | } 96 | 97 | /** 98 | * Walks the tree of nodes, calling the visitor passed in parameter. 99 | * 100 | * @param VisitorInterface $visitor 101 | * 102 | * @return NodeInterface|null|string Can return null if nothing is to be done or a node that should replace this node, or NodeTraverser::REMOVE_NODE to remove the node 103 | */ 104 | public function walk(VisitorInterface $visitor) 105 | { 106 | $node = $this; 107 | $result = $visitor->enterNode($node); 108 | if ($result instanceof NodeInterface) { 109 | $node = $result; 110 | } 111 | 112 | return $visitor->leaveNode($node); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/SQLParser/Node/OrOp.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | /** 36 | * This class represents an OR in an SQL expression. 37 | * 38 | * @author David Négrier 39 | */ 40 | class OrOp extends AbstractManyInstancesOperator 41 | { 42 | /** 43 | * Returns the symbol for this operator. 44 | * 45 | * @return string 46 | */ 47 | protected function getOperatorSymbol(): string 48 | { 49 | return 'OR'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Parameter.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufInstanceDescriptor; 37 | use Mouf\MoufManager; 38 | use SQLParser\Node\Traverser\VisitorInterface; 39 | 40 | /** 41 | * This class represents a parameter (as in parameterized query). 42 | * 43 | * @author David Négrier 44 | */ 45 | class Parameter implements NodeInterface, BypassableInterface 46 | { 47 | /** @var string */ 48 | protected $name; 49 | /** @var bool */ 50 | protected $discardedOnNull = true; 51 | 52 | /** 53 | * Returns the name. 54 | * 55 | * @return string 56 | */ 57 | public function getName() 58 | { 59 | return $this->name; 60 | } 61 | 62 | /** 63 | * Sets the name. 64 | * If the name ends with !, the parameter will be considered not nullable (regarding magicparameter settings). 65 | * 66 | * @Important 67 | * 68 | * @param string $name 69 | */ 70 | public function setName($name): void 71 | { 72 | if (strrpos($name, '!') === strlen($name) - 1) { 73 | $this->name = substr($name, 0, strlen($name) - 1); 74 | $this->discardedOnNull = false; 75 | } else { 76 | $this->name = $name; 77 | $this->discardedOnNull = true; 78 | } 79 | } 80 | 81 | /** 82 | * @var string 83 | */ 84 | protected $autoPrepend; 85 | 86 | /** 87 | * @var string 88 | */ 89 | protected $autoAppend; 90 | 91 | /** 92 | * @return string 93 | */ 94 | public function getAutoPrepend() 95 | { 96 | return $this->autoPrepend; 97 | } 98 | 99 | /** 100 | * Sets a string that will automatically be appended to the parameter, if the parameter is available. 101 | * Very useful to automatically add "%" to a parameter used in a LIKE. 102 | * 103 | * @Important IfSet 104 | * 105 | * @param string $autoPrepend 106 | */ 107 | public function setAutoPrepend($autoPrepend): self 108 | { 109 | $this->autoPrepend = $autoPrepend; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getAutoAppend() 118 | { 119 | return $this->autoAppend; 120 | } 121 | 122 | /** 123 | * Sets a string that will automatically be preprended to the parameter, if the parameter is available. 124 | * Very useful to automatically add "%" to a parameter used in a LIKE. 125 | * 126 | * @Important IfSet 127 | * 128 | * @param string $autoAppend 129 | */ 130 | public function setAutoAppend($autoAppend): self 131 | { 132 | $this->autoAppend = $autoAppend; 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Returns a Mouf instance descriptor describing this object. 139 | * 140 | * @param MoufManager $moufManager 141 | * 142 | * @return MoufInstanceDescriptor 143 | */ 144 | public function toInstanceDescriptor(MoufManager $moufManager) 145 | { 146 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 147 | $instanceDescriptor->getProperty('name')->setValue($this->name); 148 | $instanceDescriptor->getProperty('autoPrepend')->setValue($this->autoPrepend); 149 | $instanceDescriptor->getProperty('autoAppend')->setValue($this->autoAppend); 150 | 151 | return $instanceDescriptor; 152 | } 153 | 154 | /** 155 | * Renders the object as a SQL string. 156 | * 157 | * @param array $parameters 158 | * @param AbstractPlatform $platform 159 | * @param int $indent 160 | * @param int $conditionsMode 161 | * 162 | * @param bool $extrapolateParameters 163 | * @return string 164 | */ 165 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 166 | { 167 | if ($extrapolateParameters && isset($parameters[$this->name])) { 168 | if ($parameters[$this->name] === null) { 169 | return 'null'; 170 | } elseif (is_array($parameters[$this->name])) { 171 | return implode(',', array_map(function ($item) use ($platform) { 172 | return $platform->quoteStringLiteral($this->autoPrepend.$item.$this->autoAppend); 173 | }, $parameters[$this->name])); 174 | } else { 175 | return $platform->quoteStringLiteral($this->autoPrepend.$parameters[$this->name].$this->autoAppend); 176 | } 177 | } elseif ($extrapolateParameters && !$this->isDiscardedOnNull()) { 178 | return 'null'; 179 | } else { 180 | return ':'.$this->name; 181 | } 182 | } 183 | 184 | /** 185 | * Walks the tree of nodes, calling the visitor passed in parameter. 186 | * 187 | * @param VisitorInterface $visitor 188 | * 189 | * @return NodeInterface|null|string Can return null if nothing is to be done or a node that should replace this node, or NodeTraverser::REMOVE_NODE to remove the node 190 | */ 191 | public function walk(VisitorInterface $visitor) 192 | { 193 | $node = $this; 194 | $result = $visitor->enterNode($node); 195 | if ($result instanceof NodeInterface) { 196 | $node = $result; 197 | } 198 | 199 | return $visitor->leaveNode($node); 200 | } 201 | 202 | /** 203 | * Returns whether the parameter can be discarded if provided value is null. 204 | * 205 | * @return bool 206 | */ 207 | public function isDiscardedOnNull() 208 | { 209 | return $this->discardedOnNull; 210 | } 211 | 212 | /** 213 | * Returns if this node should be removed from the tree. 214 | */ 215 | public function canBeBypassed(array $parameters): bool 216 | { 217 | return $this->isDiscardedOnNull() && !isset($parameters[$this->getName()]); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Plus.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Plus extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '+'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Regexp.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Regexp extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'REGEXP'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Reserved.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufInstanceDescriptor; 37 | use Mouf\MoufManager; 38 | use SQLParser\Node\Traverser\VisitorInterface; 39 | 40 | /** 41 | * This class represents a node that is an SQL expression. 42 | * 43 | * @author David Négrier 44 | */ 45 | class Reserved implements NodeInterface 46 | { 47 | /** @var string */ 48 | private $baseExpression; 49 | 50 | /** 51 | * Returns the base expression (the string that generated this expression). 52 | * 53 | * @return string 54 | */ 55 | public function getBaseExpression() 56 | { 57 | return $this->baseExpression; 58 | } 59 | 60 | /** 61 | * Sets the base expression (the string that generated this expression). 62 | * 63 | * @param string $baseExpression 64 | */ 65 | public function setBaseExpression($baseExpression): void 66 | { 67 | $this->baseExpression = $baseExpression; 68 | } 69 | 70 | /** @var bool */ 71 | private $brackets = false; 72 | 73 | /** 74 | * Returns true if the expression is between brackets. 75 | * 76 | * @return bool 77 | */ 78 | public function hasBrackets() 79 | { 80 | return $this->brackets; 81 | } 82 | 83 | /** 84 | * Sets to true if the expression is between brackets. 85 | * 86 | * @Important 87 | * 88 | * @param bool $brackets 89 | */ 90 | public function setBrackets($brackets): void 91 | { 92 | $this->brackets = $brackets; 93 | } 94 | 95 | /** 96 | * Returns a Mouf instance descriptor describing this object. 97 | * 98 | * @param MoufManager $moufManager 99 | * 100 | * @return MoufInstanceDescriptor 101 | */ 102 | public function toInstanceDescriptor(MoufManager $moufManager) 103 | { 104 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 105 | $instanceDescriptor->getProperty('baseExpression')->setValue(NodeFactory::nodeToInstanceDescriptor($this->baseExpression, $moufManager)); 106 | $instanceDescriptor->getProperty('brackets')->setValue(NodeFactory::nodeToInstanceDescriptor($this->brackets, $moufManager)); 107 | 108 | return $instanceDescriptor; 109 | } 110 | 111 | /** 112 | * Renders the object as a SQL string. 113 | * 114 | * @param array $parameters 115 | * @param AbstractPlatform $platform 116 | * @param int $indent 117 | * @param int $conditionsMode 118 | * 119 | * @param bool $extrapolateParameters 120 | * @return string 121 | */ 122 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 123 | { 124 | $sql = ''; 125 | 126 | if ($this->baseExpression) { 127 | $sql .= ' '.$this->baseExpression.' '; 128 | } 129 | 130 | if ($this->brackets) { 131 | $sql = '('.$sql.')'; 132 | } 133 | 134 | return $sql; 135 | } 136 | 137 | /** 138 | * Walks the tree of nodes, calling the visitor passed in parameter. 139 | * 140 | * @param VisitorInterface $visitor 141 | */ 142 | public function walk(VisitorInterface $visitor) 143 | { 144 | $node = $this; 145 | $result = $visitor->enterNode($node); 146 | if ($result instanceof NodeInterface) { 147 | $node = $result; 148 | } 149 | 150 | return $visitor->leaveNode($node); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/SQLParser/Node/ShiftLeft.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ShiftLeft extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '<<'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/ShiftRight.php: -------------------------------------------------------------------------------- 1 | > operation in an SQL expression. 7 | * 8 | * @author David Négrier 9 | */ 10 | class ShiftRight extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return '>>'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/SimpleFunction.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufInstanceDescriptor; 37 | use Mouf\MoufManager; 38 | use SQLParser\Node\Traverser\NodeTraverser; 39 | use SQLParser\Node\Traverser\VisitorInterface; 40 | 41 | /** 42 | * This class represents a node that is an SQL function call. 43 | * 44 | * @author David Négrier 45 | */ 46 | class SimpleFunction implements NodeInterface 47 | { 48 | /** @var string */ 49 | private $baseExpression; 50 | 51 | /** 52 | * Returns the base expression (the string that generated this expression). 53 | * 54 | * @return string 55 | */ 56 | public function getBaseExpression() 57 | { 58 | return $this->baseExpression; 59 | } 60 | 61 | /** 62 | * Sets the base expression (the string that generated this expression). 63 | * 64 | * @param string $baseExpression 65 | */ 66 | public function setBaseExpression($baseExpression): void 67 | { 68 | $this->baseExpression = $baseExpression; 69 | } 70 | 71 | /** @var NodeInterface[]|NodeInterface */ 72 | private $subTree; 73 | 74 | /** 75 | * @return NodeInterface|NodeInterface[] 76 | */ 77 | public function getSubTree() 78 | { 79 | return $this->subTree; 80 | } 81 | 82 | /** 83 | * Sets the subtree. 84 | * 85 | * @Important 86 | * 87 | * @param array|NodeInterface $subTree 88 | */ 89 | public function setSubTree($subTree): void 90 | { 91 | $this->subTree = NodeFactory::simplify($subTree); 92 | } 93 | 94 | /** @var string */ 95 | private $alias; 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getAlias() 101 | { 102 | return $this->alias; 103 | } 104 | 105 | /** 106 | * Sets the alias. 107 | * 108 | * @Important 109 | * 110 | * @param string $alias 111 | */ 112 | public function setAlias($alias): void 113 | { 114 | $this->alias = $alias; 115 | } 116 | 117 | /** @var string */ 118 | private $direction; 119 | 120 | /** 121 | * @return string 122 | */ 123 | public function getDiretion() 124 | { 125 | return $this->direction; 126 | } 127 | 128 | /** 129 | * Sets the direction. 130 | * 131 | * @Important 132 | * 133 | * @param string $direction 134 | */ 135 | public function setDirection($direction): void 136 | { 137 | $this->direction = $direction; 138 | } 139 | 140 | /** @var bool */ 141 | private $brackets = false; 142 | 143 | /** 144 | * Returns true if the expression is between brackets. 145 | * 146 | * @return bool 147 | */ 148 | public function hasBrackets() 149 | { 150 | return $this->brackets; 151 | } 152 | 153 | /** 154 | * Returns a Mouf instance descriptor describing this object. 155 | * 156 | * @param MoufManager $moufManager 157 | * 158 | * @return MoufInstanceDescriptor 159 | */ 160 | public function toInstanceDescriptor(MoufManager $moufManager) 161 | { 162 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 163 | $instanceDescriptor->getProperty('baseExpression')->setValue(NodeFactory::nodeToInstanceDescriptor($this->baseExpression, $moufManager)); 164 | $instanceDescriptor->getProperty('subTree')->setValue(NodeFactory::nodeToInstanceDescriptor($this->subTree, $moufManager)); 165 | $instanceDescriptor->getProperty('alias')->setValue(NodeFactory::nodeToInstanceDescriptor($this->alias, $moufManager)); 166 | $instanceDescriptor->getProperty('direction')->setValue(NodeFactory::nodeToInstanceDescriptor($this->direction, $moufManager)); 167 | 168 | return $instanceDescriptor; 169 | } 170 | 171 | /** 172 | * Renders the object as a SQL string. 173 | * 174 | * @param array $parameters 175 | * @param AbstractPlatform $platform 176 | * @param int $indent 177 | * @param int $conditionsMode 178 | * 179 | * @param bool $extrapolateParameters 180 | * @return string 181 | */ 182 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 183 | { 184 | $sql = ''; 185 | if (!empty($this->baseExpression)) { 186 | $sql .= $this->baseExpression.'('; 187 | } 188 | $sql .= NodeFactory::toSql($this->subTree, $platform, $parameters, ',', false, $indent, $conditionsMode, $extrapolateParameters); 189 | if (!empty($this->baseExpression)) { 190 | $sql .= ')'; 191 | } 192 | 193 | if ($this->alias) { 194 | $sql .= ' AS '.$this->alias; 195 | } 196 | if ($this->direction) { 197 | $sql .= ' '.$this->direction; 198 | } 199 | 200 | return $sql; 201 | } 202 | 203 | /** 204 | * Walks the tree of nodes, calling the visitor passed in parameter. 205 | * 206 | * @param VisitorInterface $visitor 207 | */ 208 | public function walk(VisitorInterface $visitor) 209 | { 210 | $node = $this; 211 | $result = $visitor->enterNode($node); 212 | if ($result instanceof NodeInterface) { 213 | $node = $result; 214 | } 215 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 216 | foreach ($this->subTree as $key => $operand) { 217 | $result2 = $operand->walk($visitor); 218 | if ($result2 === NodeTraverser::REMOVE_NODE) { 219 | unset($this->subTree[$key]); 220 | } elseif ($result2 instanceof NodeInterface) { 221 | $this->subTree[$key] = $result2; 222 | } 223 | } 224 | } 225 | 226 | return $visitor->leaveNode($node); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/SQLParser/Node/SubQuery.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use SQLParser\Node\Traverser\NodeTraverser; 37 | use SQLParser\Node\Traverser\VisitorInterface; 38 | use SQLParser\Query\Select; 39 | use Mouf\MoufInstanceDescriptor; 40 | use Mouf\MoufManager; 41 | use SQLParser\Query\Union; 42 | 43 | /** 44 | * This class represents a subquery (and optionally a JOIN .. ON expression in an SQL expression. 45 | * 46 | * @author David Négrier 47 | */ 48 | class SubQuery implements NodeInterface 49 | { 50 | /** @var Select|Union|NodeInterface */ 51 | private $subQuery; 52 | 53 | /** 54 | * Returns the list of subQuery statements. 55 | * 56 | * @return Select|Union|NodeInterface 57 | */ 58 | public function getSubQuery() 59 | { 60 | return $this->subQuery; 61 | } 62 | 63 | /** 64 | * Sets the list of subQuery statements. 65 | * 66 | * @param Select|Union $subQuery 67 | */ 68 | public function setSubQuery($subQuery): void 69 | { 70 | $this->subQuery = $subQuery; 71 | } 72 | 73 | /** @var string */ 74 | private $alias; 75 | 76 | /** 77 | * Returns the alias. 78 | * 79 | * @return string 80 | */ 81 | public function getAlias() 82 | { 83 | return $this->alias; 84 | } 85 | 86 | /** 87 | * Sets the alias. 88 | * 89 | * @param string $alias 90 | */ 91 | public function setAlias($alias): void 92 | { 93 | $this->alias = $alias; 94 | } 95 | 96 | /** @var string */ 97 | private $joinType; 98 | 99 | /** 100 | * Returns the join type. 101 | * 102 | * @return string 103 | */ 104 | public function getJoinType() 105 | { 106 | return $this->joinType; 107 | } 108 | 109 | /** 110 | * Sets the join type (JOIN, LEFT JOIN, RIGHT JOIN, etc...). 111 | * 112 | * @param string $joinType 113 | */ 114 | public function setJoinType($joinType): void 115 | { 116 | $this->joinType = $joinType; 117 | } 118 | 119 | /** @var NodeInterface[] */ 120 | private $refClause; 121 | 122 | /** 123 | * Returns the list of refClause statements. 124 | * 125 | * @return NodeInterface[] 126 | */ 127 | public function getRefClause() 128 | { 129 | return $this->refClause; 130 | } 131 | 132 | /** 133 | * Sets the list of refClause statements. 134 | * 135 | * @param NodeInterface[] $refClause 136 | */ 137 | public function setRefClause($refClause): void 138 | { 139 | $this->refClause = $refClause; 140 | } 141 | 142 | /** 143 | * Returns a Mouf instance descriptor describing this object. 144 | * 145 | * @param MoufManager $moufManager 146 | * 147 | * @return MoufInstanceDescriptor 148 | */ 149 | public function toInstanceDescriptor(MoufManager $moufManager) 150 | { 151 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 152 | $instanceDescriptor->getProperty('subQuery')->setValue($this->subQuery->toInstanceDescriptor($moufManager)); 153 | $instanceDescriptor->getProperty('alias')->setValue($this->alias); 154 | $instanceDescriptor->getProperty('joinType')->setValue($this->joinType); 155 | $instanceDescriptor->getProperty('refClause')->setValue(NodeFactory::nodeToInstanceDescriptor($this->refClause, $moufManager)); 156 | 157 | return $instanceDescriptor; 158 | } 159 | 160 | /** 161 | * Walks the tree of nodes, calling the visitor passed in parameter. 162 | * 163 | * @param VisitorInterface $visitor 164 | */ 165 | public function walk(VisitorInterface $visitor) 166 | { 167 | $node = $this; 168 | $result = $visitor->enterNode($node); 169 | if ($result instanceof NodeInterface) { 170 | $node = $result; 171 | } 172 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 173 | $result2 = $this->subQuery->walk($visitor); 174 | if ($result2 === NodeTraverser::REMOVE_NODE) { 175 | return NodeTraverser::REMOVE_NODE; 176 | } elseif ($result2 instanceof NodeInterface) { 177 | $this->subQuery = $result2; 178 | } 179 | } 180 | 181 | return $visitor->leaveNode($node); 182 | } 183 | 184 | /** 185 | * Renders the object as a SQL string. 186 | * 187 | * @param array $parameters 188 | * @param AbstractPlatform $platform 189 | * @param int $indent 190 | * @param int $conditionsMode 191 | * 192 | * @param bool $extrapolateParameters 193 | * @return string 194 | */ 195 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 196 | { 197 | $sql = ''; 198 | if ($this->refClause) { 199 | $sql .= "\n ".$this->joinType.' '; 200 | } 201 | $sql .= '('.$this->subQuery->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters).')'; 202 | if ($this->alias) { 203 | $sql .= ' '.$platform->quoteSingleIdentifier($this->alias); 204 | } 205 | if ($this->refClause) { 206 | $sql .= ' ON '; 207 | $sql .= NodeFactory::toSql($this->refClause, $platform, $parameters, ' ', true, $indent, $conditionsMode, $extrapolateParameters); 208 | } 209 | 210 | return $sql; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Table.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | use Mouf\MoufInstanceDescriptor; 37 | use Mouf\MoufManager; 38 | use SQLParser\Node\Traverser\NodeTraverser; 39 | use SQLParser\Node\Traverser\VisitorInterface; 40 | 41 | /** 42 | * This class represents a table (and optionally a JOIN .. ON expression) in a SQL expression. 43 | * 44 | * @author David Négrier 45 | */ 46 | class Table implements NodeInterface 47 | { 48 | /** @var mixed */ 49 | private $database; 50 | 51 | /** 52 | * Returns the name of the database. 53 | * 54 | * @return mixed 55 | */ 56 | public function getDatabase() 57 | { 58 | return $this->database; 59 | } 60 | 61 | /** 62 | * Sets the name of the database 63 | * 64 | * @param mixed $database 65 | */ 66 | public function setDatabase($database): void 67 | { 68 | $this->database = $database; 69 | } 70 | 71 | /** @var string */ 72 | private $table; 73 | 74 | /** 75 | * Returns the table name. 76 | * 77 | * @return string 78 | */ 79 | public function getTable() 80 | { 81 | return $this->table; 82 | } 83 | 84 | /** 85 | * Sets the table name. 86 | * 87 | * @Important 88 | * 89 | * @param string $table 90 | */ 91 | public function setTable($table): void 92 | { 93 | $this->table = $table; 94 | } 95 | 96 | /** @var string */ 97 | private $alias; 98 | 99 | /** 100 | * Returns the alias. 101 | * 102 | * @return string 103 | */ 104 | public function getAlias() 105 | { 106 | return $this->alias; 107 | } 108 | 109 | /** 110 | * Sets the alias. 111 | * 112 | * @Important 113 | * 114 | * @param string $alias 115 | */ 116 | public function setAlias($alias): void 117 | { 118 | $this->alias = $alias; 119 | } 120 | 121 | /** @var string */ 122 | private $joinType; 123 | 124 | /** 125 | * Returns the join type. 126 | * 127 | * @return string 128 | */ 129 | public function getJoinType() 130 | { 131 | return $this->joinType; 132 | } 133 | 134 | /** 135 | * Sets the join type (JOIN, LEFT JOIN, RIGHT JOIN, etc...). 136 | * 137 | * @Important 138 | * 139 | * @param string $joinType 140 | */ 141 | public function setJoinType($joinType): void 142 | { 143 | $this->joinType = $joinType; 144 | } 145 | 146 | /** @var NodeInterface[]|NodeInterface */ 147 | private $refClause; 148 | 149 | /** 150 | * Returns the list of refClause statements. 151 | * 152 | * @return NodeInterface[]|NodeInterface 153 | */ 154 | public function getRefClause() 155 | { 156 | return $this->refClause; 157 | } 158 | 159 | /** 160 | * Sets the list of refClause statements. 161 | * 162 | * @Important 163 | * 164 | * @param NodeInterface[]|NodeInterface $refClause 165 | */ 166 | public function setRefClause($refClause): void 167 | { 168 | $this->refClause = $refClause; 169 | } 170 | 171 | /** 172 | * @var Hint[] 173 | */ 174 | private $hints; 175 | 176 | /** 177 | * Return the list of table hints 178 | * 179 | * @return Hint[] 180 | */ 181 | public function getHints(): array 182 | { 183 | return $this->hints; 184 | } 185 | 186 | /** 187 | * Set a list of table hints 188 | * 189 | * @param Hint[] $hints 190 | */ 191 | public function setHints(array $hints): void 192 | { 193 | $this->hints = $hints; 194 | } 195 | 196 | /** 197 | * Returns a Mouf instance descriptor describing this object. 198 | * 199 | * @param MoufManager $moufManager 200 | * 201 | * @return MoufInstanceDescriptor 202 | */ 203 | public function toInstanceDescriptor(MoufManager $moufManager) 204 | { 205 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 206 | $instanceDescriptor->getProperty('database')->setValue($this->database); 207 | $instanceDescriptor->getProperty('table')->setValue($this->table); 208 | $instanceDescriptor->getProperty('alias')->setValue($this->alias); 209 | $instanceDescriptor->getProperty('joinType')->setValue($this->joinType); 210 | $instanceDescriptor->getProperty('refClause')->setValue(NodeFactory::nodeToInstanceDescriptor($this->refClause, $moufManager)); 211 | 212 | return $instanceDescriptor; 213 | } 214 | 215 | /** 216 | * Renders the object as a SQL string. 217 | * 218 | * @param array $parameters 219 | * @param AbstractPlatform $platform 220 | * @param int $indent 221 | * @param int $conditionsMode 222 | * 223 | * @param bool $extrapolateParameters 224 | * @return string 225 | */ 226 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 227 | { 228 | $sql = ''; 229 | if ($this->refClause || $this->joinType === 'CROSS JOIN') { 230 | $sql .= "\n ".$this->joinType.' '; 231 | } 232 | if ($this->database) { 233 | $sql .= $platform->quoteSingleIdentifier($this->database).'.'; 234 | } 235 | $sql .= $platform->quoteSingleIdentifier($this->table); 236 | if ($this->alias) { 237 | $sql .= ' '.$platform->quoteSingleIdentifier($this->alias); 238 | } 239 | if ($this->hints) { 240 | foreach ($this->hints as $hint) { 241 | $sql .= ' ' . $hint->getType() . ' ' . $hint->getList(); 242 | } 243 | } 244 | if ($this->refClause) { 245 | $sql .= ' ON '; 246 | $sql .= NodeFactory::toSql($this->refClause, $platform, $parameters, ' ', true, $indent, $conditionsMode, $extrapolateParameters); 247 | } 248 | 249 | return $sql; 250 | } 251 | 252 | /** 253 | * Walks the tree of nodes, calling the visitor passed in parameter. 254 | * 255 | * @param VisitorInterface $visitor 256 | */ 257 | public function walk(VisitorInterface $visitor) 258 | { 259 | $node = $this; 260 | $result = $visitor->enterNode($node); 261 | if ($result instanceof NodeInterface) { 262 | $node = $result; 263 | } 264 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 265 | if (is_array($this->refClause)) { 266 | foreach ($this->refClause as $key => $operand) { 267 | $result2 = $operand->walk($visitor); 268 | if ($result2 === NodeTraverser::REMOVE_NODE) { 269 | unset($this->refClause[$key]); 270 | } elseif ($result2 instanceof NodeInterface) { 271 | $this->refClause[$key] = $result2; 272 | } 273 | } 274 | } elseif ($this->refClause) { 275 | $result2 = $this->refClause->walk($visitor); 276 | if ($result2 === NodeTraverser::REMOVE_NODE) { 277 | $this->refClause = null; 278 | } elseif ($result2 instanceof NodeInterface) { 279 | $this->refClause = $result2; 280 | } 281 | } 282 | } 283 | 284 | return $visitor->leaveNode($node); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Then.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Then extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'THEN'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Traverser/CompositeVisitor.php: -------------------------------------------------------------------------------- 1 | visitors[] = $visitor; 30 | array_unshift($this->reverseVisitors, $visitor); 31 | } 32 | 33 | /** 34 | * Called on every node when the traverser enters the node. 35 | * The enterNode() method can return a changed node, or null if nothing is changed. 36 | * The enterNode() method can also return the value NodeTraverser::DONT_TRAVERSE_CHILDREN, 37 | * which instructs the traverser to skip all children of the current node. 38 | * 39 | * @param NodeInterface $node 40 | * 41 | * @return NodeInterface|string|null 42 | */ 43 | public function enterNode(NodeInterface $node) 44 | { 45 | foreach ($this->reverseVisitors as $visitor) { 46 | $result = $visitor->enterNode($node); 47 | if ($result instanceof NodeInterface) { 48 | $node = $result; 49 | } elseif ($result === NodeTraverser::DONT_TRAVERSE_CHILDREN) { 50 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 51 | } elseif ($result !== null) { 52 | throw new TraverserException('Unexpected return value for enterNode. Return value should be a NodeInterface instance or the NodeTraverser::DONT_TRAVERSE_CHILDREN constant or null.'); 53 | } 54 | } 55 | 56 | return $node; 57 | } 58 | 59 | /** 60 | * {@inheritDoc} 61 | */ 62 | public function leaveNode(NodeInterface $node) 63 | { 64 | foreach ($this->visitors as $visitor) { 65 | $result = $visitor->leaveNode($node); 66 | if ($result instanceof NodeInterface) { 67 | $node = $result; 68 | } elseif ($result === NodeTraverser::REMOVE_NODE) { 69 | return NodeTraverser::REMOVE_NODE; 70 | } elseif ($result !== null) { 71 | throw new TraverserException('Unexpected return value for leaveNode. Return value should be a NodeInterface instance or the NodeTraverser::REMOVE_NODE constant or null.'); 72 | } 73 | } 74 | 75 | return $node; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Traverser/DetectMagicJoinSelectVisitor.php: -------------------------------------------------------------------------------- 1 | */ 18 | private $magicJoinSelects = array(); 19 | 20 | /** 21 | * Removes all detected magic join selects. 22 | * Useful for reusing the visitor instance on another node traversal. 23 | */ 24 | public function resetVisitor(): void 25 | { 26 | $this->magicJoinSelects = array(); 27 | $this->lastVisitedSelect = null; 28 | } 29 | 30 | /** 31 | * Return the list of all Select object that have a MagicJoin table. 32 | * 33 | * @return MagicJoinSelect[] 34 | */ 35 | public function getMagicJoinSelects() 36 | { 37 | // TODO: throw an exception if the magicjoin table is not the only one 38 | return $this->magicJoinSelects; 39 | } 40 | 41 | /** 42 | * Called on every node when the traverser enters the node. 43 | * The enterNode() method can return a changed node, or null if nothing is changed. 44 | * The enterNode() method can also return the value NodeTraverser::DONT_TRAVERSE_CHILDREN, 45 | * which instructs the traverser to skip all children of the current node. 46 | * 47 | * @param NodeInterface $node 48 | * 49 | * @return NodeInterface|string|null 50 | */ 51 | public function enterNode(NodeInterface $node) 52 | { 53 | if ($node instanceof Select) { 54 | $this->lastVisitedSelect = $node; 55 | } elseif ($node instanceof Table) { 56 | if (strtolower($node->getTable()) == 'magicjoin') { 57 | $mainTable = trim($node->getAlias(), '()'); 58 | $this->magicJoinSelects[] = new MagicJoinSelect($this->lastVisitedSelect, $mainTable); 59 | } 60 | } 61 | 62 | return null; 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public function leaveNode(NodeInterface $node) 69 | { 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Traverser/DetectTablesVisitor.php: -------------------------------------------------------------------------------- 1 | */ 19 | private $tables = array(); 20 | 21 | private $defaultTable; 22 | 23 | /** 24 | * Removes all detected magic join selects. 25 | * Useful for reusing the visitor instance on another node traversal. 26 | */ 27 | public function resetVisitor(): void 28 | { 29 | $this->tables = array(); 30 | $this->isSelectVisited = false; 31 | } 32 | 33 | /** 34 | * @param string $defaultTable Sets the default table that will be used if no table is specified in a colref. 35 | */ 36 | public function __construct($defaultTable) 37 | { 38 | $this->defaultTable = $defaultTable; 39 | } 40 | 41 | /** 42 | * Return the list of tables referenced in the Select. 43 | * 44 | * @return string[] The key and the value are the table name. 45 | */ 46 | public function getTables() 47 | { 48 | return $this->tables; 49 | } 50 | 51 | /** 52 | * Called on every node when the traverser enters the node. 53 | * The enterNode() method can return a changed node, or null if nothing is changed. 54 | * The enterNode() method can also return the value NodeTraverser::DONT_TRAVERSE_CHILDREN, 55 | * which instructs the traverser to skip all children of the current node. 56 | * 57 | * @param NodeInterface $node 58 | * 59 | * @return NodeInterface|string|null 60 | */ 61 | public function enterNode(NodeInterface $node) 62 | { 63 | if ($node instanceof Select) { 64 | if ($this->isSelectVisited) { 65 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 66 | } else { 67 | $this->isSelectVisited = true; 68 | } 69 | } elseif ($node instanceof ColRef) { 70 | if (empty($node->getTable())) { 71 | $node->setTable($this->defaultTable); 72 | } 73 | $this->tables[$node->getTable()] = $node->getTable(); 74 | } 75 | 76 | return null; 77 | } 78 | 79 | /** 80 | * {@inheritDoc} 81 | */ 82 | public function leaveNode(NodeInterface $node) 83 | { 84 | return null; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Traverser/MagicJoinSelect.php: -------------------------------------------------------------------------------- 1 | select = $select; 31 | $this->mainTable = $mainTable; 32 | } 33 | 34 | /** 35 | * @return Select 36 | */ 37 | public function getSelect() 38 | { 39 | return $this->select; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getMainTable() 46 | { 47 | return $this->mainTable; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Traverser/NodeTraverser.php: -------------------------------------------------------------------------------- 1 | visitor = new CompositeVisitor(); 20 | } 21 | 22 | /** 23 | * Registers a new visitor with this traverser. 24 | * 25 | * @param VisitorInterface $visitor 26 | */ 27 | public function addVisitor(VisitorInterface $visitor): void 28 | { 29 | $this->visitor->addVisitor($visitor); 30 | } 31 | 32 | /** 33 | * Starts traversing the tree, calling each visitor on each node. 34 | * 35 | * @param NodeInterface $node 36 | */ 37 | public function walk(NodeInterface $node): void 38 | { 39 | $node->walk($this->visitor); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SQLParser/Node/Traverser/TraverserException.php: -------------------------------------------------------------------------------- 1 | 9 | * and David Négrier 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | namespace SQLParser\Node; 34 | 35 | use Doctrine\DBAL\Platforms\AbstractPlatform; 36 | 37 | /** 38 | * This class represents a parameter (as in parameterized query). 39 | * 40 | * @author David MAECHLER 41 | */ 42 | class UnquotedParameter extends Parameter 43 | { 44 | /** 45 | * Renders the object as a SQL string without quote if its a numeric. 46 | * 47 | * @param array $parameters 48 | * @param AbstractPlatform $platform 49 | * @param int $indent 50 | * @param int $conditionsMode 51 | * 52 | * @param bool $extrapolateParameters 53 | * @return string 54 | */ 55 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 56 | { 57 | $name = parent::toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); 58 | $name = str_replace("'", '', $name ?? ''); 59 | 60 | return $name; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SQLParser/Node/WhenConditions.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class WhenConditions extends AbstractManyInstancesOperator 13 | { 14 | /** @var NodeInterface|NodeInterface[]|string */ 15 | private $value; 16 | 17 | /** 18 | * @return NodeInterface|NodeInterface[]|string 19 | */ 20 | public function getValue() 21 | { 22 | return $this->value; 23 | } 24 | 25 | /** 26 | * Sets the value. 27 | * 28 | * @Important 29 | * 30 | * @param NodeInterface|NodeInterface[]|string $value 31 | */ 32 | public function setValue($value): void 33 | { 34 | $this->value = $value; 35 | } 36 | 37 | /** 38 | * Renders the object as a SQL string. 39 | * 40 | * @param array $parameters 41 | * @param AbstractPlatform $platform 42 | * @param int $indent 43 | * @param int $conditionsMode 44 | * 45 | * @param bool $extrapolateParameters 46 | * @return string 47 | */ 48 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 49 | { 50 | $fullSql = ''; 51 | 52 | if ($this->value) { 53 | $fullSql = NodeFactory::toSql($this->value, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 54 | } 55 | 56 | foreach ($this->getOperands() as $operand) { 57 | $sql = NodeFactory::toSql($operand, $platform, $parameters, ' ', false, $indent, $conditionsMode, $extrapolateParameters); 58 | if ($sql != null) { 59 | $fullSql .= "\n".str_repeat(' ', $indent).'WHEN '.$sql; 60 | } 61 | } 62 | 63 | return $fullSql; 64 | } 65 | 66 | /** 67 | * Returns the symbol for this operator. 68 | * 69 | * @return string 70 | */ 71 | protected function getOperatorSymbol(): string 72 | { 73 | return 'WHEN'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/SQLParser/Node/XorOp.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class XorOp extends AbstractTwoOperandsOperator 11 | { 12 | /** 13 | * Returns the symbol for this operator. 14 | * 15 | * @return string 16 | */ 17 | protected function getOperatorSymbol(): string 18 | { 19 | return 'XOR'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SQLParser/Query/StatementFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class StatementFactory 16 | { 17 | /** 18 | * @return Select|Union 19 | * @throws MagicQueryException 20 | */ 21 | public static function toObject(array $desc) 22 | { 23 | if (isset($desc['SELECT'])) { 24 | $select = new Select(); 25 | 26 | $columns = array_map(function ($item) { 27 | return NodeFactory::toObject($item); 28 | }, $desc['SELECT']); 29 | $columns = NodeFactory::simplify($columns); 30 | 31 | $options = []; 32 | foreach ($columns as $key => $column) { 33 | if ($column instanceof Reserved) { 34 | if (strtoupper($column->getBaseExpression()) === 'DISTINCT') { 35 | $select->setDistinct(true); 36 | } else { 37 | $options[] = $column->getBaseExpression(); 38 | } 39 | unset($columns[$key]); 40 | } 41 | } 42 | $select->setOptions($options); 43 | 44 | $select->setColumns($columns); 45 | 46 | if (isset($desc['FROM'])) { 47 | $from = NodeFactory::mapArrayToNodeObjectList($desc['FROM']); 48 | $select->setFrom($from); 49 | } 50 | 51 | if (isset($desc['WHERE'])) { 52 | $where = NodeFactory::mapArrayToNodeObjectList($desc['WHERE']); 53 | $where = NodeFactory::simplify($where); 54 | $select->setWhere($where); 55 | } 56 | 57 | if (isset($desc['GROUP'])) { 58 | $group = NodeFactory::mapArrayToNodeObjectList($desc['GROUP']); 59 | $group = NodeFactory::simplify($group); 60 | $select->setGroup($group); 61 | } 62 | 63 | if (isset($desc['HAVING'])) { 64 | $having = NodeFactory::mapArrayToNodeObjectList($desc['HAVING']); 65 | $having = NodeFactory::simplify($having); 66 | $select->setHaving($having); 67 | } 68 | 69 | if (isset($desc['ORDER'])) { 70 | $order = NodeFactory::mapArrayToNodeObjectList($desc['ORDER']); 71 | $order = NodeFactory::simplify($order); 72 | $select->setOrder($order); 73 | } 74 | 75 | if (isset($desc['LIMIT'])) { 76 | $descLimit = $desc['LIMIT']; 77 | 78 | //$limit = NodeFactory::toObject($descLimit['limit']); 79 | //$limit = NodeFactory::simplify($limit); 80 | if (isset($descLimit['rowcount'])) { 81 | $select->setLimit(NodeFactory::toLimitNode($descLimit['rowcount'])); 82 | } 83 | 84 | if (isset($descLimit['offset'])) { 85 | $select->setOffset(NodeFactory::toLimitNode($descLimit['offset'])); 86 | } 87 | 88 | 89 | //$offset = NodeFactory::toObject($descLimit['offset']); 90 | //$offset = NodeFactory::simplify($offset); 91 | //$select->setOffset($offset); 92 | } 93 | 94 | return $select; 95 | } 96 | // UNION and UNION DISTINCT have similar behavior 97 | if (isset($desc['UNION']) || isset($desc['UNION ALL']) || isset($desc['UNION DISTINCT'])) { 98 | $isUnionAll = isset($desc['UNION ALL']); 99 | $unionStatement = $desc['UNION'] ?? ($desc['UNION ALL'] ?? $desc['UNION DISTINCT']); 100 | 101 | /** @var Select[] $selects */ 102 | $selects = array_map([self::class, 'toObject'], $unionStatement); 103 | 104 | $union = new Union($selects, $isUnionAll); 105 | 106 | if (isset($desc['0']) && isset($desc['0']['ORDER'])) { 107 | $order = NodeFactory::mapArrayToNodeObjectList($desc['0']['ORDER']); 108 | $order = NodeFactory::simplify($order); 109 | $union->setOrder($order); 110 | } 111 | 112 | return $union; 113 | } 114 | 115 | throw new \BadMethodCallException('Unknown query'); 116 | } 117 | 118 | /** 119 | * @param array $descLimit 120 | * 121 | * @return array 122 | * 123 | * @throws \Exception 124 | */ 125 | /*private static function checkLimitDesc(array $descLimit) 126 | { 127 | if (count($descLimit) > 2) { 128 | throw new \Exception('The limit returned by the SQLParser contains more than 2 items, something might went wrong.'); 129 | } 130 | 131 | return ['offset' => $descLimit['offset'], 'limit' => $descLimit['rowcount']]; 132 | }*/ 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/SQLParser/Query/StatementInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface StatementInterface extends SqlRenderInterface 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/SQLParser/Query/Union.php: -------------------------------------------------------------------------------- 1 | UNION query. You can use it to generate a SQL query statement 16 | * using the toSql method. 17 | * You can use the QueryResult class if you want to run the query directly. 18 | * 19 | * @author David Négrier 20 | */ 21 | class Union implements StatementInterface, NodeInterface 22 | { 23 | /** 24 | * @var array|Select[] 25 | */ 26 | private $selects; 27 | 28 | /** 29 | * @var bool 30 | */ 31 | private $isUnionAll; 32 | 33 | /** 34 | * Union constructor. 35 | * @param Select[] $selects 36 | */ 37 | public function __construct(array $selects, bool $isUnionAll) 38 | { 39 | $this->selects = $selects; 40 | $this->isUnionAll = $isUnionAll; 41 | } 42 | 43 | /** @var NodeInterface[]|NodeInterface */ 44 | private $order; 45 | 46 | /** 47 | * Returns the list of order statements. 48 | * 49 | * @return NodeInterface[]|NodeInterface 50 | */ 51 | public function getOrder() 52 | { 53 | return $this->order; 54 | } 55 | 56 | /** 57 | * Sets the list of order statements. 58 | * 59 | * @param NodeInterface[]|NodeInterface $order 60 | */ 61 | public function setOrder($order): void 62 | { 63 | $this->order = $order; 64 | } 65 | 66 | /** 67 | * @param MoufManager $moufManager 68 | * 69 | * @return MoufInstanceDescriptor 70 | */ 71 | public function toInstanceDescriptor(MoufManager $moufManager) 72 | { 73 | $instanceDescriptor = $moufManager->createInstance(get_called_class()); 74 | $instanceDescriptor->getProperty('selects')->setValue(NodeFactory::nodeToInstanceDescriptor($this->selects, $moufManager)); 75 | $instanceDescriptor->getProperty('order')->setValue(NodeFactory::nodeToInstanceDescriptor($this->order, $moufManager)); 76 | 77 | return $instanceDescriptor; 78 | } 79 | 80 | /** 81 | * Configure the $instanceDescriptor describing this object (it must already exist as a Mouf instance). 82 | * 83 | * @param MoufManager $moufManager 84 | * 85 | * @return MoufInstanceDescriptor 86 | */ 87 | public function overwriteInstanceDescriptor($name, MoufManager $moufManager) 88 | { 89 | //$name = $moufManager->findInstanceName($this); 90 | $instanceDescriptor = $moufManager->getInstanceDescriptor($name); 91 | $instanceDescriptor->getProperty('selects')->setValue(NodeFactory::nodeToInstanceDescriptor($this->selects, $moufManager)); 92 | $instanceDescriptor->getProperty('order')->setValue(NodeFactory::nodeToInstanceDescriptor($this->order, $moufManager)); 93 | 94 | return $instanceDescriptor; 95 | } 96 | 97 | /** 98 | * Renders the object as a SQL string. 99 | * 100 | * @param array $parameters 101 | * @param AbstractPlatform $platform 102 | * @param int $indent 103 | * @param int $conditionsMode 104 | * 105 | * @param bool $extrapolateParameters 106 | * @return string 107 | */ 108 | public function toSql(array $parameters, AbstractPlatform $platform, int $indent = 0, $conditionsMode = self::CONDITION_APPLY, bool $extrapolateParameters = true): ?string 109 | { 110 | $selectsSql = array_map(function(Select $select) use ($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters) { 111 | return $select->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); 112 | }, $this->selects); 113 | 114 | $unionStatement = $this->isUnionAll ? 'UNION ALL' : 'UNION'; 115 | 116 | $sql = '(' . implode(') ' . $unionStatement . ' (', $selectsSql) . ')'; 117 | 118 | if (!empty($this->order)) { 119 | $order = NodeFactory::toSql($this->order, $platform, $parameters, ',', false, $indent + 2, $conditionsMode, $extrapolateParameters); 120 | if ($order) { 121 | $sql .= "\nORDER BY ".$order; 122 | } 123 | } 124 | 125 | return $sql; 126 | } 127 | 128 | /** 129 | * Walks the tree of nodes, calling the visitor passed in parameter. 130 | * 131 | * @param VisitorInterface $visitor 132 | */ 133 | public function walk(VisitorInterface $visitor) 134 | { 135 | $node = $this; 136 | $result = $visitor->enterNode($node); 137 | if ($result instanceof NodeInterface) { 138 | $node = $result; 139 | } 140 | if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { 141 | $this->walkChildren($this->selects, $visitor); 142 | $this->walkChildren($this->order, $visitor); 143 | } 144 | 145 | return $visitor->leaveNode($node); 146 | } 147 | 148 | /** 149 | * @param array|NodeInterface|null $children 150 | * @param VisitorInterface $visitor 151 | */ 152 | private function walkChildren(&$children, VisitorInterface $visitor): void 153 | { 154 | if ($children) { 155 | if (is_array($children)) { 156 | foreach ($children as $key => $operand) { 157 | if ($operand) { 158 | $result2 = $operand->walk($visitor); 159 | if ($result2 === NodeTraverser::REMOVE_NODE) { 160 | unset($children[$key]); 161 | } elseif ($result2 instanceof NodeInterface) { 162 | $children[$key] = $result2; 163 | } 164 | } 165 | } 166 | } else { 167 | $result2 = $children->walk($visitor); 168 | if ($result2 === NodeTraverser::REMOVE_NODE) { 169 | $children = null; 170 | } elseif ($result2 instanceof NodeInterface) { 171 | $children = $result2; 172 | } 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/SQLParser/SqlRenderInterface.php: -------------------------------------------------------------------------------- 1 | render('SELECT * FROM toto {% if id %}WHERE id = :id{% endif %}', ['id' => 12]); 15 | $this->assertEquals('SELECT * FROM toto WHERE id = :id', $sql); 16 | } 17 | 18 | public function testException() 19 | { 20 | $twig = SqlTwigEnvironmentFactory::getTwigEnvironment(); 21 | $this->expectException(RuntimeError::class); 22 | $twig->render('SELECT * FROM toto WHERE id = {{ id }}', ['id' => 'hello']); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Mouf/Database/StubSchemaManager.php: -------------------------------------------------------------------------------- 1 | schema = $schema; 18 | } 19 | 20 | /** 21 | * Creates a schema instance for the current database. 22 | * 23 | * @return \Doctrine\DBAL\Schema\Schema 24 | */ 25 | public function createSchema() 26 | { 27 | return $this->schema; 28 | } 29 | 30 | /** 31 | * Gets Table Column Definition. 32 | * 33 | * @param array $tableColumn 34 | * 35 | * @return \Doctrine\DBAL\Schema\Column 36 | */ 37 | protected function _getPortableTableColumnDefinition($tableColumn) 38 | { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/SQLParser/Node/Traverser/DetectTableVisitorTest.php: -------------------------------------------------------------------------------- 1 | addVisitor($visitor); 16 | 17 | $parser = new PHPSQLParser(); 18 | 19 | $sql = 'SELECT foo.bar FROM users'; 20 | $parsed = $parser->parse($sql); 21 | $select = StatementFactory::toObject($parsed); 22 | $nodeTraverser->walk($select); 23 | $this->assertCount(1, $visitor->getTables()); 24 | $this->assertContains('foo', $visitor->getTables()); 25 | $visitor->resetVisitor(); 26 | 27 | $sql = 'SELECT foo.bar, foo.baz FROM users WHERE toto.tata = 12 GROUP BY yop.goo ORDER BY zap.do'; 28 | $parsed = $parser->parse($sql); 29 | $select = StatementFactory::toObject($parsed); 30 | $nodeTraverser->walk($select); 31 | $this->assertCount(4, $visitor->getTables()); 32 | $this->assertContains('foo', $visitor->getTables()); 33 | $this->assertContains('toto', $visitor->getTables()); 34 | $this->assertContains('yop', $visitor->getTables()); 35 | $this->assertContains('zap', $visitor->getTables()); 36 | $visitor->resetVisitor(); 37 | } 38 | 39 | public function testWrappedSelect() 40 | { 41 | $visitor = new DetectTablesVisitor('users'); 42 | $nodeTraverser = new NodeTraverser(); 43 | $nodeTraverser->addVisitor($visitor); 44 | 45 | $parser = new PHPSQLParser(); 46 | 47 | $sql = 'SELECT foo.bar, foo.baz FROM users WHERE toto.tata = (SELECT mii.id FROM mii WHERE mii.yo=42) GROUP BY yop.goo ORDER BY zap.do'; 48 | $parsed = $parser->parse($sql); 49 | $select = StatementFactory::toObject($parsed); 50 | $nodeTraverser->walk($select); 51 | $this->assertCount(4, $visitor->getTables()); 52 | $this->assertContains('foo', $visitor->getTables()); 53 | $this->assertContains('toto', $visitor->getTables()); 54 | $this->assertContains('yop', $visitor->getTables()); 55 | $this->assertContains('zap', $visitor->getTables()); 56 | $visitor->resetVisitor(); 57 | } 58 | 59 | public function testMissingRefTable() 60 | { 61 | $visitor = new DetectTablesVisitor('yop'); 62 | $nodeTraverser = new NodeTraverser(); 63 | $nodeTraverser->addVisitor($visitor); 64 | 65 | $parser = new PHPSQLParser(); 66 | 67 | $sql = 'SELECT foo.bar, foo.baz FROM users WHERE tata = (SELECT mii.id FROM mii WHERE mii.yo=42) GROUP BY yop.goo ORDER BY zap.do'; 68 | $parsed = $parser->parse($sql); 69 | $select = StatementFactory::toObject($parsed); 70 | $nodeTraverser->walk($select); 71 | 72 | $this->assertEquals('yop', $select->getWhere()->getLeftOperand()->getTable()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/SQLParser/Node/Traverser/NodeTraverserTest.php: -------------------------------------------------------------------------------- 1 | addVisitor($magicJoinDetector); 16 | 17 | $parser = new PHPSQLParser(); 18 | 19 | $sql = 'SELECT * FROM users'; 20 | $parsed = $parser->parse($sql); 21 | $select = StatementFactory::toObject($parsed); 22 | $nodeTraverser->walk($select); 23 | $this->assertCount(0, $magicJoinDetector->getMagicJoinSelects()); 24 | $magicJoinDetector->resetVisitor(); 25 | 26 | $sql = 'SELECT * FROM magicjoin(mytable)'; 27 | $parsed = $parser->parse($sql); 28 | $select = StatementFactory::toObject($parsed); 29 | $nodeTraverser->walk($select); 30 | $this->assertCount(1, $magicJoinDetector->getMagicJoinSelects()); 31 | $this->assertEquals('mytable', $magicJoinDetector->getMagicJoinSelects()[0]->getMainTable()); 32 | $magicJoinDetector->resetVisitor(); 33 | 34 | $sql = 'SELECT SUM(users.age) FROM users WHERE name LIKE :name AND company LIKE :company'; 35 | $parsed = $parser->parse($sql); 36 | $select = StatementFactory::toObject($parsed); 37 | $nodeTraverser->walk($select); 38 | $this->assertCount(0, $magicJoinDetector->getMagicJoinSelects()); 39 | $magicJoinDetector->resetVisitor(); 40 | 41 | $sql = 'SELECT * FROM users WHERE status in :status'; 42 | $parsed = $parser->parse($sql); 43 | $select = StatementFactory::toObject($parsed); 44 | $nodeTraverser->walk($select); 45 | $this->assertCount(0, $magicJoinDetector->getMagicJoinSelects()); 46 | $magicJoinDetector->resetVisitor(); 47 | 48 | // Triggers a const node 49 | $sql = 'SELECT id+1 FROM users'; 50 | $parsed = $parser->parse($sql); 51 | $select = StatementFactory::toObject($parsed); 52 | $nodeTraverser->walk($select); 53 | $this->assertCount(0, $magicJoinDetector->getMagicJoinSelects()); 54 | $magicJoinDetector->resetVisitor(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |