oracle_nosql_rust_sdk/
prepared_statement.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
//
// Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
//
// Licensed under the Universal Permissive License v 1.0 as shown at
//  https://oss.oracle.com/licenses/upl/
//
use crate::error::NoSQLError;
use crate::plan_iter::{PlanIter, PlanIterKind};
use crate::types::{FieldValue, TopologyInfo};

use std::collections::HashMap;
use std::result::Result;

/// A prepared query statement for use in a [`QueryRequest`](crate::QueryRequest).
///
/// PreparedStatement encapsulates a prepared query statement. It includes state
/// that can be sent to a server and executed without re-parsing the query.
///
/// The details of a prepared query are purposefully opaque, as its internal
/// data and implementation may change over time.
///
/// PreparedStatement is only created by calling [`QueryRequest::execute()`](crate::QueryRequest::execute()) followed by
/// [`QueryResult::prepared_statement()`](crate::QueryResult::prepared_statement()).
///
/// The main purpose of a prepared statement is to parse a query for execution many times,
/// with different variables. Here is a simple example using `QueryRequest` and `PreparedStatement` to
/// insert a set of rows into a table (note: it may be more optimal to use [`WriteMultipleRequest`](crate::WriteMultipleRequest)
/// for this specific case, this example is just to show the use of variables in prepared statetments):
/// ```no_run
/// # use oracle_nosql_rust_sdk::{Handle, QueryRequest, NoSQLColumnToFieldValue};
/// # #[tokio::main]
/// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let handle = Handle::builder().build().await?;
/// let prep_result = QueryRequest::new(
///     "declare $id integer; $name string; insert into testusers(id, name) values($id, $name)",
/// )
/// .prepare_only()
/// .execute(&handle)
/// .await?;
/// let data = vec!["jane", "john", "jasper"];
/// let mut qreq = QueryRequest::new_prepared(&prep_result.prepared_statement());
/// for i in 0..data.len() {
///     let id = (i as i32) + 1;
///     qreq.set_variable("$id", &id)?;
///     qreq.set_variable("$name", &data[i])?;
///     let result = qreq.execute(&handle).await?;
///     println!("Insert result = {:?}", result);
/// }
/// # Ok(())
/// # }
#[derive(Default, Clone)]
pub struct PreparedStatement {
    // sql_text represents the application provided SQL text.
    #[allow(dead_code)]
    pub(crate) sql_text: String,

    // query_plan is the string representation of query plan.
    pub(crate) query_plan: String,

    // query_schema is the string representation of query schema.
    pub(crate) query_schema: String,

    // table_name is the table name returned from a prepared query result, if any.
    pub(crate) table_name: Option<String>,

    // namespace is the namespace returned from a prepared query result, if any.
    pub(crate) namespace: Option<String>,

    // operation is the operation code for the query.
    pub(crate) operation: u8,

    // driver_query_plan represents the part of query plan that must be executed at the driver.
    // It is received from the NoSQL database proxy when the query is prepared there.
    // It is deserialized by the driver and not sent back to the database proxy.
    // This is only used for advanced queries.
    pub(crate) driver_query_plan: Box<PlanIter>,

    // topology_info represents the NoSQL database topology information that
    // are required for query execution.
    // This is only used for advanced queries.
    pub(crate) topology_info: Option<TopologyInfo>,

    // statement represents the serialized PreparedStatement created at the backend store.
    // It is opaque for the driver.
    // It is received from the NoSQL database proxy and sent back to the proxy
    // every time a new batch of results is needed.
    pub(crate) statement: Vec<u8>,

    // variable_to_ids maps the name of each external variable to its id, which is
    // a position in a FieldValue array stored in the QueryRequest and
    // holding the values of the variables.
    // This is only used for advanced queries.
    // It is only created when deserializing a prepared statement from a query response.
    pub(crate) variable_to_ids: Option<HashMap<String, i32>>,

    // for driver plans
    pub(crate) num_registers: i32,
    pub(crate) num_iterators: i32,

    pub(crate) data: PreparedStatementData,
}

impl std::fmt::Debug for PreparedStatement {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "PreparedStatement: size={}, data={:?}",
            self.statement.len(),
            self.data
        )
    }
}

#[derive(Debug, Default)]
pub struct PreparedStatementData {
    // bind_variables is a map that associates the name to the value for external
    // variables used in the query.
    //
    // This map is populated by the application using the SetVariable() method.
    // It is sent to the NoSQL database proxy every time a new batch of results is needed.
    // The values in this map are also placed in the runtimeControlBlock
    // FieldValue array, just before the query starts its execution at the driver.
    pub bind_variables: HashMap<String, FieldValue>,
}

// We do this manually because we do not want to clone the bind variables
impl Clone for PreparedStatementData {
    fn clone(&self) -> Self {
        //println!("ps.data.clone(): clearing");
        PreparedStatementData {
            bind_variables: Default::default(),
        }
    }
    fn clone_from(&mut self, _source: &Self) {
        //println!("ps.data.clone_from(): clearing");
        self.bind_variables.clear();
    }
}

impl PreparedStatement {
    pub(crate) fn is_simple(&self) -> bool {
        self.driver_query_plan.get_kind() == PlanIterKind::Empty
    }
    pub(crate) fn is_empty(&self) -> bool {
        self.statement.len() == 0
    }
    // set iterators/etc to their initial values, as if
    // they had just been deserialized
    pub(crate) fn reset(&mut self) -> Result<(), NoSQLError> {
        self.driver_query_plan.reset()?;
        // Do not clear bound variables: that's in a different call
        //self.data = PreparedStatementData::default();
        Ok(())
    }
    pub(crate) fn copy_for_internal(&self) -> Self {
        let mut data = PreparedStatementData::default();
        for (k, v) in &self.data.bind_variables {
            data.bind_variables.insert(k.clone(), v.clone_internal());
        }
        PreparedStatement {
            // we only keep the actual binary prepared statement, all other
            // fields get their defaults
            statement: self.statement.clone(),
            data: data,
            ..Default::default()
        }
    }

    pub(crate) fn set_variable(
        &mut self,
        name: &str,
        value: &FieldValue,
    ) -> Result<(), NoSQLError> {
        // TODO: verify variable should start with '$'
        self.data
            .bind_variables
            .insert(name.to_string(), value.clone_internal());
        Ok(())
    }

    pub(crate) fn set_variable_by_id(
        &mut self,
        id: i32,
        value: &FieldValue,
    ) -> Result<(), NoSQLError> {
        /*
                if let Some(vars) = &self.variable_to_ids {
                    for (k, v) in vars {
                        if *v == id {
                            self.data
                                .bind_variables
                                .insert(k.clone(), value.clone_internal());
                            return Ok(());
                        }
                    }
                    return Err(format!(
                        "prepared statement does not have variable at position {}",
                        id
                    )
                    .as_str()
                    .into());
                }
                Err("prepared statement does not have positional variables".into())
        */
        self.data
            .bind_variables
            .insert(format!("#{}", id), value.clone_internal());
        Ok(())
    }

    pub(crate) fn get_variable_by_id(&self, id: i32) -> Option<&FieldValue> {
        if let Some(vars) = &self.variable_to_ids {
            for (k, v) in vars {
                if *v == id {
                    return self.data.bind_variables.get(k);
                }
            }
        }
        None
    }
}