Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions quadratic-core/src/a1/a1_selection/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,47 @@ impl A1Selection {
|| (one_cell && range.is_single_cell(a1_context))
}

/// Returns true if the selection is a 1d range (ie, a list of columns or rows)
pub fn is_1d_range(&self, a1_context: &A1Context) -> bool {
if self.ranges.len() != 1 {
return false;
}
let Some(range) = self.ranges.first() else {
return false;
};

// checks if the range is a single cell
if range.is_single_cell(a1_context) {
return true;
}

// checks if the range is a single column or row range
if let CellRefRange::Sheet { range: sheet_range } = &range {
if (sheet_range.start.col() == sheet_range.end.col()
&& sheet_range.start.col() != UNBOUNDED)
|| (sheet_range.start.row() == sheet_range.end.row()
&& sheet_range.start.row() != UNBOUNDED)
{
return true;
}
}

// checks table ranges
if let CellRefRange::Table { range: table_range } = &range
&& let Some(table) = a1_context.try_table(&table_range.table_name)
{
// a single column in a table
if matches!(table_range.col_range, ColRange::Col(_)) {
return true;
}
// the entire table with a table width == 1
if matches!(table_range.col_range, ColRange::All) && table.bounds.width() == 1 {
return true;
}
}
false
}

/// Returns true if the selection can insert column or row:
/// The selection is a single range AND
/// 1. is a column or row selection OR
Expand Down Expand Up @@ -1901,4 +1942,49 @@ mod tests {
assert!(!A1Selection::test_a1("B2:").can_insert_column_row());
assert!(!A1Selection::test_a1("*").can_insert_column_row());
}

#[test]
fn test_is_1d_range() {
let context = A1Context::test(
&[],
&[
("Table1", &["A"], Rect::test_a1("A1:A4")), // Single column table
("Table2", &["A", "B"], Rect::test_a1("C1:D4")), // Multi-column table
],
);

// Test single column selections
assert!(A1Selection::test_a1("A").is_1d_range(&context));
assert!(A1Selection::test_a1("B").is_1d_range(&context));

// Test single row selections
assert!(A1Selection::test_a1("1").is_1d_range(&context));
assert!(A1Selection::test_a1("2").is_1d_range(&context));

// Test single cell selections
assert!(A1Selection::test_a1("A1").is_1d_range(&context));
assert!(A1Selection::test_a1("B2").is_1d_range(&context));

// Test a range of columns
assert!(A1Selection::test_a1("A3:C3").is_1d_range(&context));
assert!(A1Selection::test_a1("D10:E10").is_1d_range(&context));

// Test a range of rows
assert!(A1Selection::test_a1("A3:A5").is_1d_range(&context));
assert!(A1Selection::test_a1("D10:D12").is_1d_range(&context));

// Test table selections
assert!(A1Selection::test_a1_context("Table1", &context).is_1d_range(&context)); // Single column table
assert!(!A1Selection::test_a1_context("Table2", &context).is_1d_range(&context)); // Multi-column table
assert!(A1Selection::test_a1_context("Table2[A]", &context).is_1d_range(&context)); // Single column from table

// Test non-1D ranges (should be false)
assert!(!A1Selection::test_a1("A:B").is_1d_range(&context)); // Multiple columns
assert!(!A1Selection::test_a1("1:2").is_1d_range(&context)); // Multiple rows
assert!(!A1Selection::test_a1("A1:B2").is_1d_range(&context)); // Rectangle
assert!(!A1Selection::test_a1("A1,B2").is_1d_range(&context)); // Multiple cells
assert!(!A1Selection::test_a1("A,B").is_1d_range(&context)); // Multiple columns
assert!(!A1Selection::test_a1("1,2").is_1d_range(&context)); // Multiple rows
assert!(!A1Selection::test_a1("*").is_1d_range(&context)); // All cells
}
}
93 changes: 82 additions & 11 deletions quadratic-core/src/controller/execution/run_code/run_connection.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
use anyhow::Result;

use crate::{
RunError, RunErrorMsg, SheetPos,
Pos, Rect, RunError, RunErrorMsg, SheetPos,
a1::{A1Error, A1Selection},
controller::{GridController, active_transactions::pending_transaction::PendingTransaction},
grid::{CodeCellLanguage, CodeCellValue, ConnectionKind, HANDLEBARS_REGEX_COMPILED, SheetId},
grid::{
CodeCellLanguage, CodeCellValue, ConnectionKind, HANDLEBARS_REGEX_COMPILED, Sheet, SheetId,
},
};

impl GridController {
/// Returns a string of cells for a connection. For more than one cell, the
/// cells are comma-delimited.
pub fn get_cells_for_connections(sheet: &Sheet, rect: Rect) -> String {
let mut response = String::new();
for y in rect.y_range() {
for x in rect.x_range() {
if let Some(cell) = sheet.display_value(Pos { x, y }) {
if !response.is_empty() {
response.push(',');
}
response.push_str(&cell.to_get_cells());
}
}
}
response
}

/// Attempts to replace handlebars with the actual value from the grid
fn replace_handlebars(
&self,
Expand Down Expand Up @@ -37,25 +56,32 @@ impl GridController {
let content = cap.get(1).map(|m| m.as_str().trim()).unwrap_or("");
let selection = A1Selection::parse_a1(content, default_sheet_id, context)?;

let Some(pos) = selection.try_to_pos(context) else {
// connections support either one cell or a 1d range of cells (ie,
// one column or row), which are entered as a comma-delimited list
// of entries (e.g., "2,3,10,1,...") in the query

if selection.is_1d_range(context) {
return Err(A1Error::WrongCellCount(
"Connections only supports one cell".to_string(),
"Connections only supports one cell or a 1d range of cells".to_string(),
));
};
}

let Some(sheet) = self.try_sheet(selection.sheet_id) else {
return Err(A1Error::SheetNotFound);
};

let value = sheet
.display_value(pos)
.map(|value| value.to_display())
.unwrap_or_default();
let rects = sheet.selection_to_rects(&selection, false, false, true, context);
if rects.len() > 1 {
return Err(A1Error::WrongCellCount(
"Connections only supports one cell or a 1d range of cells".to_string(),
));
}
let rect = rects[0];
result.push_str(&Self::get_cells_for_connections(sheet, rect));

transaction
.cells_accessed
.add_sheet_pos(SheetPos::new(sheet.id, pos.x, pos.y));
result.push_str(&value);
.add_sheet_rect(rect.to_sheet_rect(sheet.id));

last_match_end = whole_match.end();
}
Expand Down Expand Up @@ -124,6 +150,7 @@ mod tests {
GridController, active_transactions::pending_transaction::PendingTransaction,
},
grid::{CodeCellLanguage, ConnectionKind, SheetId},
test_util::*,
};

#[test]
Expand Down Expand Up @@ -275,4 +302,48 @@ mod tests {
test_error(&mut gc, r#"{{'Sheet 2'!A2}}"#, sheet_id);
test_error(&mut gc, r#"{{'Sheet 2'!$A$2}}"#, sheet_id);
}

#[test]
fn test_get_cells_for_connections() {
use crate::Rect;

let mut gc = test_create_gc();
let sheet_id = first_sheet_id(&gc);

// Test single cell
gc.set_cell_value(pos![sheet_id!A1], "test".to_string(), None, false);

assert_eq!(
GridController::get_cells_for_connections(gc.sheet(sheet_id), Rect::test_a1("A1")),
"test"
);

// Test multiple cells in the same row
gc.set_cell_value(pos![sheet_id!A2], "123".to_string(), None, false);
assert_eq!(
GridController::get_cells_for_connections(gc.sheet(sheet_id), Rect::test_a1("A1:A2")),
"test,123"
);

// Test multiple cells in the same column
gc.set_cell_value(pos![sheet_id!B1], "456".to_string(), None, false);
assert_eq!(
GridController::get_cells_for_connections(gc.sheet(sheet_id), Rect::test_a1("A1:B1")),
"test,456"
);

// test code cells
gc.set_code_cell(
pos![sheet_id!C1],
CodeCellLanguage::Formula,
"=A2 * 2".to_string(),
None,
None,
false,
);
assert_eq!(
GridController::get_cells_for_connections(gc.sheet(sheet_id), Rect::test_a1("A1:C1")),
"test,456,246"
);
}
}
Loading