Skip to content

Fix FunctionMetadata.fields() crash when arg_names is None - #1638

Open
Tatamis wants to merge 2 commits into
dbcli:mainfrom
Tatamis:fix/function-metadata-fields-none-arg-names
Open

Tatamis wants to merge 2 commits into
dbcli:mainfrom
Tatamis:fix/function-metadata-fields-none-arg-names

Conversation

@Tatamis

@Tatamis Tatamis commented Sep 17, 2026

Copy link
Copy Markdown

Fixes #1204.

Bug

Autocompleting a call to a table-returning or variadic function whose parameters have no names (e.g. an unnamed variadic text[] argument, as in the reported hstore/labels(variadic text[]) example) crashes the completion thread:

File ".../pgcli/pgcompleter.py", line 985, in populate_scoped_cols
    cols = func.fields()
File ".../pgcli/packages/parseutils/meta.py", line 168, in fields
    for name, typ, mode in zip(self.arg_names, self.arg_types, self.arg_modes)
Exception 'NoneType' object is not iterable

fields() only guards against a missing arg_modes (elif not self.arg_modes: ...), then zips arg_names/arg_types/arg_modes unconditionally. arg_modes being truthy doesn't guarantee arg_names is populated too — a function can have modes but no names, in which case arg_names stays None and the zip() raises before it ever gets to filter by mode.

Fix

Fall back to None placeholders for arg_names/arg_types the same way args() (a few lines up in the same class) already falls back for arg_modes, so the zip always gets iterables.

Test plan

  • Added test_function_metadata_fields_with_variadic_and_no_arg_names, directly reproducing the reported case via FunctionMetadata.
  • Added test_function_metadata_fields_table_mode_with_no_arg_names, checking a TABLE-mode function with unnamed columns returns sensible ColumnMetadata (not just an empty list).
  • Verified both fail with the exact reported TypeError against the unpatched code.
  • pytest tests/ --ignore=tests/features — 2575 passed, 1 pre-existing failure unrelated to this change (a Windows-only tempfile permission issue in an unrelated alias-map test), 1 xfailed, 1 xpassed.
  • ruff check / ruff format clean.

Fixes dbcli#1204. fields() guarded against a missing arg_modes, but zipped
arg_names/arg_types/arg_modes unconditionally otherwise. A function
with a truthy arg_modes but no arg_names (e.g. an unnamed variadic
parameter, as in the reported hstore/variadic example) hit
'NoneType' object is not iterable instead of being handled.

Fall back to None placeholders for arg_names/arg_types the same way
args() already falls back for arg_modes a few lines up.
DiegoDAF added a commit to DiegoDAF/pgcli.daf that referenced this pull request Sep 18, 2026
Written in June on a branch that never reached main, and recovered now
because upstream dbcli#1638 touches the same lines.

fields() zipped arg_names with arg_modes and bailed out when the names
were missing, so a function like

  labels(variadic text[]) returns hstore

offered no output column at all: "select labels from labels(...)" had
nothing to complete. The same happens for a set-returning function
declared with TABLE(...) and no argument names.

When no argument carries an output mode, the function name is used as
the column name, which is exactly what the branch above already does for
functions declared without output parameters. Guarding the zip also
removes the "'NoneType' object is not iterable" crash that upstream
still has; our fork returned an empty list instead of crashing, so the
symptom here was a silently missing completion rather than a traceback.

The version bump and changelog entry from the original commit were left
out: the version stays where it is and the entry is written for the
current Upcoming section.

5 tests, 3 of which fail without the change.
@DiegoDAF

Copy link
Copy Markdown
Contributor

Thanks for tracking this down. I hit the same crash in my fork a while back, so here is what I measured against your branch, in case it is useful.

The fix removes the TypeError in fields(), but the crash moves rather than disappears. With your branch applied, completing columns of a set-returning function declared with TABLE(...) and no argument names still fails, one layer further up:

upstream main   TypeError: 'NoneType' object is not iterable
                pgcli/packages/parseutils/meta.py:162, in fields

this PR         TypeError: 'NoneType' object is not iterable
                pgcli/pgcompleter.py:77, in generate_alias

The reason is that fields() now returns ColumnMetadata entries whose name is None:

f = FunctionMetadata("s", "f", None, ["int4", "text"], ["t", "t"], "record",
                     False, False, True, False, None)
f.fields()   # [(None, 'int4'), (None, 'text')]

and generate_alias() iterates that name. The variadic case in your tests returns [], so it never reaches that code and the tests pass.

What worked for me was to treat "no argument carries an output mode, or the names are missing" the same way the branch a few lines above already treats a function declared without output parameters: use the function name as the column name.

fields = [
    ColumnMetadata(name, typ, [])
    for name, typ, mode in zip(self.arg_names or [], self.arg_types or [], self.arg_modes)
    if mode in ("o", "b", "t")
]
return fields or [ColumnMetadata(self.func_name, self.return_type, [])]

That gives [('labels', 'hstore')] for labels(variadic text[]) returns hstore, so select labels from labels(...) completes the column instead of offering nothing, and the same probe that crashes above returns suggestions.

Either way the crash is worth fixing, so this is a suggestion on top of your change rather than an objection to it.

DiegoDAF pointed out that the earlier change only moved the crash:
for a TABLE(...) function declared without argument names, fields()
now returned ColumnMetadata entries with name=None, and
generate_alias() then failed iterating that None.

When arg_modes has no usable output column (unnamed TABLE columns, or
only a variadic parameter), fall back to the function name as the
column name, like the branch above already does for functions without
output parameters. The variadic case now completes 'labels' instead of
offering nothing, and the TABLE case no longer reaches generate_alias()
with None.
@Tatamis

Tatamis commented Sep 18, 2026

Copy link
Copy Markdown
Author

Thanks @DiegoDAF, you were right: with TABLE(...) and no argument names the crash just moved to generate_alias(). I reproduced it on the branch and pushed 2224ee0 with your suggestion: when no output column can be built from arg_names/arg_modes, fields() falls back to the function name, like the branch above it. The variadic case now returns [('labels', 'hstore')], and I added tests for the unnamed TABLE case (checking generate_alias() gets a string) and for named TABLE columns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NoneType exception in get_completions()

2 participants