-
Notifications
You must be signed in to change notification settings - Fork 11.3k
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
[9.x] Fix postgres reference parsing #35530
[9.x] Fix postgres reference parsing #35530
Conversation
Primarily, these changes "normalize" the search_path parsing behavior such that the new parseSearchPath() method returns the same result whether the search_path input is an array (with one or more schemas), a string with one schema, or a string of comma-separated schemas. This method's presence makes it much simpler to retrieve the search_path as configured on the connection and use it for more complex schema-related grammar construction.
The manner in which object references were parsed in certain scenarios caused methods such as hasTable() to return incorrect results. Among other issues, the underlying SQL grammar omitted the "table_catalog" (i.e., database) in the WHERE clause, which caused inaccurate results in certain cases, e.g., the method returned true incorrectly because a schema and table with the same name exist in a *different database*.
Just FYI: Taylor doesn't reviews draft PRs so once you're ready with this feel free to mark it as ready for review. |
@driesvints Thanks! In the meantime, is your inclination that this will be closed unless I add tests to cover anything I changed or added, even though I'm only aiming to fix code that was effectively broken and with no existing test coverage, and not add any new features? I suppose what I'm asking is, should I add tests before I mark this as ready for review? :) |
Adding tests is always a good idea yeah. |
Please note that if merged, laravel/ideas#918 is satisfied and complete in my estimation. |
Does this PR introduce any additional breaking changes? |
@taylorotwell No, I don't believe that this PR introduces any additional breaking changes. |
So just on my cursory review, it looks like if I did a query like |
@taylorotwell Yes, that's correct, and yes, that's how PostgreSQL has always behaved in Laravel, and that is the correct and expected behavior. And just to be clear, this PR doesn't change that behavior. Edit to add: To clarify this point, in PostgreSQL, tables reside within a schema, not a database, so one cannot query a database directly. When selecting records, one is always querying tables within a schema (not a database). That said, the effective user might have access to several different databases, each of which could have several different schemas, which is why the ability to qualify As a point of comparison, MySQL has no concept of "schema", and its references are qualified with In both databases, the qualifier (everything before the table name) is optional. In MySQL, when the database part is omitted, MySQL uses the database defined on the connection. In PostgreSQL, the same is true, but if the schema part is omitted, PostgreSQL consults the Primarily, this PR fixes how the framework determines what qualified reference to use when querying PostgreSQL's internal Just let me know if this doesn't answer your question fully, or if you have any others, and I'll be happy to respond. |
Preface
I'm submitting this to
master
because it relies on #35463 , which was also sent tomaster
due to breaking changes.Regarding tests, there are no existing tests that cover this functionality. While I can write tests to cover what I changed and added, it'll take me a while, and given the significance of these issues for the small number of users that they affect, my recommendation is to merge this, even without new tests, because the current implementation should be fixed with urgency. I'm willing to commit to writing the tests when I have more time, and propose them in a separate PR.(UPDATE: I added tests.)
I should mention also that I inadvertently introduced a slight regression (undefined variable warning) on
framework/src/Illuminate/Database/Schema/PostgresBuilder.php
Line 167 in a974ad6
Substance
This PR is essentially "part 2 of 2" relative to the above-mentioned PR, in that it "normalizes" the search_path parsing behavior such that the new
parseSearchPath()
method returns the same result whether thesearch_path
input is an array (with one or more schemas), a string with one schema, or a string of comma-separated schemas. This method's presence makes it much simpler to retrieve thesearch_path
as configured on the connection and use it for more complex schema-related grammar construction.This PR also fixes several issues with the
parseSchemaAndTable()
method.If the database object reference is unqualified (i.e., the
$table
string does not contain a schema +.
prefix), the existing logic is reasonably sound. But if the reference is qualified, there are a few issues that arise.Assuming the input
$table
is'public.users'
:search_path
is the default,'public'
(or if it isnull
, in which case'public'
is used), the following query is executed, which will never return a result, because thepublic.
portion is never removed from the table name:For this logic to be sound, if the schema is found in the table reference, it should be removed, e.g.:
And furthermore, PostgreSQL table names cannot contain periods, so there's just no reason for a period ever to be included in the table name binding here.
But there's still another issue, which is that the database name is not considered, neither in this method nor in the SQL grammar that is used to construct the queries for
hasTable($table)
andgetColumnListing($table)
.This omission is problematic because it causes these methods to return inaccurate results in some cases. For example, failing to qualify these queries with a database name (in the
table_catalog
column on theWHERE
clause) could causehasTable($table)
to returntrue
just because another/different database has a schema and table combination with the same names.Seeking Feedback...
I don't like the fact that I added identical
parseSearchPath($searchPath)
methods to both thePostgresConnetor
and thePostgresBuilder
classes. Is there a better location for this method that both of these classes can leverage without adding new dependencies? This method is essentially a "utility function" that ingests any reasonably-formattedsearch_path
(list of schemas) from the connection's configuration and returns a neatly-formatted array of schemas that can be queried orimplode()
d, for example.Also, are the comments in the
parseSchemaAndTable()
method helpful? Sure, they seem self-evident, but the underlying logic here is deceptively complicated, due to the complexities inherent to PostgreSQL'ssearch_path
related nuances, so I thought some explicit commentary might be warranted.Finally, that method should perhaps be renamed to
parseDatabaseSchemaAndTable()
orparseDatabaseObjectReference()
, or something that better reflects what it actually does. I say this specifically because the method now supports a database part. For example, if the$table
input ishomestead.laravel.users
, thehomestead
portion is actually used and passed to the underlyinginformation_schema
query (and if it's not passed, thedatabase
configuration parameter on the connection is used). So, technically, the method is parsing a full database object reference, and not a "schema and a table", per se.