uqa_sql/semantics/
scalar_projection.rs1use crate::{expr::RowLookup, semantics::expect_column_name, SQLError, ScalarExpr};
10use uqa_core::Value;
11
12pub fn validate_score_projection_args(
13 name: &str,
14 args: &[ScalarExpr],
15 evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
16) -> Result<(), SQLError> {
17 if !(1..=2).contains(&args.len()) {
18 return Err(SQLError::BadArity {
19 name: name.into(),
20 expected: "1..=2".into(),
21 actual: args.len(),
22 });
23 }
24 let query_idx = args.len() - 1;
25 if args.len() == 2 {
26 let _ = expect_column_name(&args[0], &format!("{name}.field"))?;
27 }
28 match evaluate(&args[query_idx])? {
29 Value::Str(_) => Ok(()),
30 other => Err(SQLError::TypeMismatch(format!(
31 "{name}.query must be a string, got {other:?}"
32 ))),
33 }
34}
35
36pub struct HighlightArguments {
37 pub text: String,
38 pub query: String,
39 pub start_tag: String,
40 pub end_tag: String,
41 pub max_fragments: usize,
42 pub fragment_size: usize,
43 pub analyzer: Option<String>,
44}
45
46pub enum HighlightInput {
47 Value(Value),
48 Arguments(HighlightArguments),
49}
50
51#[expect(
53 clippy::too_many_lines,
54 reason = "preserves ordered field and option evaluation"
55)]
56pub fn highlight_arguments(
57 row: &dyn RowLookup,
58 args: &[ScalarExpr],
59 evaluate: &mut dyn FnMut(&ScalarExpr) -> Result<Value, SQLError>,
60) -> Result<HighlightInput, SQLError> {
61 if args.len() < 2 || args.len() > 7 {
62 return Err(SQLError::BadArity {
63 name: "uqa_highlight".into(),
64 expected: "2..=7".into(),
65 actual: args.len(),
66 });
67 }
68 let text = match &args[0] {
69 ScalarExpr::Column(c) => match row.column(c) {
70 Some(Value::Str(s)) => s.clone(),
71 Some(Value::Null) => return Ok(HighlightInput::Value(Value::Null)),
72 Some(other) => format!("{other:?}"),
73 None => return Ok(HighlightInput::Value(Value::Null)),
74 },
75 ScalarExpr::QualifiedColumn { qualifier, column } => {
76 match row.qualified_column(qualifier, column) {
77 Some(Value::Str(s)) => s.clone(),
78 Some(Value::Null) => return Ok(HighlightInput::Value(Value::Null)),
79 Some(other) => format!("{other:?}"),
80 None => return Ok(HighlightInput::Value(Value::Null)),
81 }
82 }
83 other => match evaluate(other)? {
84 Value::Str(s) => s,
85 Value::Null => return Ok(HighlightInput::Value(Value::Null)),
86 v => format!("{v:?}"),
87 },
88 };
89 let query_str = match evaluate(&args[1])? {
90 Value::Str(s) => s,
91 Value::Null => return Ok(HighlightInput::Value(Value::Str(text))),
92 other => {
93 return Err(SQLError::TypeMismatch(format!(
94 "uqa_highlight query must be string, got {other:?}"
95 )));
96 }
97 };
98 let start_tag = match args.get(2) {
99 Some(e) => match evaluate(e)? {
100 Value::Str(s) => s,
101 Value::Null => "<b>".into(),
102 other => {
103 return Err(SQLError::TypeMismatch(format!(
104 "uqa_highlight start_tag must be string, got {other:?}"
105 )));
106 }
107 },
108 None => "<b>".into(),
109 };
110 let end_tag = match args.get(3) {
111 Some(e) => match evaluate(e)? {
112 Value::Str(s) => s,
113 Value::Null => "</b>".into(),
114 other => {
115 return Err(SQLError::TypeMismatch(format!(
116 "uqa_highlight end_tag must be string, got {other:?}"
117 )));
118 }
119 },
120 None => "</b>".into(),
121 };
122 let max_fragments = match args.get(4) {
123 Some(e) => match evaluate(e)? {
124 Value::Int(n) if n >= 0 => usize::try_from(n).map_err(|_| {
125 SQLError::TypeMismatch(format!(
126 "uqa_highlight max_fragments {n} exceeds the platform usize range"
127 ))
128 })?,
129 Value::Null => 0,
130 other => {
131 return Err(SQLError::TypeMismatch(format!(
132 "uqa_highlight max_fragments must be non-negative integer, got {other:?}"
133 )));
134 }
135 },
136 None => 0,
137 };
138 let fragment_size = match args.get(5) {
139 Some(e) => match evaluate(e)? {
140 Value::Int(n) if n > 0 => usize::try_from(n).map_err(|_| {
141 SQLError::TypeMismatch(format!(
142 "uqa_highlight fragment_size {n} exceeds the platform usize range"
143 ))
144 })?,
145 Value::Null => 150,
146 other => {
147 return Err(SQLError::TypeMismatch(format!(
148 "uqa_highlight fragment_size must be positive integer, got {other:?}"
149 )));
150 }
151 },
152 None => 150,
153 };
154 let analyzer = match args.get(6) {
155 Some(expression) => match evaluate(expression)? {
156 Value::Str(name) => Some(name),
157 Value::Null => None,
158 other => {
159 return Err(SQLError::TypeMismatch(format!(
160 "uqa_highlight analyzer must be string, got {other:?}"
161 )))
162 }
163 },
164 None => None,
165 };
166 Ok(HighlightInput::Arguments(HighlightArguments {
167 text,
168 query: query_str,
169 start_tag,
170 end_tag,
171 max_fragments,
172 fragment_size,
173 analyzer,
174 }))
175}