From 9a086ef1ae5ce99c09cd1c9a96181bcebcecad46 Mon Sep 17 00:00:00 2001 From: Rob B Date: Fri, 11 Nov 2022 11:34:49 +0000 Subject: [PATCH] Allow defining a json file with preferred aliases At $WORK we have a lot of tables with names like `foo_noun_verb` or `foo_noun_related-noun_verb` and so while the default aliasing is very helpful for shortening unwieldy names we do end up with lots of aliases like `LEFT JOIN fnv on fnv2.id = fnv.fnv2_id` This change will allow defining a json file of preferred aliases ``` > cat ~/.config/pgcli/aliases.json { "foo_user": "user", "foo_user_group": "user_group" } ``` so the alias suggestion for `SELECT * FROM foo_user` will be `SELECT * FROM foo_user AS user` instead of the default `SELECT * FROM foo_user AS fu` --- changelog.rst | 5 ++++- pgcli/main.py | 1 + pgcli/pgclirc | 10 +++++++++- pgcli/pgcompleter.py | 13 ++++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/changelog.rst b/changelog.rst index 88b13419b..e83e2b339 100644 --- a/changelog.rst +++ b/changelog.rst @@ -4,10 +4,13 @@ Upcoming Features: --------- -* Changed the `destructive_warning` config to be a list of commands that are considered +* Changed the `destructive_warning` config to be a list of commands that are considered destructive. This would allow you to be warned on `create`, `grant`, or `insert` queries. * Destructive warnings will now include the alias dsn connection string name if provided (-D option). * pgcli.magic will now work with connection URLs that use TLS client certificates for authentication +* Allow specifying an `alias_map_file` in the config that will use + predetermined table aliases instead of generating aliases programmatically on + the fly 3.5.0 (2022/09/15): =================== diff --git a/pgcli/main.py b/pgcli/main.py index c5b5d48e9..e1064c11b 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -269,6 +269,7 @@ def __init__( "single_connection": single_connection, "less_chatty": less_chatty, "keyword_casing": keyword_casing, + "alias_map_file": c["main"]["alias_map_file"] or None, } completer = PGCompleter( diff --git a/pgcli/pgclirc b/pgcli/pgclirc index 98445c149..93ec3b0a6 100644 --- a/pgcli/pgclirc +++ b/pgcli/pgclirc @@ -24,7 +24,7 @@ multi_line_mode = psql # Destructive warning will alert you before executing a sql statement # that may cause harm to the database such as "drop table", "drop database", -# "shutdown", "delete", or "update". +# "shutdown", "delete", or "update". # You can pass a list of destructive commands or leave it empty if you want to skip all warnings. # "unconditional_update" will warn you of update statements that don't have a where clause destructive_warning = drop, shutdown, delete, truncate, alter, update, unconditional_update @@ -38,6 +38,14 @@ auto_expand = False # If set to True, table suggestions will include a table alias generate_aliases = False +# Path to a json file that specifies specific table aliases to use when generate_aliases is set to True +# the format for this file should be: +# { +# "some_table_name": "desired_alias", +# "some_other_table_name": "another_alias" +# } +alias_map_file = "" + # log_file location. # In Unix/Linux: ~/.config/pgcli/log # In Windows: %USERPROFILE%\AppData\Local\dbcli\pgcli\log diff --git a/pgcli/pgcompleter.py b/pgcli/pgcompleter.py index e66c3dc25..f7057f805 100644 --- a/pgcli/pgcompleter.py +++ b/pgcli/pgcompleter.py @@ -61,12 +61,14 @@ def Candidate( normalize_ref = lambda ref: ref if ref[0] == '"' else '"' + ref.lower() + '"' -def generate_alias(tbl): +def generate_alias(tbl, alias_map=None): """Generate a table alias, consisting of all upper-case letters in the table name, or, if there are no upper-case letters, the first letter + all letters preceded by _ param tbl - unescaped name of the table to alias """ + if alias_map and tbl in alias_map: + return alias_map[tbl] return "".join( [l for l in tbl if l.isupper()] or [l for l, prev in zip(tbl, "_" + tbl) if prev == "_" and l != "_"] @@ -100,6 +102,15 @@ def __init__(self, smart_completion=True, pgspecial=None, settings=None): self.call_arg_oneliner_max = settings.get("call_arg_oneliner_max", 2) self.search_path_filter = settings.get("search_path_filter") self.generate_aliases = settings.get("generate_aliases") + + # when should this file be loaded? IO in constructors is not my preference but slow startup is + # probably better than slow first query + alias_map_file = settings.get("alias_map_file") + if alias_map_file is not None: + with open(alias_map_file) as fo: + self.alias_map = json.load(fo) + else: + self.alias_map = None self.casing_file = settings.get("casing_file") self.insert_col_skip_patterns = [ re.compile(pattern)