Skip to content

Commit bdf8940

Browse files
committed
Initial commit
0 parents  commit bdf8940

File tree

13 files changed

+1132
-0
lines changed

13 files changed

+1132
-0
lines changed

.editorconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[*]
2+
charset = utf-8
3+
end_of_line = lf
4+
trim_trailing_whitespace = true
5+
insert_final_newline = true
6+
indent_style = space
7+
indent_size = 4
8+

.github/workflows/checks.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Checks
2+
on: [push, pull_request]
3+
jobs:
4+
checks:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v2
8+
-
9+
uses: shivammathur/setup-php@v2
10+
with:
11+
php-version: '7.1'
12+
- run: composer install --no-progress --prefer-dist
13+
- run: composer check

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/vendor
2+
/cache
3+
/.idea
4+
/phpstan.neon
5+
/composer.lock

README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
## MySQL index hints for Doctrine
2+
3+
This library provides a simple way to incorporate [MySQL's index hints](https://dev.mysql.com/doc/refman/8.0/en/index-hints.html)
4+
into SELECT queries written in [Doctrine Query Language](https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/reference/dql-doctrine-query-language.html)
5+
via [custom SqlWalker](https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/cookbook/dql-custom-walkers.html#modify-the-output-walker-to-generate-vendor-specific-sql).
6+
No need for native queries anymore.
7+
8+
### Installation:
9+
10+
```sh
11+
composer require shipmonk/doctrine-mysql-index-hints
12+
```
13+
14+
### Simple usage:
15+
16+
```php
17+
$result = $em->createQueryBuilder()
18+
->select('u.id')
19+
->from(User::class, 'u')
20+
->andWhere('u.id = 1')
21+
->getQuery()
22+
->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, UseIndexSqlWalker::class)
23+
->setHint(UseIndexSqlWalker::class, [IndexHint::force(User::IDX_FOO, User::TABLE_NAME)])
24+
->getResult();
25+
```
26+
27+
Which produces following SQL:
28+
29+
```mysql
30+
SELECT u0_.id AS id_0
31+
FROM user u0_ FORCE INDEX (IDX_FOO)
32+
WHERE u0_.id = 1
33+
```
34+
35+
See the used entity (it makes sense to put table names and index names into public constants to bind it together and reference it easily):
36+
37+
```php
38+
/**
39+
* @ORM\Table(
40+
* name=User::TABLE_NAME
41+
* indexes={
42+
* @ORM\Index(name=User::IDX_FOO, columns={"id"}),
43+
* }
44+
* )
45+
* )
46+
* @ORM\Entity
47+
*/
48+
class User
49+
{
50+
public const TABLE_NAME = 'user';
51+
public const IDX_FOO = 'IDX_FOO';
52+
53+
// ...
54+
}
55+
```
56+
57+
### Combining multiple hints:
58+
59+
You might need to give MySQL a list of possible indexes or hint it not to use some indices.
60+
As you can see, hinting joined tables is equally simple.
61+
62+
```php
63+
->from(User::class, 'u')
64+
->join('u.account', 'a')
65+
->getQuery()
66+
->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, UseIndexSqlWalker::class)
67+
->setHint(UseIndexSqlWalker::class, [
68+
IndexHint::use(Account::IDX_1, Account::TABLE_NAME),
69+
IndexHint::use(Account::IDX_2, Account::TABLE_NAME),
70+
IndexHint::ignore(Account::IDX_3, Account::TABLE_NAME),
71+
IndexHint::ignore(Account::IDX_4, Account::TABLE_NAME),
72+
])
73+
```
74+
75+
Produces this SQL:
76+
77+
```mysql
78+
FROM user u0_
79+
JOIN account a1_ IGNORE INDEX (IDX_3, IDX_4) USE INDEX (IDX_1, IDX_2) ON (...)
80+
```
81+
82+
### Hinting table joined multiple times:
83+
84+
You might need to hint only specific join of certain table. Just add which DQL alias specifies it as third argument.
85+
86+
```php
87+
->from(User::class, 'u')
88+
->join('u.account', 'a1')
89+
->join('u.anotherAccount', 'a2')
90+
->getQuery()
91+
->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, UseIndexSqlWalker::class)
92+
->setHint(UseIndexSqlWalker::class, [
93+
IndexHint::use(Account::IDX_1, Account::TABLE_NAME, 'a1'), // alias needed
94+
])
95+
```
96+
97+
Produces this SQL:
98+
99+
```mysql
100+
FROM user u0_
101+
JOIN account a1_ USE INDEX (IDX_1) ON (...)
102+
JOIN account a2_ ON (...)
103+
```
104+
105+
### Advanced usage notes
106+
- It works even for tables that are not present in the DQL, but are present in SQL!
107+
- For example parent table from [class table inheritance](https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/reference/inheritance-mapping.html#class-table-inheritance) when selecting children
108+
- Any invalid usage is checked in runtime
109+
- Table name existence is checked, so you just cannot swap `tableName` and `indexName` parameters by accident or use non-existing DQL alias
110+
- Forgotten hint or invalid arguments are also checked
111+
- Since those checks cannot be caught by any static analysis tool, it is recommended to have a test for every query
112+
113+

composer.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "shipmonk/doctrine-mysql-index-hints",
3+
"description": "Custom SQL walker for Doctrine allowing usage of MySQL index hints without need of native queries",
4+
"license": ["MIT"],
5+
"require": {
6+
"php": ">=7.1.0",
7+
"doctrine/orm": ">=2.5.0",
8+
"nette/utils": ">=2.4.0"
9+
},
10+
"require-dev": {
11+
"editorconfig-checker/editorconfig-checker": "^10.2",
12+
"phpstan/phpstan": "^0.12.94",
13+
"phpstan/phpstan-phpunit": "^0.12.22",
14+
"phpstan/phpstan-strict-rules": "^0.12.10",
15+
"phpunit/phpunit": "^9.5",
16+
"slevomat/coding-standard": "^7.0"
17+
},
18+
"config": {
19+
"sort-packages": true
20+
},
21+
"autoload": {
22+
"psr-4": {
23+
"ShipMonk\\": "src/"
24+
}
25+
},
26+
"autoload-dev": {
27+
"psr-4": {
28+
"ShipMonk\\": "tests/"
29+
}
30+
},
31+
"scripts": {
32+
"check": "ec && phpcs && phpstan analyse -vvv && phpunit -vvv tests"
33+
}
34+
}

0 commit comments

Comments
 (0)