| Crates.io | oracle-rs |
| lib.rs | oracle-rs |
| version | 0.1.5 |
| created_at | 2025-12-15 20:38:39.430402+00 |
| updated_at | 2025-12-18 19:20:13.884602+00 |
| description | Pure Rust driver for Oracle databases - no OCI/ODPI-C required |
| homepage | https://github.com/stiang/oracle-rs |
| repository | https://github.com/stiang/oracle-rs |
| max_upload_size | |
| id | 1986696 |
| size | 908,826 |
A pure Rust driver for Oracle databases. No OCI or ODPI-C dependencies required.
deadpool-oracle crateAdd to your Cargo.toml:
[dependencies]
oracle-rs = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Basic usage:
use oracle_rs::{Config, Connection};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to Oracle
let config = Config::new("localhost", 1521, "FREEPDB1", "user", "password");
let conn = Connection::connect_with_config(config).await?;
// Execute a query
let result = conn.query("SELECT id, name FROM users WHERE active = :1", &[&1]).await?;
for row in result.rows() {
let id: i64 = row.get(0)?;
let name: String = row.get(1)?;
println!("User {}: {}", id, name);
}
Ok(())
}
use oracle_rs::{Config, Connection};
let config = Config::new("hostname", 1521, "service_name", "username", "password");
let conn = Connection::connect_with_config(config).await?;
use oracle_rs::Config;
let config = Config::new("hostname", 2484, "service_name", "username", "password")
.with_tls()?; // Use system root certificates
let conn = Connection::connect_with_config(config).await?;
use oracle_rs::Config;
let config = Config::new("hostname", 2484, "service_name", "username", "password")
.with_wallet("/path/to/wallet", Some("wallet_password"))?;
let conn = Connection::connect_with_config(config).await?;
use oracle_rs::Config;
let config = Config::new("hostname", 1521, "service_name", "username", "password")
.with_drcp("connection_class", "purity");
use oracle_rs::Config;
let config = Config::new("hostname", 1521, "service_name", "username", "password")
.with_statement_cache_size(100); // Cache up to 100 statements
// Simple query
let result = conn.query("SELECT * FROM employees", &[]).await?;
// With bind parameters
let result = conn.query(
"SELECT * FROM employees WHERE department_id = :1 AND salary > :2",
&[&10, &50000.0]
).await?;
// Access rows
for row in result.rows() {
let name: String = row.get("employee_name")?;
let salary: f64 = row.get("salary")?;
}
// INSERT
let result = conn.execute(
"INSERT INTO users (id, name) VALUES (:1, :2)",
&[&1, &"Alice"]
).await?;
println!("Rows inserted: {}", result.rows_affected);
// UPDATE
let result = conn.execute(
"UPDATE users SET name = :1 WHERE id = :2",
&[&"Bob", &1]
).await?;
// DELETE
let result = conn.execute(
"DELETE FROM users WHERE id = :1",
&[&1]
).await?;
use oracle_rs::BatchBuilder;
let batch = BatchBuilder::new("INSERT INTO users (id, name) VALUES (:1, :2)")
.add_row(&[&1, &"Alice"])?
.add_row(&[&2, &"Bob"])?
.add_row(&[&3, &"Charlie"])?;
let result = conn.execute_batch(batch).await?;
// Auto-commit is off by default
conn.execute("INSERT INTO accounts (id, balance) VALUES (:1, :2)", &[&1, &100.0]).await?;
conn.execute("UPDATE accounts SET balance = balance - :1 WHERE id = :2", &[&50.0, &1]).await?;
// Commit the transaction
conn.commit().await?;
// Or rollback on error
conn.rollback().await?;
// Savepoints
conn.savepoint("before_update").await?;
conn.execute("UPDATE accounts SET balance = 0 WHERE id = :1", &[&1]).await?;
conn.rollback_to_savepoint("before_update").await?; // Undo the update
conn.execute(
"BEGIN
UPDATE accounts SET balance = balance + :1 WHERE id = :2;
UPDATE accounts SET balance = balance - :1 WHERE id = :3;
END;",
&[&100.0, &1, &2]
).await?;
use oracle_rs::{Value, OracleType};
let mut out_value = Value::null(OracleType::Number);
conn.execute_with_binds(
"BEGIN :result := calculate_tax(:amount); END;",
&mut [
("result", &mut out_value, BindDirection::Out),
("amount", &mut Value::from(1000.0), BindDirection::In),
]
).await?;
let tax: f64 = out_value.try_into()?;
let mut cursor = Value::null(OracleType::Cursor);
conn.execute_with_binds(
"BEGIN OPEN :cursor FOR SELECT * FROM employees WHERE dept_id = :dept; END;",
&mut [
("cursor", &mut cursor, BindDirection::Out),
("dept", &mut Value::from(10), BindDirection::In),
]
).await?;
// Fetch from the cursor
let result = conn.fetch_ref_cursor(&cursor, 100).await?;
for row in result.rows() {
// Process rows
}
| Oracle Type | Rust Type |
|---|---|
| NUMBER | i8, i16, i32, i64, f32, f64, String |
| VARCHAR2, CHAR | String, &str |
| DATE | chrono::NaiveDateTime |
| TIMESTAMP | chrono::NaiveDateTime |
| TIMESTAMP WITH TIME ZONE | chrono::DateTime<FixedOffset> |
| INTERVAL DAY TO SECOND | chrono::Duration |
| RAW | Vec<u8>, &[u8] |
| CLOB, NCLOB | String (auto-fetched) or streaming |
| BLOB | Vec<u8> (auto-fetched) or streaming |
| BOOLEAN | bool |
| JSON | serde_json::Value |
| VECTOR | Vec<f32>, Vec<f64>, Vec<i8> |
| ROWID | String |
| BINARY_FLOAT | f32 |
| BINARY_DOUBLE | f64 |
// Small LOBs are auto-fetched as String/Vec<u8>
let result = conn.query("SELECT document FROM files WHERE id = :1", &[&1]).await?;
let content: String = result.rows()[0].get("document")?;
// Large LOB streaming
let lob = conn.get_lob("SELECT document FROM files WHERE id = :1", &[&1]).await?;
let mut buffer = vec![0u8; 8192];
while let Some(bytes_read) = lob.read(&mut buffer).await? {
// Process chunk
}
use serde_json::json;
// Insert JSON
let data = json!({"name": "Alice", "roles": ["admin", "user"]});
conn.execute(
"INSERT INTO documents (id, data) VALUES (:1, :2)",
&[&1, &data]
).await?;
// Query JSON
let result = conn.query("SELECT data FROM documents WHERE id = :1", &[&1]).await?;
let data: serde_json::Value = result.rows()[0].get("data")?;
// Insert vector embeddings
let embedding: Vec<f32> = vec![0.1, 0.2, 0.3, /* ... */];
conn.execute(
"INSERT INTO embeddings (id, vector) VALUES (:1, :2)",
&[&1, &embedding]
).await?;
// Vector similarity search
let query_vector: Vec<f32> = get_embedding("search text");
let result = conn.query(
"SELECT id, description FROM embeddings
ORDER BY VECTOR_DISTANCE(vector, :1, COSINE)
FETCH FIRST 10 ROWS ONLY",
&[&query_vector]
).await?;
Use the deadpool-oracle crate for connection pooling:
[dependencies]
oracle-rs = "0.1"
deadpool-oracle = "0.1"
use oracle_rs::Config;
use deadpool_oracle::PoolBuilder;
let config = Config::new("localhost", 1521, "FREEPDB1", "user", "password");
let pool = PoolBuilder::new(config)
.max_size(20)
.build()?;
// Get a connection from the pool
let conn = pool.get().await?;
// Use the connection
let result = conn.query("SELECT * FROM users", &[]).await?;
// Connection is automatically returned to the pool when dropped
// Create a scrollable cursor
let cursor = conn.create_scrollable_cursor(
"SELECT * FROM large_table ORDER BY id"
).await?;
// Navigate the result set
let first = cursor.fetch_first(10).await?; // First 10 rows
let last = cursor.fetch_last(10).await?; // Last 10 rows
let abs = cursor.fetch_absolute(100, 10).await?; // 10 rows starting at position 100
let rel = cursor.fetch_relative(-5, 10).await?; // 10 rows, 5 positions back from current
The following features are planned but not yet available:
Oracle Database 12c Release 1 (12.1) or later. Some features require newer versions:
Licensed under either of:
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.