summaryrefslogtreecommitdiffstats
path: root/mycli/completion_refresher.py
blob: 5d5f40fc28bca08716b7cf9b7106634e93bdf314 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import threading
from .packages.special.main import COMMANDS
from collections import OrderedDict

from .sqlcompleter import SQLCompleter
from .sqlexecute import SQLExecute, ServerSpecies

class CompletionRefresher(object):

    refreshers = OrderedDict()

    def __init__(self):
        self._completer_thread = None
        self._restart_refresh = threading.Event()

    def refresh(self, executor, callbacks, completer_options=None):
        """Creates a SQLCompleter object and populates it with the relevant
        completion suggestions in a background thread.

        executor - SQLExecute object, used to extract the credentials to connect
                   to the database.
        callbacks - A function or a list of functions to call after the thread
                    has completed the refresh. The newly created completion
                    object will be passed in as an argument to each callback.
        completer_options - dict of options to pass to SQLCompleter.

        """
        if completer_options is None:
            completer_options = {}

        if self.is_refreshing():
            self._restart_refresh.set()
            return [(None, None, None, 'Auto-completion refresh restarted.')]
        else:
            self._completer_thread = threading.Thread(
                target=self._bg_refresh,
                args=(executor, callbacks, completer_options),
                name='completion_refresh')
            self._completer_thread.daemon = True
            self._completer_thread.start()
            return [(None, None, None,
                     'Auto-completion refresh started in the background.')]

    def is_refreshing(self):
        return self._completer_thread and self._completer_thread.is_alive()

    def _bg_refresh(self, sqlexecute, callbacks, completer_options):
        completer = SQLCompleter(**completer_options)

        # Create a new pgexecute method to populate the completions.
        e = sqlexecute
        executor = SQLExecute(e.dbname, e.user, e.password, e.host, e.port,
                              e.socket, e.charset, e.local_infile, e.ssl,
                              e.ssh_user, e.ssh_host, e.ssh_port,
                              e.ssh_password, e.ssh_key_filename)

        # If callbacks is a single function then push it into a list.
        if callable(callbacks):
            callbacks = [callbacks]

        while 1:
            for refresher in self.refreshers.values():
                refresher(completer, executor)
                if self._restart_refresh.is_set():
                    self._restart_refresh.clear()
                    break
            else:
                # Break out of while loop if the for loop finishes natually
                # without hitting the break statement.
                break

            # Start over the refresh from the beginning if the for loop hit the
            # break statement.
            continue

        for callback in callbacks:
            callback(completer)

def refresher(name, refreshers=CompletionRefresher.refreshers):
    """Decorator to add the decorated function to the dictionary of
    refreshers. Any function decorated with a @refresher will be executed as
    part of the completion refresh routine."""
    def wrapper(wrapped):
        refreshers[name] = wrapped
        return wrapped
    return wrapper

@refresher('databases')
def refresh_databases(completer, executor):
    completer.extend_database_names(executor.databases())

@refresher('schemata')
def refresh_schemata(completer, executor):
    # schemata - In MySQL Schema is the same as database. But for mycli
    # schemata will be the name of the current database.
    completer.extend_schemata(executor.dbname)
    completer.set_dbname(executor.dbname)

@refresher('tables')
def refresh_tables(completer, executor):
    completer.extend_relations(executor.tables(), kind='tables')
    completer.extend_columns(executor.table_columns(), kind='tables')

@refresher('users')
def refresh_users(completer, executor):
    completer.extend_users(executor.users())

# @refresher('views')
# def refresh_views(completer, executor):
#     completer.extend_relations(executor.views(), kind='views')
#     completer.extend_columns(executor.view_columns(), kind='views')

@refresher('functions')
def refresh_functions(completer, executor):
    completer.extend_functions(executor.functions())
    if executor.server_info.species == ServerSpecies.TiDB:
        completer.extend_functions(completer.tidb_functions, builtin=True)

@refresher('special_commands')
def refresh_special(completer, executor):
    completer.extend_special_commands(COMMANDS.keys())

@refresher('show_commands')
def refresh_show_commands(completer, executor):
    completer.extend_show_items(executor.show_candidates())

@refresher('keywords')
def refresh_keywords(completer, executor):
    if executor.server_info.species == ServerSpecies.TiDB:
        completer.extend_keywords(completer.tidb_keywords, replace=True)