Skip to content
This repository was archived by the owner on Mar 12, 2020. It is now read-only.

Commit 90e8086

Browse files
authored
Merge pull request #104 from mtxr/smart_completions_perfomance
Improved performance of smart completions
2 parents a101c8e + 0e529f6 commit 90e8086

File tree

1 file changed

+97
-88
lines changed

1 file changed

+97
-88
lines changed

SQLToolsAPI/Completion.py

Lines changed: 97 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,31 @@
1919
def _stripQuotes(ident):
2020
return ident.strip('"\'`')
2121

22+
2223
# used for formatting output
2324
def _stripQuotesOnDemand(ident, doStrip=True):
2425
if doStrip:
2526
return _stripQuotes(ident)
2627
return ident
2728

29+
2830
def _startsWithQuote(ident):
2931
# str.startswith can be matched against a tuple
3032
quotes = ('`', '"')
3133
return ident.startswith(quotes)
3234

35+
3336
def _stripPrefix(text, prefix):
3437
if text.startswith(prefix):
3538
return text[len(prefix):]
3639
return text
3740

3841

39-
class CompletionItem(namedtuple('CompletionItem', ['type', 'ident', 'score'])):
42+
class CompletionItem(namedtuple('CompletionItem', ['type', 'ident'])):
4043
"""
4144
Represents a potential or actual completion item.
4245
* type - Type of item e.g. (Table, Function, Column)
4346
* ident - identifier e.g. ("tablename.column", "database.table", "alias")
44-
* score - the lower score, the better is match for completion item
4547
"""
4648
__slots__ = ()
4749

@@ -100,9 +102,10 @@ def prefixMatchScore(self, search, exactly=False):
100102
targetList = target.split('.')
101103
targetObject = _stripQuotes(targetList.pop())
102104
targetParent = _stripQuotes(targetList.pop())
103-
if (searchParent == targetParent and
104-
self._stringMatched(targetObject, searchObject, exactly)):
105-
return 1 # highest score
105+
if (searchParent == targetParent):
106+
if self._stringMatched(targetObject, searchObject, exactly):
107+
return 1 # highest score
108+
return 0
106109

107110
# second part matches ?
108111
if '.' in target:
@@ -119,6 +122,13 @@ def prefixMatchScore(self, search, exactly=False):
119122
return 0
120123
return 0
121124

125+
def prefixMatchListScore(self, searchList, exactly=False):
126+
for item in searchList:
127+
score = self.prefixMatchScore(item, exactly)
128+
if score:
129+
return score
130+
return 0
131+
122132
# format completion item according to sublime text completions format
123133
def format(self, stripQuotes=False):
124134
typeDisplay = ''
@@ -147,9 +157,9 @@ def format(self, stripQuotes=False):
147157

148158
class Completion:
149159
def __init__(self, uppercaseKeywords, allTables, allColumns, allFunctions):
150-
self.allTables = [CompletionItem('Table', table, 0) for table in allTables]
151-
self.allColumns = [CompletionItem('Column', column, 0) for column in allColumns]
152-
self.allFunctions = [CompletionItem('Function', func, 0) for func in allFunctions]
160+
self.allTables = [CompletionItem('Table', table) for table in allTables]
161+
self.allColumns = [CompletionItem('Column', column) for column in allColumns]
162+
self.allFunctions = [CompletionItem('Function', func) for func in allFunctions]
153163

154164
self.allKeywords = []
155165
for keyword in keywords_list:
@@ -158,7 +168,7 @@ def __init__(self, uppercaseKeywords, allTables, allColumns, allFunctions):
158168
else:
159169
keyword = keyword.lower()
160170

161-
self.allKeywords.append(CompletionItem('Keyword', keyword, 0))
171+
self.allKeywords.append(CompletionItem('Keyword', keyword))
162172

163173
def getBasicAutoCompleteList(self, prefix):
164174
prefix = prefix.lower()
@@ -168,17 +178,17 @@ def getBasicAutoCompleteList(self, prefix):
168178
for item in self.allColumns:
169179
score = item.prefixMatchScore(prefix)
170180
if score:
171-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
181+
autocompleteList.append(item)
172182

173183
for item in self.allTables:
174184
score = item.prefixMatchScore(prefix)
175185
if score:
176-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
186+
autocompleteList.append(item)
177187

178188
for item in self.allFunctions:
179189
score = item.prefixMatchScore(prefix)
180190
if score:
181-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
191+
autocompleteList.append(item)
182192

