diff --git a/changelog.rst b/changelog.rst index 8864a88d2..45b22f062 100644 --- a/changelog.rst +++ b/changelog.rst @@ -4,6 +4,7 @@ Upcoming (TBD) Bug fixes: ---------- * Restore cursor shape behaviour for Emacs mode +* Suggest columns, not datatypes, after a column literally named ``type`` in a ``SELECT`` list. Features: --------- diff --git a/pgcli/packages/sqlcompletion.py b/pgcli/packages/sqlcompletion.py index d8996609b..2bb57e3d2 100644 --- a/pgcli/packages/sqlcompletion.py +++ b/pgcli/packages/sqlcompletion.py @@ -278,6 +278,29 @@ def suggest_special(text): return (Keyword(), Special()) +def _is_datatype_keyword(token, stmt): + """Decide whether a `type` token is the SQL TYPE keyword or a column name. + + `type` is a non-reserved word in postgres, so sqlparse tags a column + literally named "type" as a keyword. The TYPE keyword only introduces a + datatype in DDL contexts (`ALTER COLUMN bar TYPE`, `SET DATA TYPE`, + `CREATE TYPE`), never directly inside a DML select/insert/update list. + """ + if isinstance(token, str): + # An explicit internal request for datatype suggestions, e.g. the + # parenthesized column list in `CREATE TABLE foo (bar `. + return True + + if not token.is_keyword: + return False + + prev_keyword, _ = find_prev_keyword(stmt.text_before_cursor.rstrip(), n_skip=1) + if prev_keyword is None: + return False + + return prev_keyword.ttype is not sqlparse.tokens.Keyword.DML + + def suggest_based_on_last_token(token, stmt): if isinstance(token, str): token_v = token.lower() @@ -492,7 +515,7 @@ def suggest_based_on_last_token(token, stmt): return suggest_based_on_last_token(prev_keyword, stmt) else: return () - elif token_v in ("type", "::"): + elif token_v == "::" or (token_v == "type" and _is_datatype_keyword(token, stmt)): # ALTER TABLE foo SET DATA TYPE bar # SELECT foo::bar # Note that tables are a form of composite type in postgresql, so diff --git a/tests/test_sqlcompletion.py b/tests/test_sqlcompletion.py index f9fe9ca1a..9deb3455d 100644 --- a/tests/test_sqlcompletion.py +++ b/tests/test_sqlcompletion.py @@ -798,6 +798,23 @@ def test_alter_column_type_suggests_types(): } +@pytest.mark.parametrize( + "text", + [ + "SELECT type ", + "SELECT type, ", + "SELECT id, type, ", + ], +) +def test_column_named_type_still_suggests_columns(text): + # `type` is a non-reserved word, so sqlparse tags a column literally + # named "type" as the TYPE keyword; it must not switch the SELECT list + # over to datatype suggestions. + suggestions = suggest_type(text, text) + assert Column in {type(s) for s in suggestions} + assert Datatype(schema=None) not in suggestions + + @pytest.mark.parametrize( "text", [