Skip to content

Commit 9b56a08

Browse files
committed
Improve SQL context loading
- add fallback in case there is no version match - do not zero one by one, byt by two to match version parts - better handle corner cases Fixes phpmyadmin/phpmyadmin#13728 Signed-off-by: Michal Čihař <[email protected]>
1 parent 849bbf5 commit 9b56a08

File tree

3 files changed

+52
-37
lines changed

3 files changed

+52
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
* Fix parsing of CREATE TABLE with per field COLLATE.
6+
* Improved Context::loadClosest to better deal with corner cases.
67

78
## [4.2.3] - 2017-10-10
89

src/Context.php

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -486,37 +486,31 @@ public static function load($context = '')
486486
*/
487487
public static function loadClosest($context = '')
488488
{
489-
/**
490-
* The number of replaces done by `preg_replace`.
491-
* This actually represents whether a new context was generated or not.
492-
*
493-
* @var int
494-
*/
495-
$count = 0;
496-
497-
// As long as a new context can be generated, we try to load it.
498-
do {
489+
$length = strlen($context);
490+
for ($i = $length; $i > 0;) {
499491
try {
500-
// Trying to load the new context.
492+
/* Trying to load the new context */
501493
static::load($context);
494+
return $context;
502495
} catch (LoaderException $e) {
503-
// If it didn't work, we are looking for a new one and skipping
504-
// over to the next generation that will try the new context.
505-
$context = preg_replace(
506-
'/[1-9](0*)$/',
507-
'0$1',
508-
$context,
509-
-1,
510-
$count
511-
);
512-
continue;
496+
/* Replace last two non zero digits by zeroes */
497+
do {
498+
$i -= 2;
499+
$part = substr($context, $i, 2);
500+
/* No more numeric parts to strip */
501+
if (! is_numeric($part)) {
502+
break 2;
503+
}
504+
} while (intval($part) === 0 && $i > 0);
505+
$context = substr($context, 0, $i) . '00' . substr($context, $i + 2);
513506
}
514-
515-
// Last generated context was valid (did not throw any exceptions).
516-
// So we return it, to let the user know what context was loaded.
517-
return $context;
518-
} while ($count !== 0);
519-
507+
}
508+
/* Fallback to loading at least matching engine */
509+
if (strncmp($context, 'MariaDb', 7) === 0) {
510+
return static::loadClosest('MariaDb100300');
511+
} elseif (strncmp($context, 'MySql', 5) === 0) {
512+
return static::loadClosest('MySql50700');
513+
}
520514
return null;
521515
}
522516

tests/Lexer/ContextTest.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,43 @@ public function testLoad()
1414
$this->assertTrue(isset(Context::$KEYWORDS['STORED']));
1515
$this->assertFalse(isset(Context::$KEYWORDS['AUTHORS']));
1616

17-
Context::load('MySql50600');
18-
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50600', Context::$loadedContext);
19-
$this->assertFalse(isset(Context::$KEYWORDS['STORED']));
20-
$this->assertTrue(isset(Context::$KEYWORDS['AUTHORS']));
21-
22-
Context::loadClosest('MySql50712');
23-
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50700', Context::$loadedContext);
24-
25-
$this->assertEquals(null, Context::loadClosest('Sql'));
26-
2717
// Restoring context.
2818
Context::load('');
2919
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50700', Context::$defaultContext);
3020
$this->assertTrue(isset(Context::$KEYWORDS['STORED']));
3121
$this->assertFalse(isset(Context::$KEYWORDS['AUTHORS']));
3222
}
3323

24+
/**
25+
* Test for loading closest SQL context
26+
*
27+
* @dataProvider contextLoading
28+
*/
29+
public function testLoadClosest($context, $expected)
30+
{
31+
$this->assertEquals($expected, Context::loadClosest($context));
32+
if (! is_null($expected)) {
33+
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\Context' . $expected, Context::$loadedContext);
34+
$this->assertTrue(class_exists(Context::$loadedContext));
35+
}
36+
37+
// Restoring context.
38+
Context::load('');
39+
}
40+
41+
public function contextLoading()
42+
{
43+
return [
44+
'MySQL match' => ['MySql50500', 'MySql50500'],
45+
'MySQL strip' => ['MySql50712', 'MySql50700'],
46+
'MySQL fallback' => ['MySql99999', 'MySql50700'],
47+
'MariaDB match' => ['MariaDb100000', 'MariaDb100000'],
48+
'MariaDB strip' => ['MariaDb109900', 'MariaDb100000'],
49+
'MariaDB fallback' => ['MariaDb990000', 'MariaDb100300'],
50+
'Invalid' => ['Sql', null],
51+
];
52+
}
53+
3454
/**
3555
* @dataProvider contextNames
3656
*

0 commit comments

Comments
 (0)