183193
if len(autocompleteList) == 0:
184194
return None
@@ -263,94 +273,93 @@ def _noDotsCompletions(self, prefix, identifiers, joinAlias=None):
263273
Order: statement aliases -> statement cols -> statement tables -> statement functions,
264274
then: other cols -> other tables -> other functions that match the prefix in their names
265275
"""
266-
# get join conditions
267-
joinConditions = []
268-
if joinAlias:
269-
joinConditions = self._joinConditionCompletions(identifiers, joinAlias)
270276

271277
# use set, as we are interested only in unique identifiers
272278
sqlAliases = set()
273-
sqlTables = set()
274-
sqlColumns = set()
275-
sqlFunctions = set()
279+
sqlTables = []
280+
sqlColumns = []
281+
sqlFunctions = []
282+
otherTables = []
283+
otherColumns = []
284+
otherFunctions = []
285+
otherKeywords = []
286+
otherJoinConditions = []
287+
288+
# utilitary temp lists
289+
identTables = set()
290+
identColumns = set()
291+
identFunctions = set()
276292

277293
for ident in identifiers:
278294
if ident.has_alias():
279-
sqlAliases.add(CompletionItem('Alias', ident.alias, 0))
295+
aliasItem = CompletionItem('Alias', ident.alias)
296+
score = aliasItem.prefixMatchScore(prefix)
297+
if score and aliasItem.ident != prefix:
298+
sqlAliases.add(aliasItem)
280299

281300
if ident.is_function:
282-
functions = [
283-
fun
284-
for fun in self.allFunctions
285-
if fun.prefixMatchScore(ident.full_name, exactly=True) > 0
286-
]
287-
sqlFunctions.update(functions)
288-
else:
289-
tables = [
290-
table
291-
for table in self.allTables
292-
if table.prefixMatchScore(ident.full_name, exactly=True) > 0
293-
]
294-
sqlTables.update(tables)
295-
prefixForColumnMatch = ident.name + '.'
296-
columns = [
297-
col
298-
for col in self.allColumns
299-
if col.prefixMatchScore(prefixForColumnMatch, exactly=True) > 0
300-
]
301-
sqlColumns.update(columns)
302-
303-
autocompleteList = []
304-
305-
for condition in joinConditions:
306-
if condition.ident.lower().startswith(prefix):
307-
autocompleteList.append(CompletionItem(condition.type, condition.ident, 1))
308-
309-
# first of all list aliases and identifiers related to currently parsed statement
310-
for item in sqlAliases:
311-
score = item.prefixMatchScore(prefix)
312-
if score and item.ident != prefix:
313-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
301+
identFunctions.add(ident.full_name)
302+
elif ident.is_table_alias:
303+
identTables.add(ident.full_name)
304+
identColumns.add(ident.name + '.' + prefix)
314305

315-
for item in sqlColumns:
316-
score = item.prefixMatchScore(prefix)
306+
for table in self.allTables:
307+
score = table.prefixMatchScore(prefix, exactly=False)
317308
if score:
318-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
309+
if table.prefixMatchListScore(identTables, exactly=True) > 0:
310+
sqlTables.append(table)
311+
else:
312+
otherTables.append(table)
319313

320-
for item in sqlTables:
321-
score = item.prefixMatchScore(prefix)
314+
for col in self.allColumns:
315+
score = col.prefixMatchScore(prefix, exactly=False)
322316
if score:
323-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
317+
if col.prefixMatchListScore(identColumns, exactly=False) > 0:
318+
sqlColumns.append(col)
319+
else:
320+
otherColumns.append(col)
324321

325-
for item in sqlFunctions:
326-
score = item.prefixMatchScore(prefix)
322+
for fun in self.allFunctions:
323+
score = fun.prefixMatchScore(prefix, exactly=False)
327324
if score:
328-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
325+
if fun.prefixMatchListScore(identFunctions, exactly=True) > 0:
326+
sqlColumns.append(fun)
327+
else:
328+
otherColumns.append(fun)
329329

330-
# add keywords to auto-complete results
330+
# keywords
331331
for item in self.allKeywords:
332332
score = item.prefixMatchScore(prefix)
333333
if score:
334-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
334+
otherKeywords.append(item)
335335

336-
# add the rest of the columns, tables and functions that also match the prefix
337-
for item in self.allColumns:
338-
score = item.prefixMatchScore(prefix)
339-
if score:
340-
if item not in sqlColumns:
341-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
336+
# join conditions
337+
if joinAlias:
338+
joinConditions = self._joinConditionCompletions(identifiers, joinAlias)
342339

343-
for item in self.allTables:
344-
score = item.prefixMatchScore(prefix)
345-
if score:
346-
if item not in sqlTables:
347-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
340+
for condition in joinConditions:
341+
if condition.ident.lower().startswith(prefix):
342+
otherJoinConditions.append(condition)
348343

349-
for item in self.allFunctions:
350-
score = item.prefixMatchScore(prefix)
351-
if score:
352-
if item not in sqlFunctions:
353-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
344+
# collect the results in prefered order
345+
autocompleteList = []
346+
347+
# first of all list join conditions (if applicable)
348+
autocompleteList.extend(otherJoinConditions)
349+
350+
# then aliases and identifiers related to currently parsed statement
351+
autocompleteList.extend(sqlAliases)
352+
353+
# then cols, tables, functions related to current statement
354+
autocompleteList.extend(sqlColumns)
355+
autocompleteList.extend(sqlTables)
356+
autocompleteList.extend(sqlFunctions)
357+
358+
# then other matching cols, tables, functions
359+
autocompleteList.extend(otherKeywords)
360+
autocompleteList.extend(otherColumns)
361+
autocompleteList.extend(otherTables)
362+
autocompleteList.extend(otherFunctions)
354363

355364
return autocompleteList, False
356365

@@ -390,7 +399,7 @@ def _singleDotCompletions(self, prefix, identifiers, joinAlias=None):
390399
aliasPrefix = prefixParent + '.'
391400
if condition.ident.lower().startswith(aliasPrefix):
392401
autocompleteList.append(CompletionItem(condition.type,
393-
_stripPrefix(condition.ident, aliasPrefix), 1))
402+
_stripPrefix(condition.ident, aliasPrefix)))
394403

395404
# first of all expand table aliases to real table names and try
396405
# to match their columns with prefix of these expanded identifiers
@@ -400,23 +409,23 @@ def _singleDotCompletions(self, prefix, identifiers, joinAlias=None):
400409
for item in self.allColumns:
401410
score = item.prefixMatchScore(prefix_to_match)
402411
if score:
403-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
412+
autocompleteList.append(item)
404413

405414
# try to match all our other objects (tables, columns, functions) with prefix
406415
for item in self.allColumns:
407416
score = item.prefixMatchScore(prefix)
408417
if score:
409-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
418+
autocompleteList.append(item)
410419

411420
for item in self.allTables:
412421
score = item.prefixMatchScore(prefix)
413422
if score:
414-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
423+
autocompleteList.append(item)
415424

416425
for item in self.allFunctions:
417426
score = item.prefixMatchScore(prefix)
418427
if score:
419-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
428+
autocompleteList.append(item)
420429

421430
inhibit = len(autocompleteList) > 0
422431
# in case prefix parent is a query alias we simply don't know what those
@@ -432,7 +441,7 @@ def _multiDotCompletions(self, prefix, identifiers):
432441
for item in self.allColumns:
433442
score = item.prefixMatchScore(prefix)
434443
if score:
435-
autocompleteList.append(CompletionItem(item.type, item.ident, score))
444+
autocompleteList.append(item)
436445

437446
if len(autocompleteList) > 0:
438447
return autocompleteList, True
@@ -450,7 +459,7 @@ def _joinConditionCompletions(self, identifiers, joinAlias=None):
450459

451460
for ident in identifiers:
452461
if ident.has_alias() and not ident.is_function:
453-
sqlTableAliases.add(CompletionItem('Alias', ident.alias, 0))
462+
sqlTableAliases.add(CompletionItem('Alias', ident.alias))
454463

455464
prefixForColumnMatch = ident.name + '.'
456465
columns = [
@@ -486,7 +495,7 @@ def _joinConditionCompletions(self, identifiers, joinAlias=None):
486495
sideA = joinAlias + '.' + joinColumn.name
487496
sideB = otherAlias + '.' + otherColumn.name
488497

489-
joinCandidatesCompletions.append(CompletionItem('Condition', sideA + ' = ' + sideB, 0))
490-
joinCandidatesCompletions.append(CompletionItem('Condition', sideB + ' = ' + sideA, 0))
498+
joinCandidatesCompletions.append(CompletionItem('Condition', sideA + ' = ' + sideB))
499+
joinCandidatesCompletions.append(CompletionItem('Condition', sideB + ' = ' + sideA))
491500

492501
return joinCandidatesCompletions

0 commit comments

Comments
 (0)