Skip to content

Commit 6c45e8c

Browse files
authored
Data Explorer: Implement sorting columns by data type for Python and DuckDB, improve search_schema RPC documentation (#8911)
This adds the missing "sort columns by data type" mode, and renames the ascending/descending sort to AscendingName or DescendingName for clarity.
1 parent eda3abf commit 6c45e8c

File tree

8 files changed

+235
-56
lines changed

8 files changed

+235
-56
lines changed

extensions/positron-duckdb/src/extension.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,20 +910,34 @@ export class DuckDBTableView {
910910

911911
// Sort the filtered indices
912912
switch (params.sort_order) {
913-
case SearchSchemaSortOrder.Ascending:
913+
case SearchSchemaSortOrder.AscendingName:
914914
filteredIndices.sort((a, b) => {
915915
const nameA = this.fullSchema[a].column_name.toLowerCase();
916916
const nameB = this.fullSchema[b].column_name.toLowerCase();
917917
return nameA.localeCompare(nameB);
918918
});
919919
break;
920-
case SearchSchemaSortOrder.Descending:
920+
case SearchSchemaSortOrder.DescendingName:
921921
filteredIndices.sort((a, b) => {
922922
const nameA = this.fullSchema[a].column_name.toLowerCase();
923923
const nameB = this.fullSchema[b].column_name.toLowerCase();
924924
return nameB.localeCompare(nameA);
925925
});
926926
break;
927+
case SearchSchemaSortOrder.AscendingType:
928+
filteredIndices.sort((a, b) => {
929+
const typeA = this.fullSchema[a].column_type.toLowerCase();
930+
const typeB = this.fullSchema[b].column_type.toLowerCase();
931+
return typeA.localeCompare(typeB);
932+
});
933+
break;
934+
case SearchSchemaSortOrder.DescendingType:
935+
filteredIndices.sort((a, b) => {
936+
const typeA = this.fullSchema[a].column_type.toLowerCase();
937+
const typeB = this.fullSchema[b].column_type.toLowerCase();
938+
return typeB.localeCompare(typeA);
939+
});
940+
break;
927941
case SearchSchemaSortOrder.Original:
928942
default:
929943
// Keep original order

extensions/positron-duckdb/src/interfaces.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,8 +1165,10 @@ export type ArraySelection = DataSelectionRange | DataSelectionIndices;
11651165
*/
11661166
export enum SearchSchemaSortOrder {
11671167
Original = 'original',
1168-
Ascending = 'ascending',
1169-
Descending = 'descending'
1168+
AscendingName = 'ascending_name',
1169+
DescendingName = 'descending_name',
1170+
AscendingType = 'ascending_type',
1171+
DescendingType = 'descending_type'
11701172
}
11711173

11721174
/**

extensions/positron-duckdb/src/test/extension.test.ts

Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,7 @@ suite('Positron DuckDB Extension Test Suite', () => {
17641764
});
17651765

17661766
test('searchSchema functionality', async () => {
1767-
// Create a test table with various column types
1767+
// Create test table with overlapping column names and mixed types
17681768
const tableName = makeTempTableName();
17691769
await createTempTable(tableName, [
17701770
{
@@ -1773,12 +1773,30 @@ suite('Positron DuckDB Extension Test Suite', () => {
17731773
display_type: ColumnDisplayType.Number,
17741774
values: ['1', '2', '3'],
17751775
},
1776+
{
1777+
name: 'user_id',
1778+
type: 'INTEGER',
1779+
display_type: ColumnDisplayType.Number,
1780+
values: ['101', '102', '103'],
1781+
},
17761782
{
17771783
name: 'name',
17781784
type: 'VARCHAR',
17791785
display_type: ColumnDisplayType.String,
17801786
values: ["'Alice'", "'Bob'", "'Charlie'"],
17811787
},
1788+
{
1789+
name: 'full_name',
1790+
type: 'VARCHAR',
1791+
display_type: ColumnDisplayType.String,
1792+
values: ["'Alice Smith'", "'Bob Jones'", "'Charlie Brown'"],
1793+
},
1794+
{
1795+
name: 'first_name',
1796+
type: 'VARCHAR',
1797+
display_type: ColumnDisplayType.String,
1798+
values: ["'Alice'", "'Bob'", "'Charlie'"],
1799+
},
17821800
{
17831801
name: 'age',
17841802
type: 'INTEGER',
@@ -1795,18 +1813,46 @@ suite('Positron DuckDB Extension Test Suite', () => {
17951813
"'2024-01-03 00:00:00'",
17961814
],
17971815
},
1816+
{
1817+
name: 'updated_at',
1818+
type: 'TIMESTAMP',
1819+
display_type: ColumnDisplayType.Datetime,
1820+
values: [
1821+
"'2024-02-01 00:00:00'",
1822+
"'2024-02-02 00:00:00'",
1823+
"'2024-02-03 00:00:00'",
1824+
],
1825+
},
17981826
{
17991827
name: 'is_active',
18001828
type: 'BOOLEAN',
18011829
display_type: ColumnDisplayType.Boolean,
18021830
values: ['true', 'false', 'true'],
18031831
},
1832+
{
1833+
name: 'is_deleted',
1834+
type: 'BOOLEAN',
1835+
display_type: ColumnDisplayType.Boolean,
1836+
values: ['false', 'false', 'true'],
1837+
},
18041838
{
18051839
name: 'birth_date',
18061840
type: 'DATE',
18071841
display_type: ColumnDisplayType.Date,
18081842
values: ["'1999-01-01'", "'1994-01-01'", "'1989-01-01'"],
18091843
},
1844+
{
1845+
name: 'start_date',
1846+
type: 'DATE',
1847+
display_type: ColumnDisplayType.Date,
1848+
values: ["'2020-01-01'", "'2019-01-01'", "'2018-01-01'"],
1849+
},
1850+
{
1851+
name: 'salary',
1852+
type: 'DOUBLE',
1853+
display_type: ColumnDisplayType.Number,
1854+
values: ['50000.0', '60000.0', '70000.0'],
1855+
},
18101856
]);
18111857

18121858
const uri = vscode.Uri.from({ scheme: 'duckdb', path: tableName });
@@ -1869,49 +1915,73 @@ suite('Positron DuckDB Extension Test Suite', () => {
18691915
{
18701916
filters: [],
18711917
sortOrder: SearchSchemaSortOrder.Original,
1872-
expected: [0, 1, 2, 3, 4, 5],
1918+
expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
18731919
description: 'No filters, original order',
18741920
},
18751921

18761922
// Text search tests
18771923
{
18781924
filters: [textFilter(TextSearchType.Contains, 'date')],
18791925
sortOrder: SearchSchemaSortOrder.Original,
1880-
expected: [5],
1926+
expected: [7, 10, 11],
18811927
description: 'Contains "date"',
18821928
},
1929+
{
1930+
filters: [textFilter(TextSearchType.Contains, 'name')],
1931+
sortOrder: SearchSchemaSortOrder.Original,
1932+
expected: [2, 3, 4],
1933+
description: 'Contains "name"',
1934+
},
1935+
{
1936+
filters: [textFilter(TextSearchType.Contains, 'id')],
1937+
sortOrder: SearchSchemaSortOrder.Original,
1938+
expected: [0, 1],
1939+
description: 'Contains "id"',
1940+
},
18831941
{
18841942
filters: [textFilter(TextSearchType.StartsWith, 'is')],
18851943
sortOrder: SearchSchemaSortOrder.Original,
1886-
expected: [4],
1944+
expected: [8, 9],
18871945
description: 'Starts with "is"',
18881946
},
18891947
{
1890-
filters: [textFilter(TextSearchType.EndsWith, 'e')],
1948+
filters: [textFilter(TextSearchType.EndsWith, 'at')],
18911949
sortOrder: SearchSchemaSortOrder.Original,
1892-
expected: [1, 2, 4, 5],
1893-
description: 'Ends with "e"',
1950+
expected: [6, 7],
1951+
description: 'Ends with "at"',
18941952
},
18951953
{
18961954
filters: [textFilter(TextSearchType.NotContains, '_')],
18971955
sortOrder: SearchSchemaSortOrder.Original,
1898-
expected: [0, 1, 2],
1956+
expected: [0, 2, 5, 12],
18991957
description: 'Not contains "_"',
19001958
},
19011959
{
19021960
filters: [textFilter(TextSearchType.RegexMatch, '^[a-z]+$')],
19031961
sortOrder: SearchSchemaSortOrder.Original,
1904-
expected: [0, 1, 2],
1962+
expected: [0, 2, 5, 12],
19051963
description: 'Regex match ^[a-z]+$',
19061964
},
19071965

19081966
// Type filter tests
19091967
{
19101968
filters: [typeFilter(ColumnDisplayType.Number)],
19111969
sortOrder: SearchSchemaSortOrder.Original,
1912-
expected: [0, 2],
1970+
expected: [0, 1, 5, 12],
19131971
description: 'Number columns',
19141972
},
1973+
{
1974+
filters: [typeFilter(ColumnDisplayType.String)],
1975+
sortOrder: SearchSchemaSortOrder.Original,
1976+
expected: [2, 3, 4],
1977+
description: 'String columns',
1978+
},
1979+
{
1980+
filters: [typeFilter(ColumnDisplayType.Boolean)],
1981+
sortOrder: SearchSchemaSortOrder.Original,
1982+
expected: [8, 9],
1983+
description: 'Boolean columns',
1984+
},
19151985
{
19161986
filters: [
19171987
typeFilter(
@@ -1920,7 +1990,7 @@ suite('Positron DuckDB Extension Test Suite', () => {
19201990
),
19211991
],
19221992
sortOrder: SearchSchemaSortOrder.Original,
1923-
expected: [3, 5],
1993+
expected: [6, 7, 10, 11],
19241994
description: 'Date/Datetime columns',
19251995
},
19261996

@@ -1931,42 +2001,62 @@ suite('Positron DuckDB Extension Test Suite', () => {
19312001
typeFilter(ColumnDisplayType.Number),
19322002
],
19332003
sortOrder: SearchSchemaSortOrder.Original,
1934-
expected: [2],
1935-
description: 'Contains "a" AND type is Number',
2004+
expected: [5, 12],
2005+
description: 'Contains "a" AND Number type',
19362006
},
19372007

1938-
// Sort order tests
2008+
// Sort order tests - by name
19392009
{
19402010
filters: [],
1941-
sortOrder: SearchSchemaSortOrder.Ascending,
1942-
expected: [2, 5, 3, 0, 4, 1],
1943-
description: 'Ascending alphabetical order',
2011+
sortOrder: SearchSchemaSortOrder.AscendingName,
2012+
expected: [5, 10, 6, 4, 3, 0, 8, 9, 2, 12, 11, 7, 1],
2013+
description: 'Ascending by name',
19442014
},
19452015
{
19462016
filters: [],
1947-
sortOrder: SearchSchemaSortOrder.Descending,
1948-
expected: [1, 4, 0, 3, 5, 2],
1949-
description: 'Descending alphabetical order',
2017+
sortOrder: SearchSchemaSortOrder.DescendingName,
2018+
expected: [1, 7, 11, 12, 2, 9, 8, 0, 3, 4, 6, 10, 5],
2019+
description: 'Descending by name',
19502020
},
19512021
{
19522022
filters: [textFilter(TextSearchType.Contains, 'a')],
1953-
sortOrder: SearchSchemaSortOrder.Ascending,
1954-
expected: [2, 5, 3, 4, 1],
1955-
description: 'Filtered with ascending sort',
2023+
sortOrder: SearchSchemaSortOrder.AscendingName,
2024+
expected: [5, 10, 6, 4, 3, 8, 2, 12, 11, 7],
2025+
description: 'Filtered and sorted by name',
2026+
},
2027+
2028+
// Sort order tests - by type
2029+
{
2030+
filters: [],
2031+
sortOrder: SearchSchemaSortOrder.AscendingType,
2032+
expected: [8, 9, 10, 11, 12, 0, 1, 5, 6, 7, 2, 3, 4],
2033+
description: 'Ascending by type',
2034+
},
2035+
{
2036+
filters: [],
2037+
sortOrder: SearchSchemaSortOrder.DescendingType,
2038+
expected: [2, 3, 4, 6, 7, 0, 1, 5, 12, 10, 11, 8, 9],
2039+
description: 'Descending by type',
2040+
},
2041+
{
2042+
filters: [typeFilter(ColumnDisplayType.Number)],
2043+
sortOrder: SearchSchemaSortOrder.AscendingType,
2044+
expected: [12, 0, 1, 5],
2045+
description: 'Number columns by type',
19562046
},
19572047

19582048
// Case sensitivity tests
19592049
{
19602050
filters: [textFilter(TextSearchType.Contains, 'AGE', true)],
19612051
sortOrder: SearchSchemaSortOrder.Original,
19622052
expected: [],
1963-
description: 'Case-sensitive, wrong case',
2053+
description: 'Case-sensitive "AGE"',
19642054
},
19652055
{
19662056
filters: [textFilter(TextSearchType.Contains, 'age', true)],
19672057
sortOrder: SearchSchemaSortOrder.Original,
1968-
expected: [2],
1969-
description: 'Case-sensitive, correct case',
2058+
expected: [5],
2059+
description: 'Case-sensitive "age"',
19702060
},
19712061
];
19722062

extensions/positron-python/python_files/posit/positron/data_explorer.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,26 @@ def search_schema(self, params: SearchSchemaParams):
308308
self._search_schema_last_result = (filters, matches)
309309

310310
# Apply sorting based on sort_order
311-
if sort_order == SearchSchemaSortOrder.Ascending:
311+
if sort_order == SearchSchemaSortOrder.AscendingName:
312312
# Sort by column name ascending
313-
matches = sorted(matches, key=lambda idx: self._get_column_name(idx))
314-
elif sort_order == SearchSchemaSortOrder.Descending:
313+
matches = sorted(matches, key=lambda idx: self._get_column_name(idx).lower())
314+
elif sort_order == SearchSchemaSortOrder.DescendingName:
315315
# Sort by column name descending
316-
matches = sorted(matches, key=lambda idx: self._get_column_name(idx), reverse=True)
316+
matches = sorted(
317+
matches, key=lambda idx: self._get_column_name(idx).lower(), reverse=True
318+
)
319+
elif sort_order == SearchSchemaSortOrder.AscendingType:
320+
# Sort by column type ascending (using lowercase type name)
321+
matches = sorted(
322+
matches, key=lambda idx: str(self._get_column_type_display(idx)).lower()
323+
)
324+
elif sort_order == SearchSchemaSortOrder.DescendingType:
325+
# Sort by column type descending (using lowercase type name)
326+
matches = sorted(
327+
matches,
328+
key=lambda idx: str(self._get_column_type_display(idx)).lower(),
329+
reverse=True,
330+
)
317331
# For SearchSchemaSortOrder.Original, keep original order (no sorting needed)
318332

319333
return SearchSchemaResult(matches=matches)

extensions/positron-python/python_files/posit/positron/data_explorer_comm.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ class SearchSchemaSortOrder(str, enum.Enum):
2626

2727
Original = "original"
2828

29-
Ascending = "ascending"
29+
AscendingName = "ascending_name"
3030

31-
Descending = "descending"
31+
DescendingName = "descending_name"
32+
33+
AscendingType = "ascending_type"
34+
35+
DescendingType = "descending_type"
3236

3337

3438
@enum.unique
@@ -243,7 +247,7 @@ class SearchSchemaResult(BaseModel):
243247
"""
244248

245249
matches: List[StrictInt] = Field(
246-
description="The column indices of the matching column indices in the indicated sort order",
250+
description="The column indices that match the search parameters in the indicated sort order.",
247251
)
248252

249253

0 commit comments

Comments
 (0)