oracle-rs 0.1.7

Pure Rust driver for Oracle databases - no OCI/ODPI-C required
Documentation
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
//! LOB (Large Object) operation messages
//!
//! This module implements the LOB operation protocol for reading
//! and writing CLOB, BLOB, and BFILE data.

use crate::buffer::WriteBuffer;
use crate::capabilities::Capabilities;
use crate::constants::{charset, lob_duration, lob_op, FunctionCode, MessageType, OracleType, PacketType, PACKET_HEADER_SIZE};
use crate::error::Result;
use crate::types::LobLocator;
use bytes::Bytes;

/// LOB operation message for reading/writing LOB data
pub struct LobOpMessage<'a> {
    /// The LOB locator (optional for CREATE_TEMP which uses owned bytes)
    locator: Option<&'a LobLocator>,
    /// Owned locator bytes for CREATE_TEMP operation
    owned_locator: Option<Vec<u8>>,
    /// Operation to perform
    operation: u32,
    /// Source offset (1-based for reads, write offset for writes, csfrm for create_temp)
    source_offset: u64,
    /// Destination offset (oracle_type_num for create_temp)
    dest_offset: u64,
    /// Amount to read/write (or new size for trim)
    amount: u64,
    /// Destination length (duration for create_temp)
    dest_length: u32,
    /// Whether to send amount in message
    send_amount: bool,
    /// Data to write (for write operations)
    write_data: Option<&'a [u8]>,
    /// Sequence number
    sequence_number: u8,
    /// Oracle type for the LOB (needed for CREATE_TEMP charset selection)
    oracle_type: Option<OracleType>,
}

