-
Notifications
You must be signed in to change notification settings - Fork 821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQLSelect regression with subqueries #11276
Comments
You say this is a regression - what is the latest version you saw this working in? |
We're on version 4.13.42 of the framework I believe. Upgrading straight to 5.2 |
@marwan38 the example SQL ( |
It's mentioned just above the example,.. my mistake I've updated it. An important note right below is that it's not exactly a "possible solution" but rather something I discovered which may help debug the issue. |
I'm going to move that into the "additional context" section then - since that's what you're saying it is. |
I've been asked to look into fixing this at my new job. So I'll have glance to see how difficult - or not difficult it is. |
I'm making a start on this. First, I just replicated the behaviour. Adding this unit test to public function testSubqueries()
{
$query = new SQLSelect('*', '( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO" ) as FINAL');
$this->assertSQLEquals(
'SELECT * FROM ( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO" ) as FINAL',
$query->sql()
);
} The test passes on
|
Here's what I found so far. When did the issue get introducedThis behaviour was introduced by this PR: #10462 From the unit test that got added, it looks like we were trying to resolved an issue where someone would define an alias in the subquery's SQL and in the array key. Doesn't look like this was meant to affect the scenario where a SQLSelect is created with a Why this is happeningLet's say you instanciate an SQLSelect this way: The The problem is that if you provide a string, the key is assumed to be equal to the string. Presumably, this behaviour is there so that [
"'( SELECT DISTINCT \"SQLSelectTest_DO\".\"ClassName\" FROM \"SQLSelectTest_DO\" ) as FINAL')" =>
"'( SELECT DISTINCT \"SQLSelectTest_DO\".\"ClassName\" FROM \"SQLSelectTest_DO\" ) as FINAL')"
] That wasn't a problem in CMS 4 because the key was ignored if the subquery already contained an Bypassing the problemIf you wrap the sub query string in an array, it's happy because In the example below, all the scenarios except public function subqueryProvider()
{
return [
'no-alias-string' => ['( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO") AS "FINAL"'],
'no-alias-array' => [['( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO") AS "FINAL"']],
'no-alias-array-numeric-key' => [[0 => '( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO") AS "FINAL"']],
'explicit-alias-string' => [['FINAL' => '( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO")']],
];
}
/**
* @dataProvider subqueryProvider
*/
public function testSubqueries($subquery)
{
// var_dump($subquery);
$query = new SQLSelect('*', $subquery);
$actualSQL = $query->sql();
$this->assertSQLEquals(
'SELECT * FROM ( SELECT DISTINCT "SQLSelectTest_DO"."ClassName" FROM "SQLSelectTest_DO") AS "FINAL"',
$actualSQL
);
} Questions or Possible solution
Assuming we agree that this is behaviour is a bug, then the next step would be to decide how to fix it:
I'm leaning towards option 2, but that may have a lot of other implications, which I'm not aware of. |
Great investigation. Yup sounds like a bug to me. IMO reverting isn't really viable, so some variation of option 2 sounds good. Haven't fully wrapped my head around it so I'm not sure if there are edge cases where that'll cause a new problem, but it still sounds like the better way forward even with that possibility. |
I'm pretty sure it's coming from this line:
I'll try hacking the line and I'll throw the kitchen sink at it. We'll see what breaks. |
Here's my initial results. Just documenting here so I remember where I got up to. Try number 1First I tried doing this change to - $this->from[str_replace(['"','`'], '', $from)] = $from;
+ $this->from[] = $from; My assumption was that the alias wouldn't matter because it would be equal to the table name anyway. However, Versioned didn't like that. Versioned is apparently very concerned about those table aliases. This is the specific Versioned bit that appear to trip up my test, but there's other places in Versioned that do stuff to the table aliases. Not obviously clear to me why Versioned works this way. Try number twoFor try number 2, decided to retain the current logic for Other $from = trim($from);
if (preg_match('/^"?([a-zA-Z0-9_]+)"?$/', $from)) {
$this->from[str_replace(['"','`'], '', $from)] = $from;
} else {
$this->from[] = $from;
} I ran builds on those changes:
|
Could the second approach open up some SQL injection vectors if we use it as it is? |
@michalkleiner I don't think so ... or more accurately, it's not introducing any SQL Injection vector that wouldn't be there already.
|
My kitchen-sink build mostly passes. The PHP 8.3 fails because I had to lock to PHP 8.1, so that's expected. There's a few Behat failures, but those are on the regular 5.2 kitchen-sink as well ... apparently some people aren't keeping up on top of their build dashboard since their boss quit on them 😜. I'll do a PR with my changes. |
Pull request ready for review: #11312 |
PR merged. Will be automatically tagged by GitHub Actions. |
Module version(s) affected
5
Description
Regression within the SQLSelect class causes sub queries to be duplicated. This can be very easily reproduced in a blank silverstripe project.
How to reproduce
The regression itself:
Creating the select statement
Output
Note that the subquery is being duplicated.
Possible Solution
no response
Additional Context
I noticed that the issue cannot be reproduced when double quotations (") are left out of the query. Following the same reproduction steps WITHOUT the double quotations results in the correct output:
Output
The reproduction code is an MVP, our actual use case is a little bit more complex with additional where statements, and froms. This worked just fine in silverstripe v4. A closer reproduction would be something like
Output (Note the duplication after the aliasing "AS FINAL AS "...."):
Changing the alias to be resolved as the array index, like
resolves the same way.
Validations
silverstripe/installer
(with any code examples you've provided)Pull requests
The text was updated successfully, but these errors were encountered: