Created
January 8, 2025 16:16
-
-
Save gz/f676fdf6c949311529c348d5ad187988 to your computer and use it in GitHub Desktop.
xls code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create table spreadsheet_data ( | |
id bigint not null, | |
ts timestamp not null, | |
raw_value varchar(64) not null | |
) with ( | |
'materialized' = 'true', | |
'connectors' = '[{ | |
"transport": { | |
"name": "datagen", | |
"config": { | |
"plan": [{ | |
"limit": 4, | |
"fields": { | |
"id": { "values": [0, 1, 2, 3] }, | |
"ts": { "range": ["2025-01-01T00:00:00Z", "2025-01-01T00:00:04Z"], "scale": 1000 }, | |
"raw_value": { "values": ["41", "1", "=B0", "=A0+C0"] } | |
} | |
}] | |
}} | |
}]' | |
); | |
create function mentions(cell varchar(64)) returns bigint array; | |
create materialized view latest_cells as with | |
max_ts_per_cell as ( | |
select | |
id, | |
max(ts) as max_ts | |
from | |
spreadsheet_data | |
group by | |
id | |
) | |
select | |
s.id, | |
s.raw_value, | |
ARRAY_APPEND(mentions(s.raw_value), null) as mentioned_cell_ids | |
from | |
spreadsheet_data s | |
join max_ts_per_cell mt on s.id = mt.id and s.ts = mt.max_ts; | |
create materialized view latest_cells_with_mentions as | |
select | |
s.id, | |
s.raw_value, | |
m.mentioned_id | |
from | |
latest_cells s, unnest(s.mentioned_cell_ids) as m(mentioned_id); | |
create materialized view mentions_with_values as | |
select | |
m.id, | |
m.raw_value, | |
m.mentioned_id, | |
sv.raw_value as mentioned_value | |
from | |
latest_cells_with_mentions m | |
left join | |
spreadsheet_data sv on m.mentioned_id = sv.id; | |
create materialized view mentions_aggregated as | |
select | |
id, | |
raw_value, | |
ARRAY_AGG(mentioned_id) as mentions_ids, | |
ARRAY_AGG(mentioned_value) as mentions_values | |
from | |
mentions_with_values | |
group by | |
id, | |
raw_value; | |
create function cell_value(cell varchar(64), mentions_ids bigint array, mentions_values varchar(64) array) returns varchar(64); | |
create materialized view spreadsheet_view as | |
select | |
id, | |
raw_value, | |
cell_value(raw_value, mentions_ids, mentions_values) AS computed_value | |
from | |
mentions_aggregated; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::{BTreeMap, VecDeque}; | |
use xlformula_engine::calculate; | |
use xlformula_engine::parse_formula; | |
use xlformula_engine::NoCustomFunction; | |
use xlformula_engine::types::{Formula, Value, Error, Boolean}; | |
use chrono::DateTime; | |
// Transforms spreadsheet coordinates to cell id's | |
// `A0 -> 0, B0 -> 1, C0 -> 2 ...` | |
fn cell_references_to_ids(crf: &str) -> Option<i64> { | |
let mut col = 0; | |
let mut row = 0; | |
for c in crf.chars() { | |
if c.is_ascii_alphabetic() { | |
col = col * 26 + (c.to_ascii_uppercase() as i64 - 'A' as i64); | |
} else if c.is_ascii_digit() { | |
row = row * 10 + (c as i64 - '0' as i64); | |
} else { | |
return None; | |
} | |
} | |
Some(col + row * 26) | |
} | |
// Returns a list of references for a spreadsheet formula | |
// `42 -> []` `=A0 -> [0]`, `=A0+B0 -> [0, 1]` | |
pub fn mentions(raw_content: Option<String>) -> Result<Option<Vec<Option<i64>>>, Box<dyn std::error::Error>> { | |
let cell_content = raw_content.unwrap_or_else(|| String::new()); | |
let formula = parse_formula::parse_string_to_formula(&cell_content, None::<NoCustomFunction>); | |
let mut formulas = VecDeque::from(vec![formula]); | |
let mut references = vec![]; | |
while !formulas.is_empty() { | |
let formula = formulas.pop_front().unwrap(); | |
match formula { | |
Formula::Reference(reference) => { | |
references.push(reference); | |
}, | |
Formula::Iterator(iterator) => { | |
formulas.extend(iterator); | |
}, | |
Formula::Operation(expression) => { | |
formulas.extend(expression.values); | |
}, | |
_ => {} | |
} | |
} | |
let mut cell_ids: Vec<Option<i64>> = references.iter().map(|r| cell_references_to_ids(r)).collect(); | |
cell_ids.sort_unstable(); | |
Ok(Some(cell_ids)) | |
} | |
pub fn cell_value(raw_content: Option<String>, mentions_ids: Option<Vec<Option<i64>>>, mentions_values: Option<Vec<Option<String>>>) -> Result<Option<String>, Box<dyn std::error::Error>> { | |
let cell_content = raw_content.unwrap_or_else(|| String::new()); | |
let formula = parse_formula::parse_string_to_formula(&*cell_content, None::<NoCustomFunction>); | |
let mentions_ids = mentions_ids.unwrap_or_else(|| vec![]); | |
let mentions_values = mentions_values.unwrap_or_else(|| vec![]); | |
assert_eq!(mentions_ids.len(), mentions_values.len()); | |
let mut context = BTreeMap::new(); | |
for (id, value) in mentions_ids.into_iter().zip(mentions_values.into_iter()) { | |
if let (Some(id), Some(value)) = (id, value) { | |
context.insert(id_to_cell_reference(id), parse_as_value(value)); | |
} | |
} | |
let data_function = |s: String| context.get(&s).cloned().unwrap_or_else(|| Value::Error(Error::Value)); | |
let result = calculate::calculate_formula(formula, Some(&data_function)); | |
let result_str = calculate::result_to_string(result); | |
Ok(Some(result_str)) | |
} | |
fn parse_as_value(input: String) -> Value { | |
if let Ok(number) = input.parse::<f32>() { | |
return Value::Number(number); | |
} | |
if let Ok(boolean) = input.parse::<bool>() { | |
return Value::Boolean(if boolean { Boolean::True } else { Boolean::False }); | |
} | |
if let Ok(date) = DateTime::parse_from_rfc3339(input.as_str()) { | |
return Value::Date(date); | |
} | |
Value::Text(input) | |
} | |
fn id_to_cell_reference(id: i64) -> String { | |
let mut col = id % 26; | |
let row = id / 26; | |
let mut result = String::new(); | |
while col >= 0 { | |
result.push((col as u8 + 'A' as u8) as char); | |
col = col / 26 - 1; | |
} | |
result.push_str(&row.to_string()); | |
result | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
xlformula_engine = { git = "https://github.com/gz/XLFormula-Engine.git", rev = "3b39201" } | |
log = "0.4" | |
chrono = "0.4" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment