Skip to content
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

Optimize BaseRelation.matches() #6844

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 13 additions & 26 deletions core/dbt/adapters/base/relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
Path,
)
from dbt.exceptions import (
ApproximateMatchError,
DbtInternalError,
MultipleDatabasesNotAllowedError,
)
Expand Down Expand Up @@ -77,36 +76,24 @@ def matches(
schema: Optional[str] = None,
identifier: Optional[str] = None,
) -> bool:
search = filter_null_values(
{
ComponentName.Database: database,
ComponentName.Schema: schema,
ComponentName.Identifier: identifier,
}
)

if not search:
# nothing was passed in
raise dbt.exceptions.DbtRuntimeError(
"Tried to match relation, but no search path was passed!"
)
if identifier is not None and not self._is_exactish_match(
ComponentName.Identifier, identifier
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the way it was originally written. I think the performance pickup comes from two places:

  1. not looking for the approximate match
  2. not exiting the for loop once a match was found
    I think it could look something like this:
if not any(identifier, schema, database):
    raise dbt.exceptions.DbtRuntimeError(...)

search = filter_null_values(
    {
        ComponentName.Identifier: identifier,
        ComponentName.Schema: schema,
        ComponentName.Database: database
    }
)

return any(
    (
        self._is_exactish_match(existing_components, new_component)
        for existing_components, new_component in search.items()
    )
)

I'm pretty sure any() will lazily evaluate each element in the generator and then stop when it finds one, which is what you're trying to do.

):
return False

exact_match = True
approximate_match = True
if schema is not None and not self._is_exactish_match(ComponentName.Schema, schema):
return False

for k, v in search.items():
if not self._is_exactish_match(k, v):
exact_match = False
if str(self.path.get_lowered_part(k)).strip(self.quote_character) != v.lower().strip(
self.quote_character
):
approximate_match = False # type: ignore[union-attr]
if database is not None and not self._is_exactish_match(ComponentName.Database, database):
return False

if approximate_match and not exact_match:
target = self.create(database=database, schema=schema, identifier=identifier)
raise ApproximateMatchError(target, self)
if database is None and schema is None and identifier is None:
raise dbt.exceptions.DbtRuntimeError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put this first since you don't need to run self._is_exactish_match(). I'm assuming that method is the expensive method. I'd also rephrase the if clause:

if not any(database, schema, identifier):
    raise dbt.exceptions.DbtRuntimeError(...)

"Tried to match relation, but no search path was passed!"
)

return exact_match
return True

def replace_path(self, **kwargs):
return self.replace(path=self.path.replace(**kwargs))
Expand Down