1use super::value::{bind_point_id, bind_value, resolve_param, resolve_positional};
4use crate::ast::Value;
5use crate::ast::statement::{ContextPair, FeedbackItem, PointId, QueryInput};
6use crate::error::{QqlError, Span};
7use alloc::format;
8use alloc::string::String;
9
10pub fn bind_query_input<F>(
12 input: &mut QueryInput,
13 lookup: &F,
14 positional: &[Value],
15) -> Result<(), QqlError>
16where
17 F: Fn(&str) -> Option<Value>,
18{
19 match input {
20 QueryInput::Param(name, span) => {
21 let sp = span.as_deref().copied();
22 let val = resolve_param(name, sp, lookup)?;
23 *input = value_to_query_input(val, sp)?;
24 }
25 QueryInput::PositionalParam(idx, span) => {
26 let sp = span.as_deref().copied();
27 let val = resolve_positional(*idx, sp, positional)?;
28 *input = value_to_query_input(val, sp)?;
29 }
30 QueryInput::Point(point) => {
31 bind_point_id(point, lookup, positional)?;
32 }
33 QueryInput::Text {
34 text,
35 text_param,
36 options,
37 ..
38 } => {
39 if let Some(param) = text_param.take() {
40 if let Some(param_name) = param.strip_prefix(':') {
41 let val = resolve_param(param_name, None, lookup)?;
42 if let Value::Str(s) = val {
43 *text = s;
44 } else {
45 return Err(QqlError::validation(
46 "QQL-BIND-TYPE-MISMATCH",
47 format!("parameter ':{param_name}' for TEXT query must be a string"),
48 None,
49 ));
50 }
51 } else if let Some(idx_str) = param.strip_prefix('?') {
52 let idx = idx_str.parse::<usize>().map_err(|_| {
53 QqlError::validation(
54 "QQL-BIND-INVALID-PARAMS",
55 format!("invalid positional parameter index '?{idx_str}'"),
56 None,
57 )
58 })?;
59 let val = resolve_positional(idx, None, positional)?;
60 if let Value::Str(s) = val {
61 *text = s;
62 } else {
63 return Err(QqlError::validation(
64 "QQL-BIND-TYPE-MISMATCH",
65 format!("positional parameter ?{idx} for TEXT query must be a string"),
66 None,
67 ));
68 }
69 } else {
70 let val = resolve_param(¶m, None, lookup)?;
71 if let Value::Str(s) = val {
72 *text = s;
73 } else {
74 return Err(QqlError::validation(
75 "QQL-BIND-TYPE-MISMATCH",
76 format!("parameter ':{param}' for TEXT query must be a string"),
77 None,
78 ));
79 }
80 }
81 }
82 bind_option_values(options, lookup, positional)?;
83 }
84 QueryInput::Image { options, .. } => {
85 bind_option_values(options, lookup, positional)?;
86 }
87 QueryInput::Object {
88 object, options, ..
89 } => {
90 bind_value(object, lookup, positional)?;
91 bind_option_values(options, lookup, positional)?;
92 }
93 _ => {}
94 }
95 Ok(())
96}
97
98fn bind_option_values<F>(
100 options: &mut [(String, Value)],
101 lookup: &F,
102 positional: &[Value],
103) -> Result<(), QqlError>
104where
105 F: Fn(&str) -> Option<Value>,
106{
107 for (_, value) in options {
108 bind_value(value, lookup, positional)?;
109 }
110 Ok(())
111}
112
113pub fn value_to_query_input(val: Value, span: Option<Span>) -> Result<QueryInput, QqlError> {
115 match val {
116 Value::Str(s) => Ok(QueryInput::Text {
117 text: s,
118 model: None,
119 text_param: None,
120 options: alloc::vec::Vec::new(),
121 }),
122 Value::Int(n) if n >= 0 => Ok(QueryInput::Point(PointId::Number(n as u64))),
123 Value::UInt(n) => Ok(QueryInput::Point(PointId::Number(n))),
124 Value::List(_) | Value::Dict(_) | Value::F32Array(_) => {
125 let vec = crate::parser::helpers::vector_from_value(val, span)?;
126 Ok(QueryInput::Vector(vec))
127 }
128 _ => Err(QqlError::validation(
129 "QQL-BIND-TYPE-MISMATCH",
130 format!("unsupported value type for query input: {:?}", val),
131 span,
132 )),
133 }
134}
135
136pub fn bind_context_pair<F>(
138 pair: &mut ContextPair,
139 lookup: &F,
140 positional: &[Value],
141) -> Result<(), QqlError>
142where
143 F: Fn(&str) -> Option<Value>,
144{
145 bind_query_input(&mut pair.positive, lookup, positional)?;
146 bind_query_input(&mut pair.negative, lookup, positional)?;
147 Ok(())
148}
149
150pub fn bind_feedback_item<F>(
152 item: &mut FeedbackItem,
153 lookup: &F,
154 positional: &[Value],
155) -> Result<(), QqlError>
156where
157 F: Fn(&str) -> Option<Value>,
158{
159 bind_query_input(&mut item.example, lookup, positional)
160}