impl<'a> LobOpMessage<'a> {
    /// Create a new LOB read message
    pub fn new_read(locator: &'a LobLocator, offset: u64, amount: u64) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::READ,
            source_offset: offset,
            dest_offset: 0,
            amount,
            dest_length: 0,
            send_amount: true,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new LOB write message
    pub fn new_write(locator: &'a LobLocator, offset: u64, data: &'a [u8]) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::WRITE,
            source_offset: offset,
            dest_offset: 0,
            amount: 0,
            dest_length: 0,
            send_amount: false,
            write_data: Some(data),
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new LOB get length message
    pub fn new_get_length(locator: &'a LobLocator) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::GET_LENGTH,
            source_offset: 0,
            dest_offset: 0,
            amount: 0,
            dest_length: 0,
            send_amount: true,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new LOB trim message
    pub fn new_trim(locator: &'a LobLocator, new_size: u64) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::TRIM,
            source_offset: 0,
            dest_offset: 0,
            amount: new_size,
            dest_length: 0,
            send_amount: true,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new LOB get chunk size message
    pub fn new_get_chunk_size(locator: &'a LobLocator) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::GET_CHUNK_SIZE,
            source_offset: 0,
            dest_offset: 0,
            amount: 0,
            dest_length: 0,
            send_amount: true,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new BFILE file exists message
    pub fn new_file_exists(locator: &'a LobLocator) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::FILE_EXISTS,
            source_offset: 0,
            dest_offset: 0,
            amount: 0,
            dest_length: 0,
            send_amount: false,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new BFILE file open message
    pub fn new_file_open(locator: &'a LobLocator) -> Self {
        use crate::constants::lob_flags;
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::FILE_OPEN,
            source_offset: 0,
            dest_offset: 0,
            amount: lob_flags::OPEN_READ_ONLY as u64,
            dest_length: 0,
            send_amount: true,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new BFILE file close message
    pub fn new_file_close(locator: &'a LobLocator) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::FILE_CLOSE,
            source_offset: 0,
            dest_offset: 0,
            amount: 0,
            dest_length: 0,
            send_amount: false,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new BFILE file is open message
    pub fn new_file_is_open(locator: &'a LobLocator) -> Self {
        Self {
            locator: Some(locator),
            owned_locator: None,
            operation: lob_op::FILE_ISOPEN,
            source_offset: 0,
            dest_offset: 0,
            amount: 0,
            dest_length: 0,
            send_amount: false,
            write_data: None,
            sequence_number: 0,
            oracle_type: None,
        }
    }

    /// Create a new CREATE_TEMP LOB message
    ///
    /// Creates a temporary LOB of the specified type on the server.
    /// The locator bytes will be populated after the response is received.
    pub fn new_create_temp(oracle_type: OracleType) -> Self {
        // csfrm: 1 for CLOB (character), 0 for BLOB (binary)
        let csfrm = match oracle_type {
            OracleType::Clob => 1u64,
            _ => 0u64,
        };
        // oracle_type_num as dest_offset
        let ora_type_num = oracle_type as u64;

        Self {
            locator: None,
            owned_locator: Some(vec![0u8; 40]), // Empty 40-byte locator
            operation: lob_op::CREATE_TEMP,
            source_offset: csfrm,
            dest_offset: ora_type_num,
            amount: lob_duration::SESSION,
            dest_length: lob_duration::SESSION as u32,
            send_amount: true,
            write_data: None,
            sequence_number: 0,
            oracle_type: Some(oracle_type),
        }
    }

    /// Get the owned locator bytes (for CREATE_TEMP operations)
    pub fn take_owned_locator(&mut self) -> Option<Vec<u8>> {
        self.owned_locator.take()
    }

    /// Set the owned locator bytes (updated by response parsing)
    pub fn set_owned_locator(&mut self, locator: Vec<u8>) {
        self.owned_locator = Some(locator);
    }

    /// Get oracle type (for CREATE_TEMP)
    pub fn oracle_type(&self) -> Option<OracleType> {
        self.oracle_type
    }

    /// Set the sequence number
    pub fn set_sequence_number(&mut self, seq: u8) {
        self.sequence_number = seq;
    }

    /// Build the LOB operation request packet (for small payloads that fit in one packet)
    pub fn build_request(&self, caps: &Capabilities, large_sdu: bool) -> Result<Bytes> {
        let message = self.build_message_only(caps)?;
        // Total payload = data flags (2) + message
        let payload_len = 2 + message.len();

        // Wrap in DATA packet
        let mut packet = WriteBuffer::new();

        // Packet header
        if large_sdu {
            packet.write_u32_be(payload_len as u32 + PACKET_HEADER_SIZE as u32)?;
        } else {
            packet.write_u16_be(payload_len as u16 + PACKET_HEADER_SIZE as u16)?;
            packet.write_u16_be(0)?; // Checksum
        }
        packet.write_u8(PacketType::Data as u8)?;
        packet.write_u8(0)?; // Reserved flags
        packet.write_u16_be(0)?; // Header checksum

        // Data flags
        packet.write_u16_be(0)?;

        // Message payload
        packet.write_bytes(&message)?;

        Ok(packet.freeze())
    }

    /// Build just the message bytes (without packet header or data flags)
    ///
    /// This is used for multi-packet sending where the message needs to be
    /// split across multiple packets. Each packet will have its own data flags.
    pub fn build_message_only(&self, caps: &Capabilities) -> Result<Bytes> {
        let mut buf = WriteBuffer::new();

        // Write message payload (no data flags - those are per-packet)
        self.write_message(&mut buf, caps)?;

        Ok(buf.freeze())
    }

    /// Write the LOB operation message to the buffer
    fn write_message(&self, buf: &mut WriteBuffer, caps: &Capabilities) -> Result<()> {
        // Function code header
        buf.write_u8(MessageType::Function as u8)?;
        buf.write_u8(FunctionCode::LobOp as u8)?;
        buf.write_u8(self.sequence_number)?;

        // Token number (required for TTC field version >= 18, i.e. Oracle 23ai)
        if caps.ttc_field_version >= 18 {
            buf.write_ub8(0)?;
        }

        // Get locator bytes from either the reference or owned bytes
        let locator_bytes: &[u8] = if let Some(loc) = self.locator {
            loc.locator_bytes()
        } else if let Some(ref owned) = self.owned_locator {
            owned.as_slice()
        } else {
            &[]
        };

        let is_create_temp = self.operation == lob_op::CREATE_TEMP;

        // Source pointer (1 if we have locator bytes)
        if locator_bytes.is_empty() {
            buf.write_u8(0)?;
            buf.write_ub4(0)?;
        } else {
            buf.write_u8(1)?;
            buf.write_ub4(locator_bytes.len() as u32)?;
        }
        // Dest pointer (0)
        buf.write_u8(0)?;
        // Dest length (duration for create_temp, 0 otherwise)
        buf.write_ub4(self.dest_length)?;
        // Short source offset (0, using long offset below)
        buf.write_ub4(0)?;
        // Short dest offset (0)
        buf.write_ub4(0)?;
        // Charset pointer (1 for CREATE_TEMP, 0 otherwise)
        if is_create_temp {
            buf.write_u8(1)?;
        } else {
            buf.write_u8(0)?;
        }
        // Short amount pointer (0)
        buf.write_u8(0)?;
        // NULL LOB pointer (1 for FILE_EXISTS, FILE_ISOPEN, CREATE_TEMP, IS_OPEN)
        if self.operation == lob_op::FILE_EXISTS
            || self.operation == lob_op::FILE_ISOPEN
            || self.operation == lob_op::CREATE_TEMP
            || self.operation == lob_op::IS_OPEN
        {
            buf.write_u8(1)?;
        } else {
            buf.write_u8(0)?;
        }
        // Operation code
        buf.write_ub4(self.operation)?;
        // SCN pointer (0)
        buf.write_u8(0)?;
        // SCN array length (0)
        buf.write_u8(0)?;
        // Source offset (csfrm for create_temp, 1-based for read, write offset for write)
        buf.write_ub8(self.source_offset)?;
        // Dest offset (oracle_type_num for create_temp, 0 otherwise)
        buf.write_ub8(self.dest_offset)?;
        // Amount pointer (1 if send_amount)
        if self.send_amount {
            buf.write_u8(1)?;
        } else {
            buf.write_u8(0)?;
        }
        // Array LOB (3 x uint16be = 0)
        buf.write_u16_be(0)?;
        buf.write_u16_be(0)?;
        buf.write_u16_be(0)?;

        // Write locator bytes
        if !locator_bytes.is_empty() {
            buf.write_bytes(locator_bytes)?;
        }

        // Write charset for CREATE_TEMP
        if is_create_temp {
            // Use UTF8 charset for CLOB/BLOB
            buf.write_ub4(charset::UTF8 as u32)?;
        }

        // Write data for write operations
        if let Some(data) = self.write_data {
            buf.write_u8(MessageType::LobData as u8)?;
            buf.write_bytes_with_length(Some(data))?;
        }

        // Write amount if needed
        if self.send_amount {
            buf.write_ub8(self.amount)?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::constants::OracleType;

    #[test]
    fn test_lob_read_message_creation() {
        let locator_bytes = Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]);
        let locator = LobLocator::new(locator_bytes, 100, 8132, OracleType::Clob, 1);

        let msg = LobOpMessage::new_read(&locator, 1, 100);
        assert_eq!(msg.operation, lob_op::READ);
        assert_eq!(msg.source_offset, 1);
        assert_eq!(msg.amount, 100);
        assert!(msg.send_amount);
    }

    #[test]
    fn test_lob_write_message_creation() {
        let locator_bytes = Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]);
        let locator = LobLocator::new(locator_bytes, 100, 8132, OracleType::Clob, 1);

        let data = b"Hello, World!";
        let msg = LobOpMessage::new_write(&locator, 1, data);
        assert_eq!(msg.operation, lob_op::WRITE);
        assert_eq!(msg.source_offset, 1);
        assert!(msg.write_data.is_some());
    }

    #[test]
    fn test_lob_get_length_message_creation() {
        let locator_bytes = Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]);
        let locator = LobLocator::new(locator_bytes, 100, 8132, OracleType::Clob, 1);

        let msg = LobOpMessage::new_get_length(&locator);
        assert_eq!(msg.operation, lob_op::GET_LENGTH);
        assert!(msg.send_amount);
    }

    #[test]
    fn test_lob_trim_message_creation() {
        let locator_bytes = Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]);
        let locator = LobLocator::new(locator_bytes, 100, 8132, OracleType::Clob, 1);

        let msg = LobOpMessage::new_trim(&locator, 50);
        assert_eq!(msg.operation, lob_op::TRIM);
        assert_eq!(msg.amount, 50);
        assert!(msg.send_amount);
    }
}