private static void AddTopColumnValues(IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions, Table singleTable, ColumnDetails singleColumn) { string completeQuery = GetCompleteQueryPrefix(result); // Recommend the top ten values in the column with the prefix typed so far DistinctResult topValues = singleTable.Query(new DistinctQueryTop(singleColumn.Name, lastTerm.Value.ToString(), completeQuery, 10)); int total = (int)topValues.Total; if (topValues.Total == 0) { return; } // Walk values in order for ==, :, ::, backwards with inverse percentages for != bool isNotEquals = lastTerm.Operator == Operator.NotEquals; int start = isNotEquals ? topValues.Values.RowCount - 1 : 0; int end = isNotEquals ? -1 : topValues.Values.RowCount; int step = isNotEquals ? -1 : 1; for (int i = start; i != end; i += step) { string value = topValues.Values[i, 0].ToString(); int countForValue = (int)topValues.Values[i, 1]; if (isNotEquals) { countForValue = (int)topValues.Total - countForValue; } if ((countForValue > 1 || total <= 10) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase) && value.Length < 100) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryParser.WrapValue(topValues.Values[i, 0]), countForValue, topValues.Total)); } } }
private static void AddValueDistribution(IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions, Table singleTable, ColumnDetails singleColumn) { string completeQuery = GetCompleteQueryPrefix(result); bool inclusive = (lastTerm.Operator == Operator.LessThanOrEqual || lastTerm.Operator == Operator.GreaterThan); bool reverse = (lastTerm.Operator == Operator.GreaterThan || lastTerm.Operator == Operator.GreaterThanOrEqual); // Recommend the top ten values in the column with the prefix typed so far DataBlockResult distribution = singleTable.Query(new DistributionQuery(singleColumn.Name, completeQuery, inclusive)); if (distribution.Total == 0 || distribution.Details.Succeeded == false) { return; } int countSoFar; if (reverse) { countSoFar = (int)distribution.Values[distribution.Values.RowCount - 1, 1]; for (int i = distribution.Values.RowCount - 2; i >= 0; --i) { string value = distribution.Values[i, 0].ToString(); double frequency = (double)countSoFar / (double)(distribution.Total); int countForRange = (int)distribution.Values[i, 1]; if ((distribution.Values.RowCount == 2 || (int)distribution.Values[i + 1, 1] > 0) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryParser.WrapValue(distribution.Values[i, 0]), countSoFar, distribution.Total)); } countSoFar += countForRange; } } else { countSoFar = 0; for (int i = 0; i < distribution.Values.RowCount - 1; ++i) { string value = distribution.Values[i, 0].ToString(); int countForRange = (int)distribution.Values[i, 1]; countSoFar += countForRange; double frequency = (double)countSoFar / (double)(distribution.Total); if ((distribution.Values.RowCount == 2 || countForRange > 0) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryParser.WrapValue(distribution.Values[i, 0]), countSoFar, distribution.Total)); } } } }
/// <summary> /// CompleteQuery takes a query before the cursor, the IntelliSenseResult, a selected IntelliSenseItem, and the /// completion character and returns the proper completed query. /// /// It removes the token in progress, adds the 'CompleteAs' value, a space, and the non-whitespace completion character. /// </summary> /// <param name="queryBeforeCursor">Query up to the cursor position</param> /// <param name="result">IntelliSenseResult from GetIntelliSenseItems</param> /// <param name="selectedItem">IntelliSenseItem selected</param> /// <param name="completionCharacter">Completion Character typed</param> /// <returns>New Arriba Query after completion</returns> public string CompleteQuery(string queryBeforeCursor, IntelliSenseResult result, IntelliSenseItem selectedItem, char completionCharacter) { // If there is no completion for this item (grammar suggestions), just append the character if (selectedItem == null || String.IsNullOrEmpty(selectedItem.CompleteAs)) { return(queryBeforeCursor + completionCharacter); } // Add the value to complete. Add a space afterward for non-values (so we don't jump to the next term). Add a space if you completed with space. string separator = (selectedItem.Category == QueryTokenCategory.Value && completionCharacter != ' ' ? "" : " "); string newQuery = result.Complete + selectedItem.CompleteAs + separator; // If the completion character isn't '\t' or ' ', add the completion character as well if (completionCharacter != '\n' && completionCharacter != '\t' && completionCharacter != ' ') { newQuery += completionCharacter; } return(newQuery); }
private static void AddTopColumnValues(IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions, Table singleTable, ColumnDetails singleColumn) { // Lame, to turn single terms into AllQuery [normally they return nothing] string completeQuery = QueryParser.Parse(result.Complete).ToString(); // Recommend the top ten values in the column with the prefix typed so far DistinctResult topValues = singleTable.Query(new DistinctQueryTop(singleColumn.Name, completeQuery, 10)); int total = (int)topValues.Total; if (topValues.Total == 0) { return; } // Walk values in order for ==, :, ::, backwards with inverse percentages for != bool isNotEquals = lastTerm.Operator == Operator.NotEquals; int start = isNotEquals ? topValues.Values.RowCount - 1 : 0; int end = isNotEquals ? -1 : topValues.Values.RowCount; int step = isNotEquals ? -1 : 1; for (int i = start; i != end; i += step) { string value = topValues.Values[i, 0].ToString(); int countForValue = (int)topValues.Values[i, 1]; if (isNotEquals) { countForValue = (int)topValues.Total - countForValue; } double frequency = (double)countForValue / (double)(topValues.Total); if ((countForValue > 1 || total <= 10) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { string hint = (countForValue == topValues.Total ? "all" : frequency.ToString("P0")); suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryScanner.WrapValue(value), hint)); } } }
private static string GetCompleteQueryPrefix(IntelliSenseResult result) { // Lame, to turn single terms into AllQuery [normally they return nothing] return(QueryParser.Parse(result.Complete).ToString()); }
private static void AddSuggestionsForTerm(IReadOnlyCollection <Table> targetTables, IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions) { if (lastTerm != null && lastTerm.ColumnName == "*") { object termValue = lastTerm.Value; List <Tuple <string, int, int> > columnsForTerm = new List <Tuple <string, int, int> >(); foreach (Table table in targetTables) { DataBlockResult columns = table.Query(new TermInColumnsQuery(termValue.ToString(), GetCompleteQueryPrefix(result))); for (int i = 0; i < columns.Values.RowCount; ++i) { columnsForTerm.Add(new Tuple <string, int, int>((string)columns.Values[i, 0], (int)columns.Values[i, 1], (int)columns.Total)); } } // Sort overall set by frequency descending columnsForTerm.Sort((left, right) => ((double)right.Item2 / (double)right.Item3).CompareTo((double)left.Item2 / (double)left.Item3)); // Add top 10 suggestions int countToReturn = Math.Min(10, columnsForTerm.Count); for (int i = 0; i < countToReturn; ++i) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.ColumnName, QueryParser.WrapColumnName(columnsForTerm[i].Item1) + " : " + QueryParser.WrapValue(termValue), columnsForTerm[i].Item2, columnsForTerm[i].Item3)); } } }
private static void AddSuggestionsForValue(IReadOnlyCollection <Table> targetTables, IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, ref bool spaceIsSafeCompletionCharacter, List <IntelliSenseItem> suggestions) { Table singleTable; ColumnDetails singleColumn; if (!TryFindSingleMatchingColumn(targetTables, lastTerm, out singleTable, out singleColumn)) { // If more than one table has the column, we can't recommend anything result.SyntaxHint = Value; } else { if (lastTerm.Operator == Operator.Equals || lastTerm.Operator == Operator.NotEquals || lastTerm.Operator == Operator.Matches || lastTerm.Operator == Operator.MatchesExact) { AddTopColumnValues(result, lastTerm, guidance, suggestions, singleTable, singleColumn); } else if (lastTerm.Operator == Operator.LessThan || lastTerm.Operator == Operator.LessThanOrEqual || lastTerm.Operator == Operator.GreaterThan || lastTerm.Operator == Operator.GreaterThanOrEqual) { AddValueDistribution(result, lastTerm, guidance, suggestions, singleTable, singleColumn); } Type columnType = singleTable.GetColumnType(singleColumn.Name); if (columnType == typeof(ByteBlock)) { result.SyntaxHint = StringValue; } else if (columnType == typeof(bool)) { if (suggestions.Count == 0) { AddWhenPrefixes(BooleanValues, guidance.Value, suggestions); } spaceIsSafeCompletionCharacter = true; } else if (columnType == typeof(DateTime)) { result.SyntaxHint = DateTimeValue; } else if (columnType == typeof(TimeSpan)) { result.SyntaxHint = TimeSpanValue; } else if (columnType == typeof(float) || columnType == typeof(double)) { result.SyntaxHint = FloatValue; } else if (columnType == typeof(byte) || columnType == typeof(sbyte) || columnType == typeof(short) || columnType == typeof(ushort) || columnType == typeof(int) || columnType == typeof(uint) || columnType == typeof(long) || columnType == typeof(ulong)) { result.SyntaxHint = IntegerValue; } else { result.SyntaxHint = String.Format("<{0}>", columnType.Name); } } }
/// <summary> /// Get the set of IntelliSense suggestions valid at the current position. /// It's filtered to the set of valid query parts are the current position, /// as well as the set of values the current partial value is a prefix for. /// </summary> /// <param name="queryBeforeCursor">Current Arriba Query up to the cursor position</param> /// <param name="targetTables">Table[s] which are valid for the current query</param> /// <returns>IntelliSenseResult reporting what to show</returns> public IntelliSenseResult GetIntelliSenseItems(string queryBeforeCursor, IReadOnlyCollection <Table> targetTables) { IntelliSenseResult result = new IntelliSenseResult() { Query = queryBeforeCursor, Incomplete = "", Complete = "", SyntaxHint = "", CompletionCharacters = new char[0], Suggestions = new List <IntelliSenseItem>() }; // If no tables were passed, show no IntelliSense (hint that there's an error blocking all tables) if (queryBeforeCursor == null || targetTables == null || targetTables.Count == 0) { return(result); } // Filter the set of tables to those valid for the query so far targetTables = FilterToValidTablesForQuery(targetTables, queryBeforeCursor); // If no tables remain valid, show no IntelliSense (hint that there's an error blocking all tables) if (targetTables == null || targetTables.Count == 0) { return(result); } // Get grammatical categories valid after the query prefix TermExpression lastTerm; IExpression query; IntelliSenseGuidance guidance = GetCurrentTokenOptions(queryBeforeCursor, out lastTerm, out query); bool spaceIsSafeCompletionCharacter = !String.IsNullOrEmpty(guidance.Value); // If there are no tokens suggested here, return empty completion if (guidance.Options == QueryTokenCategory.None) { return(result); } // Compute the CurrentCompleteValue, *before* considering values, so value IntelliSense can use them string queryWithoutIncompleteValue = queryBeforeCursor; if (!queryWithoutIncompleteValue.EndsWith(guidance.Value)) { throw new ArribaException("Error: IntelliSense suggestion couldn't be applied."); } queryWithoutIncompleteValue = queryWithoutIncompleteValue.Substring(0, queryWithoutIncompleteValue.Length - guidance.Value.Length); // If the CurrentIncompleteValue is an explicit column name, remove and re-complete that, also if (queryWithoutIncompleteValue.EndsWith("[")) { queryWithoutIncompleteValue = queryWithoutIncompleteValue.Substring(0, queryWithoutIncompleteValue.Length - 1); } result.Complete = queryWithoutIncompleteValue; result.Incomplete = guidance.Value; // Build a ranked list of suggestions - preferred token categories, filtered to the prefix already typed List <IntelliSenseItem> suggestions = new List <IntelliSenseItem>(); if (guidance.Options.HasFlag(QueryTokenCategory.BooleanOperator)) { AddWhenPrefixes(BooleanOperators, guidance.Value, suggestions); } if (guidance.Options.HasFlag(QueryTokenCategory.CompareOperator)) { AddSuggestionsForCompareOperator(targetTables, lastTerm, guidance, suggestions); } if (guidance.Options.HasFlag(QueryTokenCategory.ColumnName)) { AddSuggestionsForColumnNames(targetTables, guidance, ref spaceIsSafeCompletionCharacter, suggestions); } // Space isn't safe to complete values (except when all explicit values shown, bool below) if (guidance.Options.HasFlag(QueryTokenCategory.Value)) { spaceIsSafeCompletionCharacter = false; AddSuggestionsForTerm(targetTables, result, lastTerm, guidance, suggestions); } // If *only* a value is valid here, provide a syntax hint for the value type (and reconsider if space is safe to complete) if (guidance.Options == QueryTokenCategory.Value) { AddSuggestionsForValue(targetTables, result, lastTerm, guidance, ref spaceIsSafeCompletionCharacter, suggestions); } if (guidance.Options.HasFlag(QueryTokenCategory.TermPrefixes)) { AddWhenPrefixes(TermPrefixes, guidance.Value, suggestions); } // Build a list of valid completion characters List <char> completionCharacters = new List <char>(); completionCharacters.Add('\t'); if (spaceIsSafeCompletionCharacter) { completionCharacters.Add(' '); } // If column names are valid here but term prefixes or compare operators, operator start characters are valid completion characters if (guidance.Options.HasFlag(QueryTokenCategory.ColumnName) && !guidance.Options.HasFlag(QueryTokenCategory.CompareOperator) && !guidance.Options.HasFlag(QueryTokenCategory.TermPrefixes)) { completionCharacters.AddRange(ColumnNameCompletionCharacters); } // If there's only one suggestion and it's been fully typed, remove it if (suggestions.Count == 1 && suggestions[0].Display == guidance.Value) { suggestions.Clear(); } // Finish populating the result result.Suggestions = suggestions; result.CompletionCharacters = completionCharacters; return(result); }