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

Commit 0e529f6

Browse files
committed
Improved performance of smart completions
On lengthy SQL statements with lots of identifiers and aliases the performance was poor, this commit improves performance of smart completions for most widely used case - when prefix does not contain any dots (no parent object specified).
1 parent a101c8e commit 0e529f6

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)