diff --git a/changelog.md b/changelog.md index 2bf0dac8..9c5cb8cc 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ Features * Add warnings-count prompt format strings: `\w` and `\W`. * Handle/document more attributes in the `[colors]` section of `~/.myclirc`. * Enable customization of table border color/attributes in `~/.myclirc`. +* Complete much more precisely in the "value" position. Bug Fixes diff --git a/mycli/packages/completion_engine.py b/mycli/packages/completion_engine.py index 6d8258b5..4ef140af 100644 --- a/mycli/packages/completion_engine.py +++ b/mycli/packages/completion_engine.py @@ -384,7 +384,9 @@ def suggest_based_on_last_token( {"type": "view", "schema": parent}, {"type": "function", "schema": parent}, ] - else: + elif is_inside_quotes(text_before_cursor, -1) == 'backtick': + # todo: this should be revised, since we complete too exuberantly within + # backticks, including keywords aliases = [alias or table for (schema, table, alias) in tables] return [ {"type": "column", "tables": tables}, @@ -392,6 +394,13 @@ def suggest_based_on_last_token( {"type": "alias", "aliases": aliases}, {"type": "keyword"}, ] + else: + aliases = [alias or table for (schema, table, alias) in tables] + return [ + {"type": "column", "tables": tables}, + {"type": "function", "schema": []}, + {"type": "alias", "aliases": aliases}, + ] elif ( (token_v.endswith("join") and isinstance(token, Token) and token.is_keyword) or (token_v in ("copy", "from", "update", "into", "describe", "truncate", "desc", "explain")) diff --git a/mycli/sqlcompleter.py b/mycli/sqlcompleter.py index de618c2f..130b7996 100644 --- a/mycli/sqlcompleter.py +++ b/mycli/sqlcompleter.py @@ -743,7 +743,113 @@ class SQLCompleter(Completer): "ZEROFILL", ] - functions = [x.upper() for x in MYSQL_FUNCTIONS] + # misclassified as keywords + # do they need to also be subtracted from keywords? + pygments_misclassified_functions = ( + 'ASCII', + 'AVG', + 'CHARSET', + 'COALESCE', + 'COLLATION', + 'CONVERT', + 'CUME_DIST', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DATABASE', + 'DAY', + 'DEFAULT', + 'DENSE_RANK', + 'EXISTS', + 'FIRST_VALUE', + 'FORMAT', + 'GEOMCOLLECTION', + 'GET_FORMAT', + 'GROUPING', + 'HOUR', + 'IF', + 'INSERT', + 'INTERVAL', + 'JSON_TABLE', + 'JSON_VALUE', + 'LAG', + 'LAST_VALUE', + 'LEAD', + 'LEFT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'MATCH', + 'MICROSECOND', + 'MINUTE', + 'MOD', + 'MONTH', + 'NTH_VALUE', + 'NTILE', + 'PERCENT_RANK', + 'QUARTER', + 'RANK', + 'REPEAT', + 'REPLACE', + 'REVERSE', + 'RIGHT', + 'ROW_COUNT', + 'ROW_NUMBER', + 'SCHEMA', + 'SECOND', + 'TIMESTAMPADD', + 'TIMESTAMPDIFF', + 'TRUNCATE', + 'USER', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'WEEK', + 'WEIGHT_STRING', + ) + + pygments_missing_functions = ( + 'BINARY', # deprecated function, but available everywhere + 'CHAR', + 'DATE', + 'DISTANCE', + 'ETAG', + 'GeometryCollection', + 'JSON_DUALITY_OBJECT', + 'LineString', + 'MultiLineString', + 'MultiPoint', + 'MultiPolygon', + 'Point', + 'Polygon', + 'STRING_TO_VECTOR', + 'TIME', + 'TIMESTAMP', + 'VECTOR_DIM', + 'VECTOR_TO_STRING', + 'YEAR', + ) + + # so far an incomplete list + # these should be spun out and completed independently from functions + pygments_value_position_nonfunction_keywords = ( + 'BETWEEN', + 'CASE', + 'FALSE', + 'NOT', + 'NULL', + 'TRUE', + ) + + # should https://dev.mysql.com/doc/refman/9.6/en/loadable-function-reference.html also be added? + functions = sorted({ + x.upper() + for x in MYSQL_FUNCTIONS + + pygments_misclassified_functions + + pygments_missing_functions + + pygments_value_position_nonfunction_keywords + }) # https://docs.pingcap.com/tidb/dev/tidb-functions tidb_functions = [ diff --git a/test/test_completion_engine.py b/test/test_completion_engine.py index 7b1c9f60..06720e36 100644 --- a/test/test_completion_engine.py +++ b/test/test_completion_engine.py @@ -21,7 +21,6 @@ def test_select_suggests_cols_with_visible_table_scope(): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [(None, "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -31,7 +30,6 @@ def test_select_suggests_cols_with_qualified_table_scope(): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [("sch", "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -55,7 +53,6 @@ def test_where_suggests_columns_functions(expression): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [(None, "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -67,7 +64,6 @@ def test_where_equals_suggests_enum_values_first(): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [(None, "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -84,7 +80,6 @@ def test_where_in_suggests_columns(expression): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [(None, "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -95,7 +90,6 @@ def test_where_equals_any_suggests_columns_or_keywords(): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [(None, "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -120,7 +114,6 @@ def test_select_suggests_cols_and_funcs(): {"type": "alias", "aliases": []}, {"type": "column", "tables": []}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -193,7 +186,6 @@ def test_col_comma_suggests_cols(): {"type": "alias", "aliases": ["tbl"]}, {"type": "column", "tables": [(None, "tbl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -236,7 +228,6 @@ def test_partially_typed_col_name_suggests_col_names(): {"type": "alias", "aliases": ["tabl"]}, {"type": "column", "tables": [(None, "tabl", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -331,7 +322,6 @@ def test_sub_select_col_name_completion(): {"type": "alias", "aliases": ["abc"]}, {"type": "column", "tables": [(None, "abc", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -484,7 +474,6 @@ def test_2_statements_2nd_current(): {"type": "alias", "aliases": ["b"]}, {"type": "column", "tables": [(None, "b", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) # Should work even if first statement is invalid @@ -509,7 +498,6 @@ def test_2_statements_1st_current(): {"type": "alias", "aliases": ["a"]}, {"type": "column", "tables": [(None, "a", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) @@ -526,7 +514,6 @@ def test_3_statements_2nd_current(): {"type": "alias", "aliases": ["b"]}, {"type": "column", "tables": [(None, "b", None)]}, {"type": "function", "schema": []}, - {"type": "keyword"}, ]) diff --git a/test/test_smart_completion_public_schema_only.py b/test/test_smart_completion_public_schema_only.py index ca6ce245..b0326a5b 100644 --- a/test/test_smart_completion_public_schema_only.py +++ b/test/test_smart_completion_public_schema_only.py @@ -199,75 +199,11 @@ def test_function_name_completion(completer, complete_event): assert list(result) == [ Completion(text='MAX', start_position=-2), Completion(text='MATCH', start_position=-2), - Completion(text='MASTER', start_position=-2), - Completion(text='MAKE_SET', start_position=-2), Completion(text='MAKEDATE', start_position=-2), Completion(text='MAKETIME', start_position=-2), - Completion(text='MAX_ROWS', start_position=-2), - Completion(text='MAX_SIZE', start_position=-2), - Completion(text='MAXVALUE', start_position=-2), - Completion(text='MASTER_SSL', start_position=-2), - Completion(text='MASTER_BIND', start_position=-2), - Completion(text='MASTER_HOST', start_position=-2), - Completion(text='MASTER_PORT', start_position=-2), - Completion(text='MASTER_USER', start_position=-2), - Completion(text='MASTER_DELAY', start_position=-2), - Completion(text='MASTER_SSL_CA', start_position=-2), - Completion(text='MASTER_LOG_POS', start_position=-2), - Completion(text='MASTER_SSL_CRL', start_position=-2), - Completion(text='MASTER_SSL_KEY', start_position=-2), + Completion(text='MAKE_SET', start_position=-2), Completion(text='MASTER_POS_WAIT', start_position=-2), - Completion(text='MASTER_LOG_FILE', start_position=-2), - Completion(text='MASTER_PASSWORD', start_position=-2), - Completion(text='MASTER_SSL_CERT', start_position=-2), - Completion(text='MASTER_SSL_CAPATH', start_position=-2), - Completion(text='MASTER_SSL_CIPHER', start_position=-2), - Completion(text='MASTER_RETRY_COUNT', start_position=-2), - Completion(text='MASTER_SSL_CRLPATH', start_position=-2), - Completion(text='MASTER_TLS_VERSION', start_position=-2), - Completion(text='MASTER_AUTO_POSITION', start_position=-2), - Completion(text='MASTER_CONNECT_RETRY', start_position=-2), - Completion(text='MAX_QUERIES_PER_HOUR', start_position=-2), - Completion(text='MAX_UPDATES_PER_HOUR', start_position=-2), - Completion(text='MAX_USER_CONNECTIONS', start_position=-2), - Completion(text='MASTER_PUBLIC_KEY_PATH', start_position=-2), - Completion(text='MASTER_HEARTBEAT_PERIOD', start_position=-2), - Completion(text='MASTER_TLS_CIPHERSUITES', start_position=-2), - Completion(text='MAX_CONNECTIONS_PER_HOUR', start_position=-2), - Completion(text='MASTER_COMPRESSION_ALGORITHMS', start_position=-2), - Completion(text='MASTER_SSL_VERIFY_SERVER_CERT', start_position=-2), - Completion(text='MASTER_ZSTD_COMPRESSION_LEVEL', start_position=-2), Completion(text='email', start_position=-2), - Completion(text='DECIMAL', start_position=-2), - Completion(text='SMALLINT', start_position=-2), - Completion(text='TIMESTAMP', start_position=-2), - Completion(text='COLUMN_FORMAT', start_position=-2), - Completion(text='COLUMN_NAME', start_position=-2), - Completion(text='COMPACT', start_position=-2), - Completion(text='CONSTRAINT_SCHEMA', start_position=-2), - Completion(text='CURRENT_TIMESTAMP', start_position=-2), - Completion(text='FORMAT', start_position=-2), - Completion(text='GET_FORMAT', start_position=-2), - Completion(text='GET_MASTER_PUBLIC_KEY', start_position=-2), - Completion(text='LOCALTIMESTAMP', start_position=-2), - Completion(text='MESSAGE_TEXT', start_position=-2), - Completion(text='MIGRATE', start_position=-2), - Completion(text='NETWORK_NAMESPACE', start_position=-2), - Completion(text='PRIMARY', start_position=-2), - Completion(text='REQUIRE_ROW_FORMAT', start_position=-2), - Completion(text='REQUIRE_TABLE_PRIMARY_KEY_CHECK', start_position=-2), - Completion(text='ROW_FORMAT', start_position=-2), - Completion(text='SCHEMA', start_position=-2), - Completion(text='SCHEMA_NAME', start_position=-2), - Completion(text='SCHEMAS', start_position=-2), - Completion(text='SQL_SMALL_RESULT', start_position=-2), - Completion(text='TEMPORARY', start_position=-2), - Completion(text='TEMPTABLE', start_position=-2), - Completion(text='TERMINATED', start_position=-2), - Completion(text='TIMESTAMPADD', start_position=-2), - Completion(text='TIMESTAMPDIFF', start_position=-2), - Completion(text='UTC_TIMESTAMP', start_position=-2), - Completion(text='CHANGE MASTER TO', start_position=-2), ] @@ -292,12 +228,11 @@ def test_suggested_column_names(completer, complete_event): ] + list(map(Completion, completer.functions)) + [Completion(text="users", start_position=0)] - + [x for x in map(Completion, completer.keywords) if x.text not in completer.functions] ) def test_suggested_column_names_empty_db(empty_completer, complete_event): - """Suggest * and function/keywords when selecting from no-table db. + """Suggest * and function when selecting from no-table db. :param empty_completer: :param complete_event: @@ -312,7 +247,6 @@ def test_suggested_column_names_empty_db(empty_completer, complete_event): Completion(text="*", start_position=0), ] + list(map(Completion, empty_completer.functions)) - + [x for x in map(Completion, empty_completer.keywords) if x.text not in empty_completer.functions] ) @@ -399,7 +333,6 @@ def test_suggested_multiple_column_names(completer, complete_event): ] + list(map(Completion, completer.functions)) + [Completion(text="u", start_position=0)] - + [x for x in map(Completion, completer.keywords) if x.text not in completer.functions] ) @@ -551,7 +484,6 @@ def test_auto_escaped_col_names(completer, complete_event): ] + completer.functions + ["select"] - + [x for x in completer.keywords if x not in completer.functions] ) assert result == expected @@ -565,7 +497,7 @@ def test_un_escaped_table_names(completer, complete_event): "id", "`insert`", "ABC", - ] + completer.functions + ["réveillé"] + [x for x in completer.keywords if x not in completer.functions] + ] + completer.functions + ["réveillé"] # todo: the fixtures are insufficient; the database name should also appear in the result @@ -647,14 +579,12 @@ def test_file_name_completion(completer, complete_event, text, expected): def test_auto_case_heuristic(completer, complete_event): - text = "select jon_" - position = len("select jon_") + text = "select json_v" + position = len("select json_v") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) assert [x.text for x in result] == [ - 'json_table', + 'json_valid', 'json_value', - 'join', - 'json', ] @@ -817,16 +747,17 @@ def test_backticked_column_completion_two_character(completer, complete_event): Completion(text='`fast`', start_position=-2), Completion(text='`file`', start_position=-2), Completion(text='`full`', start_position=-2), + Completion(text='`false`', start_position=-2), Completion(text='`field`', start_position=-2), Completion(text='`floor`', start_position=-2), Completion(text='`fixed`', start_position=-2), Completion(text='`float`', start_position=-2), - Completion(text='`false`', start_position=-2), Completion(text='`fetch`', start_position=-2), Completion(text='`first`', start_position=-2), Completion(text='`flush`', start_position=-2), Completion(text='`force`', start_position=-2), Completion(text='`found`', start_position=-2), + Completion(text='`format`', start_position=-2), Completion(text='`float4`', start_position=-2), Completion(text='`float8`', start_position=-2), Completion(text='`factor`', start_position=-2), @@ -834,7 +765,6 @@ def test_backticked_column_completion_two_character(completer, complete_event): Completion(text='`fields`', start_position=-2), Completion(text='`filter`', start_position=-2), Completion(text='`finish`', start_position=-2), - Completion(text='`format`', start_position=-2), Completion(text='`follows`', start_position=-2), Completion(text='`foreign`', start_position=-2), Completion(text='`fulltext`', start_position=-2), @@ -844,8 +774,8 @@ def test_backticked_column_completion_two_character(completer, complete_event): Completion(text='`first_name`', start_position=-2), Completion(text='`found_rows`', start_position=-2), Completion(text='`find_in_set`', start_position=-2), - Completion(text='`from_base64`', start_position=-2), Completion(text='`first_value`', start_position=-2), + Completion(text='`from_base64`', start_position=-2), Completion(text='`foreign key`', start_position=-2), Completion(text='`format_bytes`', start_position=-2), Completion(text='`from_unixtime`', start_position=-2),