├── rustfmt.toml ├── .gitignore ├── TODO.md ├── examples ├── using_atoms.rs ├── using_dictionaries.rs ├── publishing.rs ├── connecting_to_kdb.rs ├── using_any.rs ├── working_with_time.rs ├── creating_a_list.rs └── fun_with_symbols.rs ├── src ├── table.rs ├── callbacks.rs ├── k_error.rs ├── error.rs ├── serialization.rs ├── kbox.rs ├── kapi.rs ├── symbol.rs ├── dictionary.rs ├── k.rs ├── type_traits.rs ├── lib.rs ├── k_type.rs ├── any.rs ├── connection.rs ├── date_time_types.rs ├── atom.rs └── list.rs ├── Cargo.toml ├── LICENSE ├── README.md └── Cargo.lock /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Implement Extend on RangeLists 2 | 3 | Dict: 4 | 5 | 6 | MixedList: 7 | 8 | 9 | 10 | Structure: 11 | 12 | lists, 13 | atoms, 14 | primitives, 15 | prelude, -------------------------------------------------------------------------------- /examples/using_atoms.rs: -------------------------------------------------------------------------------- 1 | extern crate kdb; 2 | 3 | use kdb::KBox; 4 | 5 | fn main() { 6 | let mut k = KBox::new_atom(42u8); 7 | println!("{}", k); 8 | assert_eq!(k.value(), 42); 9 | 10 | k.set_value(43); 11 | 12 | assert_eq!(k.value(), 43); 13 | } 14 | -------------------------------------------------------------------------------- /examples/using_dictionaries.rs: -------------------------------------------------------------------------------- 1 | extern crate kdb; 2 | 3 | use kdb::{cast, symbol, Atom, KBox, Symbol}; 4 | fn main() { 5 | //Symbols use a lot of try_into. Sad. 6 | let mut dict = KBox::new_dict(); 7 | 8 | dict.insert(symbol("One"), 1i32); 9 | dict.insert(symbol("Two"), 2i32); 10 | dict.insert(symbol("Three"), 3i32); 11 | 12 | println!("{:?}", cast!(&dict[symbol("Two")]; Atom)); 13 | } 14 | -------------------------------------------------------------------------------- /src/table.rs: -------------------------------------------------------------------------------- 1 | use crate::{k::K, type_traits::KObject}; 2 | 3 | /// Represents a table (a dictionary of columns) in KDB 4 | #[repr(transparent)] 5 | pub struct Table { 6 | k: K, 7 | } 8 | 9 | impl KObject for Table { 10 | #[inline] 11 | fn k_ptr(&self) -> *const K { 12 | &self.k 13 | } 14 | 15 | #[inline] 16 | fn k_ptr_mut(&mut self) -> *mut K { 17 | &mut self.k 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/publishing.rs: -------------------------------------------------------------------------------- 1 | use kdb::{list, symbol, Connection}; 2 | 3 | /// Note you'll need a kdb instance on localhost:4200 with the function "upd" defined for this to work... 4 | fn main() { 5 | let conn = Connection::connect("127.0.0.1", 4200, "", None).unwrap(); 6 | let l = list![i32; 1, 2, 3]; 7 | if let Err(err) = conn.publish("upd", symbol("some_topic"), l) { 8 | println!("Publishing failed: {}", err); 9 | } 10 | println!("Publish succeeded! Probably."); 11 | } 12 | -------------------------------------------------------------------------------- /examples/connecting_to_kdb.rs: -------------------------------------------------------------------------------- 1 | use kdb::{cast, Atom, Connection, ConnectionError, List, Symbol}; 2 | 3 | fn main() -> Result<(), ConnectionError> { 4 | let conn = Connection::connect("127.0.0.1", 4200, "", None)?; 5 | let result = cast!(conn.eval("2+2").unwrap(); Atom); 6 | println!("{}", result.value()); 7 | 8 | //Returning a list of symbols 9 | let result = cast!(conn.eval("`a`b`c").unwrap(); List); 10 | println!("{:?}", &result[0..]); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/using_any.rs: -------------------------------------------------------------------------------- 1 | use kdb::{cast, try_cast, Any, Atom, KBox}; 2 | 3 | fn main() { 4 | let int = KBox::new_atom(42); 5 | 6 | // convert to an "any" value: 7 | let any: KBox = int.into(); 8 | 9 | // convert back to an i32 atom. 10 | let int = cast!(any; Atom); 11 | println!("{:?}", int); 12 | 13 | let any: KBox = int.into(); 14 | // try to convert to a u8 atom. This will fail! 15 | if let Err(e) = try_cast!(any; Atom) { 16 | println!("Error: {}", e); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kdb" 3 | version = "0.3.0" 4 | authors = ["Fifth Row Technologies"] 5 | edition = "2018" 6 | description = "Idiomatic, High performance API for using KDB+ and Q from Rust" 7 | readme = "README.md" 8 | license = "MIT" 9 | keywords = ["kdb", "q"] 10 | categories = ["api-bindings", "database"] 11 | repository = "https://github.com/Fifthrow/rust-kdb" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | uuid = { version = "0.8.2", optional = true } 17 | thiserror="1" 18 | array_iterator="1.3" 19 | 20 | [features] 21 | default = ["uuid"] 22 | embedded = [] -------------------------------------------------------------------------------- /examples/working_with_time.rs: -------------------------------------------------------------------------------- 1 | use kdb::{Date, Timespan, Timestamp}; 2 | use std::{ 3 | convert::TryFrom, 4 | time::{Duration, SystemTime}, 5 | }; 6 | 7 | fn main() { 8 | let t = Timestamp::from(SystemTime::now()); 9 | println!("{} nanos from unix epoch", t.as_nanos_unix()); 10 | println!("{} nanos from 1 Jan 2000 (KDB epoch)", t.as_raw()); 11 | 12 | let ts = Timespan::from_nanos(60_000_000_000); 13 | let ts2 = Timespan::from_secs(60); 14 | let ts3 = Timespan::try_from(Duration::from_secs(60)).unwrap(); 15 | assert_eq!(ts, ts2); 16 | assert_eq!(ts, ts3); 17 | 18 | let d = Date::new(2020, 2, 12); 19 | assert_eq!(d.as_raw(), 20 * 365 + 5 + 31 + 12 - 1); // Days from 1 Jan 2000 20 | } 21 | -------------------------------------------------------------------------------- /examples/creating_a_list.rs: -------------------------------------------------------------------------------- 1 | use kdb::{KBox, List}; 2 | 3 | fn main() { 4 | //Create a list containing the numbers from 1 to 10. 5 | let mut list: KBox> = (1..=10).collect(); 6 | 7 | //Create another list by pushing incrementally 8 | let mut list_2: KBox> = KBox::new_list(); 9 | for i in 11..=20 { 10 | list_2.push(i); 11 | } 12 | 13 | //Append the second list to the first 14 | list.join(list_2); 15 | 16 | // Append from an iterator: 17 | list.extend(21..=30); 18 | 19 | // write out the contents 20 | for i in list.iter() { 21 | println!("{}", i); 22 | } 23 | 24 | // we can also use it as a slice: 25 | for i in &list[..5] { 26 | println!("{}", i) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/callbacks.rs: -------------------------------------------------------------------------------- 1 | use crate::{kapi, Any, Atom, Error, KBox}; 2 | use std::mem; 3 | 4 | /// Callback for using in the `set_callback` function. 5 | pub type Callback = extern "C" fn(i32) -> Option>; 6 | 7 | /// Registers a callback function to be called when data is available on a particular file descriptor. 8 | /// Equivalent to calling `sd1(fd, cb)` in the C API. 9 | pub fn register_callback(fd: i32, cb: Callback) -> Result>, Error> { 10 | unsafe { 11 | let r = kapi::sd1(fd, mem::transmute(cb)); 12 | if r.is_null() { 13 | Err(Error::Callback) 14 | } else { 15 | Ok(KBox::from_raw(r)) 16 | } 17 | } 18 | } 19 | 20 | /// Removes a callback registered on the specified file descriptor with the `register_callback` function 21 | pub fn remove_callback(fd: i32) { 22 | unsafe { kapi::sd0x(fd, 0) }; 23 | } 24 | -------------------------------------------------------------------------------- /examples/fun_with_symbols.rs: -------------------------------------------------------------------------------- 1 | use kdb::{symbol, KBox, Symbol}; 2 | 3 | fn main() { 4 | //Create two identical symbols in different ways, and check that they are equal. 5 | let sym = symbol("Hello, World"); 6 | // Note: converting a string into a symbol is not an infallible operation 7 | // rust strings can contain embedded nuls, whereas symbols cannot. 8 | let sym_2 = Symbol::new(String::from("Hello") + ", World").unwrap(); 9 | assert_eq!(sym, sym_2); 10 | 11 | // As an atom: 12 | let atom = KBox::new_atom(sym); 13 | let atom_2 = KBox::new_atom(Symbol::new(String::from("Hello") + ", World").unwrap()); 14 | 15 | assert_eq!(atom.value(), atom_2.value()); 16 | 17 | // Note that because rust strings are utf-8, and symbols have no encoding requirement, 18 | // this may not display the same way as you will see it in kdb, especially if the string is 19 | // not a valid ASCII or utf-8 string. 20 | println!("{}", sym); 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fifthrow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust KDB 2 | 3 | rust-kdb is an idiomatic Rust wrapper around the C API for KDB+, the ultra fast time series database from KX Systems. 4 | 5 | [![Docs.rs][docs-badge]][docs-url] 6 | [![Crates.io][crates-badge]][crates-url] 7 | [![MIT licensed][mit-badge]][mit-url] 8 | 9 | [docs-badge]: https://docs.rs/kdb/badge.svg 10 | [docs-url]: https://docs.rs/kdb 11 | [crates-badge]: https://img.shields.io/crates/v/kdb.svg 12 | [crates-url]: https://crates.io/crates/kdb 13 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 14 | [mit-url]: LICENSE 15 | 16 | Check out the examples for more information on using it. Performance should be extremely good - 17 | there is little to no overhead over and above using the API directly. 18 | 19 | ## Compilation 20 | 21 | In order to build the library or run the tests or any of the examples, you'll need to have `libkdb.a` available somewhere in `LIBRARY_PATH`. If you'd rather not use library path, you can set the variable `LKDB_LIB_DIR` instead. 22 | 23 | ## Embedding 24 | 25 | To use the library in an embedded context, compile with the the `embedded` feature. Make sure that you are compiling with the right architecture, and linking to the right version of `libkdb.a` for that architecture (either the 32-bit or 64-bit edition). 26 | 27 | ## Future plans 28 | 29 | 1. Table support! 30 | 2. There are a few API calls's that aren't supported yet. 31 | 3. Add better integration between chrono/std::time and the KDB time types. 32 | -------------------------------------------------------------------------------- /src/k_error.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, fmt}; 2 | 3 | use crate::k_type::ERROR; 4 | use crate::kbox::KBox; 5 | use crate::symbol::symbol; 6 | use crate::type_traits::KObject; 7 | use crate::{error::Error, k::K}; 8 | 9 | /// Represents an error in KDB. 10 | pub struct KError { 11 | k: K, 12 | } 13 | 14 | impl KObject for KError { 15 | fn k_ptr(&self) -> *const K { 16 | &self.k 17 | } 18 | 19 | fn k_ptr_mut(&mut self) -> *mut K { 20 | &mut self.k 21 | } 22 | } 23 | 24 | impl KBox { 25 | /// Create a new KDB error from the specified string. 26 | pub fn new_error(msg: &str) -> Self { 27 | let mut err = KBox::new_atom(symbol(msg)).into_raw() as *mut K; 28 | unsafe { 29 | (*err).t = ERROR; 30 | KBox::::from_raw(err) 31 | } 32 | } 33 | } 34 | 35 | impl fmt::Display for KError { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | let cs = unsafe { CStr::from_ptr(self.k.union.s) }; 38 | write!(f, "{}", String::from_utf8_lossy(cs.to_bytes())) 39 | } 40 | } 41 | 42 | impl fmt::Debug for KError { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | let cs = unsafe { CStr::from_ptr(self.k.union.s) }; 45 | write!(f, "{}", String::from_utf8_lossy(cs.to_bytes())) 46 | } 47 | } 48 | impl From> for Error { 49 | fn from(ke: KBox) -> Self { 50 | Error::QError(ke.to_string()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::k_type::KTypeCode; 2 | use std::str::Utf8Error; 3 | use thiserror::Error; 4 | 5 | /// Error type for converting from/to kdb types. 6 | #[derive(Debug, Error)] 7 | pub enum ConversionError { 8 | /// Attempted to cast between objects with two different KDB types 9 | #[error("Invalid k object cast from {from} to {to}")] 10 | InvalidKCast { 11 | /// The type of the object being cast 12 | from: KTypeCode, 13 | /// The type wanted 14 | to: KTypeCode, 15 | }, 16 | /// Duration too long - the timespan would overflow. 17 | #[error("Duration too long for K timespan type")] 18 | DurationTooLong, 19 | /// Symbol is not a valid rust string (not UTF-8) 20 | #[error("Symbol not a valid Rust string")] 21 | InvalidString, 22 | } 23 | 24 | impl From for ConversionError { 25 | fn from(_: Utf8Error) -> ConversionError { 26 | ConversionError::InvalidString 27 | } 28 | } 29 | 30 | /// The error type for connecting to KDB. 31 | #[derive(Debug, Error)] 32 | pub enum ConnectionError { 33 | /// Credentials were incorrect. 34 | #[error("Bad credentials")] 35 | BadCredentials, 36 | /// Unable to connect. 37 | #[error("Could not connect")] 38 | CouldNotConnect, 39 | /// Timed out. 40 | #[error("Timeout")] 41 | Timeout, 42 | } 43 | 44 | /// The error type for Q query execution. 45 | #[derive(Debug, Error)] 46 | pub enum Error { 47 | /// Network error accessing remote KDB instance. 48 | #[error("Network error")] 49 | NetworkError, 50 | /// Error setting callback using sd1 51 | #[error("Error setting callback")] 52 | Callback, 53 | /// Custom error from query. 54 | #[error("Query failed: {0}")] 55 | QError(String), 56 | /// Unknown Error. 57 | #[error("Query failed: [unknown q error]")] 58 | UnknownQError, 59 | } 60 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "array_iterator" 5 | version = "1.3.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "163c3f9e62eea089971c2b15cbd1e13f7c667f9911d3e2d5a1b418a2316859ec" 8 | 9 | [[package]] 10 | name = "kdb" 11 | version = "0.3.0" 12 | dependencies = [ 13 | "array_iterator", 14 | "thiserror", 15 | "uuid", 16 | ] 17 | 18 | [[package]] 19 | name = "proc-macro2" 20 | version = "1.0.24" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 23 | dependencies = [ 24 | "unicode-xid", 25 | ] 26 | 27 | [[package]] 28 | name = "quote" 29 | version = "1.0.9" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 32 | dependencies = [ 33 | "proc-macro2", 34 | ] 35 | 36 | [[package]] 37 | name = "syn" 38 | version = "1.0.60" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 41 | dependencies = [ 42 | "proc-macro2", 43 | "quote", 44 | "unicode-xid", 45 | ] 46 | 47 | [[package]] 48 | name = "thiserror" 49 | version = "1.0.23" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 52 | dependencies = [ 53 | "thiserror-impl", 54 | ] 55 | 56 | [[package]] 57 | name = "thiserror-impl" 58 | version = "1.0.23" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 61 | dependencies = [ 62 | "proc-macro2", 63 | "quote", 64 | "syn", 65 | ] 66 | 67 | [[package]] 68 | name = "unicode-xid" 69 | version = "0.2.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 72 | 73 | [[package]] 74 | name = "uuid" 75 | version = "0.8.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 78 | -------------------------------------------------------------------------------- /src/serialization.rs: -------------------------------------------------------------------------------- 1 | use crate::{k::K, k_type::ERROR, kapi, type_traits::KObject, Any, KBox, KError, List}; 2 | 3 | /// Describes how to perform serialization when using `b9_serialize`. 4 | pub enum SerializationMode { 5 | /// Valid for V3.0+ for serializing/deserializing within the same process. 6 | InProc = -1, 7 | /// unenumerate, block serialization of timespan and timestamp (for working with versions prior to V2.6). 8 | Unenumerate = 0, 9 | /// Retain enumerations, allow serialization of timespan and timestamp: Useful for passing data between threads. 10 | Enumerate = 1, 11 | /// Unenumerate, allow serialization of timespan and timestamp. 12 | UnenumerateWithTimestamps = 2, 13 | /// Unenumerate, compress, allow serialization of timespan and timestamp. 14 | Compress = 3, 15 | } 16 | 17 | /// Serialize a K object using KDB serialization. 18 | #[inline] 19 | pub fn b9_serialize(mode: SerializationMode, k: impl AsRef) -> Result>, KBox> { 20 | b9_serialize_any(mode, k.as_ref()) 21 | } 22 | 23 | unsafe fn wrap_ee(k: *mut K) -> Result, KBox> { 24 | let r = kapi::ee(k); 25 | if (*r).t == ERROR { 26 | Err(KBox::from_raw(r)) 27 | } else { 28 | Ok(KBox::from_raw(r)) 29 | } 30 | } 31 | 32 | fn b9_serialize_any(mode: SerializationMode, k: &Any) -> Result>, KBox> { 33 | unsafe { wrap_ee(kapi::b9(mode as i32, k.k_ptr())) } 34 | } 35 | 36 | /// Decode a serialized K object. 37 | #[inline] 38 | pub fn d9_deserialize(k: impl AsRef>) -> Result, KBox> { 39 | unsafe { wrap_ee(kapi::d9(k.as_ref().k_ptr() as *mut _) as *mut _) } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | use crate::{cast, list}; 46 | 47 | #[test] 48 | fn b9_d9_roundtrips() { 49 | let l = list![i32; 1, 2, 3]; 50 | 51 | let bytes = b9_serialize(SerializationMode::InProc, l.as_ref()).unwrap(); 52 | let v = cast!(d9_deserialize(bytes).unwrap(); List); 53 | assert_eq!(v.as_slice(), &[1, 2, 3]); 54 | 55 | let bytes = b9_serialize(SerializationMode::Enumerate, l.as_ref()).unwrap(); 56 | let v = cast!(d9_deserialize(bytes).unwrap(); List); 57 | assert_eq!(v.as_slice(), &[1, 2, 3]); 58 | 59 | let bytes = b9_serialize(SerializationMode::Unenumerate, l.as_ref()).unwrap(); 60 | let v = cast!(d9_deserialize(bytes).unwrap(); List); 61 | assert_eq!(v.as_slice(), &[1, 2, 3]); 62 | 63 | let bytes = b9_serialize(SerializationMode::Compress, l.as_ref()).unwrap(); 64 | let v = cast!(d9_deserialize(bytes).unwrap(); List); 65 | assert_eq!(v.as_slice(), &[1, 2, 3]); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/kbox.rs: -------------------------------------------------------------------------------- 1 | use crate::type_traits::KObject; 2 | use crate::{k::K, kapi}; 3 | use std::mem::ManuallyDrop; 4 | use std::ops::Deref; 5 | use std::ptr::NonNull; 6 | use std::{fmt, ops::DerefMut}; 7 | 8 | /// Represents a memory managed K pointer. They are the 9 | /// KDB equivalent of a Rust Box, a zero overhead wrapper 10 | /// around a K pointer. It will call `r0` to decrement the reference 11 | /// count when it is dropped. 12 | #[repr(transparent)] 13 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub struct KBox { 15 | pub(crate) k: NonNull, 16 | } 17 | 18 | impl KBox { 19 | /// Converts a box into a raw unmanged K pointer. 20 | /// Note that into raw will consume the KBox, and not call 21 | /// r0, so it's possible to leak memory by doing this. 22 | pub fn into_raw(self) -> *mut T { 23 | ManuallyDrop::new(self).k.as_ptr() 24 | } 25 | 26 | /// Converts a raw K pointer into a boxed K object. 27 | /// This is the reciprocal of into_raw 28 | /// 29 | /// # Safety 30 | /// 31 | /// The type of the k pointer must match the type of the KBox being used. 32 | /// The pointer must not be null. 33 | /// 34 | /// Do not use this to take ownership of a kdb callback function parameter, 35 | /// use from_shared instead. 36 | pub unsafe fn from_raw(k: *mut K) -> Self { 37 | KBox { 38 | k: NonNull::new_unchecked(k as *mut T), 39 | } 40 | } 41 | 42 | /// Takes a reference and calls r1, incrementing the reference count 43 | /// and "re-boxing" it. Typically this is a bad thing as you have multiple 44 | /// owned references and kdb does not provide equivalent guarantees to rust 45 | /// about what happens to shared references (especially when reallocating a list for example) 46 | /// 47 | /// However in the embedded case, where you do not own the parameter and you wish to manipulate it 48 | /// without copying the data, then you need this functionality. 49 | /// 50 | /// # Safety 51 | /// 52 | /// A reference should not be owned by more than one `KBox` instance. 53 | pub unsafe fn from_shared(t: &mut T) -> Self { 54 | KBox { 55 | k: NonNull::new_unchecked(kapi::r1(t.k_ptr_mut()) as *mut T), 56 | } 57 | } 58 | } 59 | 60 | impl Drop for KBox { 61 | fn drop(&mut self) { 62 | unsafe { 63 | kapi::r0(self.k.as_mut().k_ptr_mut()); 64 | } 65 | } 66 | } 67 | 68 | impl Deref for KBox { 69 | type Target = T; 70 | 71 | fn deref(&self) -> &T { 72 | unsafe { self.k.as_ref() } 73 | } 74 | } 75 | 76 | impl DerefMut for KBox { 77 | fn deref_mut(&mut self) -> &mut T { 78 | unsafe { self.k.as_mut() } 79 | } 80 | } 81 | 82 | impl AsRef for KBox { 83 | fn as_ref(&self) -> &T { 84 | unsafe { self.k.as_ref() } 85 | } 86 | } 87 | 88 | impl fmt::Debug for KBox { 89 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 | write!(f, "KBox({:?})", self.as_ref()) 91 | } 92 | } 93 | 94 | impl fmt::Display for KBox { 95 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 96 | write!(f, "{}", self.as_ref()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/kapi.rs: -------------------------------------------------------------------------------- 1 | //! Provides ffi definitions for the KDB C API. 2 | #![allow(missing_docs)] 3 | 4 | use crate::date_time_types::{Minute, Month, Second}; 5 | use crate::k::{F, I, J, K, S, V}; 6 | use crate::k_type::{MINUTE_ATOM, MONTH_ATOM, SECOND_ATOM, TIMESPAN_ATOM, TIMESTAMP_ATOM}; 7 | 8 | pub type KCallback = extern "C" fn(arg1: I) -> *const K; 9 | 10 | pub(crate) unsafe fn tst(nanos: i64) -> *const K { 11 | ktj(TIMESTAMP_ATOM.into(), nanos) 12 | } 13 | 14 | pub(crate) unsafe fn tsp(nanos: i64) -> *const K { 15 | ktj(TIMESPAN_ATOM.into(), nanos) 16 | } 17 | 18 | pub(crate) unsafe fn ksec(sec: Second) -> *mut K { 19 | let atom = ka(SECOND_ATOM.into()); 20 | (*atom).union.sec = sec; 21 | atom 22 | } 23 | 24 | pub(crate) unsafe fn kmin(min: Minute) -> *mut K { 25 | let atom = ka(MINUTE_ATOM.into()); 26 | (*atom).union.min = min; 27 | atom 28 | } 29 | 30 | pub(crate) unsafe fn kmonth(m: Month) -> *mut K { 31 | let atom = ka(MONTH_ATOM.into()); 32 | (*atom).union.m = m; 33 | atom 34 | } 35 | 36 | #[cfg_attr(not(feature = "embedded"), link(name = "kdb"))] 37 | extern "C" { 38 | pub fn b9(mode: I, x: *const K) -> *mut K; 39 | 40 | pub fn d9(x: *const K) -> *const K; 41 | pub fn dj(date: I) -> I; 42 | pub fn dl(f: *mut V, n: I) -> *const K; 43 | pub fn dot(x: *const K, y: *const K) -> *mut K; 44 | 45 | pub fn ee(x: *mut K) -> *mut K; 46 | 47 | pub fn ja(list: *mut *mut K, atom: *const V) -> *mut K; 48 | pub fn js(list: *mut *mut K, symbol: S) -> *mut K; 49 | pub fn jk(list: *mut *mut K, k: *const K) -> *mut K; 50 | pub fn jv(list1: *mut *mut K, list2: *const K) -> *mut K; 51 | 52 | pub fn k(handle: I, query: S, ...) -> *mut K; 53 | pub fn ka(k_type: I) -> *mut K; 54 | pub fn kb(boolean: I) -> *mut K; 55 | pub fn kc(c_char: I) -> *mut K; 56 | pub fn kd(date: I) -> *mut K; 57 | pub fn ke(real: F) -> *mut K; 58 | pub fn kf(float: F) -> *mut K; 59 | pub fn kg(byte: I) -> *mut K; 60 | pub fn kh(short: I) -> *mut K; 61 | pub fn ki(int: I) -> *mut K; 62 | pub fn kj(long: J) -> *mut K; 63 | pub fn kp(c_str: S) -> *mut K; 64 | pub fn kpn(c_str: S, len: J) -> *mut K; 65 | pub fn krr(c_str: S) -> *mut K; 66 | pub fn ks(c_str: S) -> *mut K; 67 | pub fn kt(time: I) -> *mut K; 68 | pub fn ktd(keyed_table: *const K) -> *const K; 69 | pub fn ktj(begin: I, end: J) -> *const K; 70 | pub fn ktn(k_type: I, len: J) -> *mut K; 71 | 72 | #[cfg(feature = "uuid")] 73 | pub fn ku(guid: uuid::Uuid) -> *mut K; 74 | pub fn kz(date_time: F) -> *const K; 75 | 76 | pub fn m9() -> V; 77 | 78 | pub fn orr(c_str: S) -> *const K; 79 | pub fn okx(x: *const K) -> I; 80 | 81 | pub fn r0(k: *mut K) -> V; 82 | pub fn r1(k: *mut K) -> *mut K; 83 | 84 | pub fn sd1(d: I, cb: Option) -> *mut K; 85 | pub fn sd0(d: I) -> V; 86 | pub fn sd0x(d: I, f: I) -> V; 87 | pub fn setm(m: I) -> I; 88 | pub fn ss(c_str: S) -> S; 89 | pub fn sn(c_str: S, len: I) -> S; 90 | 91 | pub fn xT(dict: *const K) -> *const K; 92 | pub fn xD(keys: *const K, values: *const K) -> *const K; 93 | 94 | pub fn ymd(y: I, m: I, d: I) -> I; 95 | } 96 | 97 | #[cfg(not(feature = "embedded"))] 98 | #[link(name = "kdb")] 99 | extern "C" { 100 | pub fn kclose(handle: I) -> V; 101 | pub fn khp(hostname: S, port: I) -> I; 102 | pub fn khpu(hostname: S, port: I, credentials: S) -> I; 103 | pub fn khpun(hostname: S, port: I, credentials: S, timeout: I) -> I; 104 | } 105 | -------------------------------------------------------------------------------- /src/symbol.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ConversionError; 2 | use crate::kapi; 3 | use std::ffi::{c_void, CStr}; 4 | use std::fmt; 5 | use thiserror::Error; 6 | 7 | /// Error type for string to symbol conversions 8 | #[derive(Debug, Error)] 9 | pub enum SymbolError { 10 | /// An embedded NUL character was found in the string. The string would be truncated at this point if it were passed to KDB 11 | #[error("Embedded NUL character found at index {0}")] 12 | InternalNul(usize), 13 | /// The string is too long to convert to a symbol. It can be a maximum of 2GB. 14 | #[error("String too long ({0} chars)")] 15 | StringTooLong(usize), 16 | } 17 | 18 | /// Represents a KDB Symbol (interned string) 19 | /// Implements basic symbol operations for efficiency 20 | /// Can be converted to/from strings 21 | #[repr(transparent)] 22 | #[derive(Clone, Copy)] 23 | pub struct Symbol(*const i8); 24 | 25 | impl PartialEq for Symbol { 26 | fn eq(&self, other: &Symbol) -> bool { 27 | std::ptr::eq(self.0, other.0) 28 | } 29 | } 30 | 31 | impl Eq for Symbol {} 32 | 33 | impl std::hash::Hash for Symbol { 34 | fn hash(&self, state: &mut H) { 35 | std::ptr::hash(self.0, state) 36 | } 37 | } 38 | 39 | extern "C" { 40 | fn memchr(cx: *const c_void, c: i32, n: usize) -> *mut c_void; 41 | } 42 | 43 | impl Symbol { 44 | /// Create a new symbol from the specified string. If the string is 45 | /// too long, or contains an embedded nul character, then it returns an error. 46 | pub fn new>(st: T) -> Result { 47 | let s = st.as_ref(); 48 | 49 | if s.len() > isize::MAX as usize { 50 | return Err(SymbolError::StringTooLong(s.len())); 51 | } 52 | 53 | let first_nul = unsafe { memchr(s.as_ptr() as *const c_void, 0, s.len()) }; 54 | if !first_nul.is_null() { 55 | return Err(SymbolError::InternalNul(first_nul as usize - s.as_ptr() as usize)); 56 | } 57 | 58 | Ok(Symbol(unsafe { kapi::sn(s.as_ptr() as *const i8, s.len() as i32) })) 59 | } 60 | 61 | /// Creates a new symbol, skipping the safety checks for length 62 | /// 63 | /// # Safety 64 | /// 65 | /// Any string passed in must not contain embedded nul characters (`\0`). 66 | /// It's length must be less than or equal to isize::MAX. 67 | pub unsafe fn new_unchecked>(st: T) -> Symbol { 68 | let s = st.as_ref(); 69 | Symbol(kapi::sn(s.as_ptr() as *const i8, s.len() as i32)) 70 | } 71 | 72 | /// Attempts to convert to a valid utf-8 string. 73 | /// This will return an error if the string contains invalid utf-8 characters. 74 | /// This function does not allocate. 75 | pub fn try_as_str(&self) -> Result<&'static str, ConversionError> { 76 | Ok(unsafe { CStr::from_ptr(self.0).to_str()? }) 77 | } 78 | 79 | /// Converts the symbol to a rust str without checking if it is valid. 80 | /// 81 | /// # Safety 82 | /// 83 | /// The string must be valid UTF-8. 84 | /// It's length must be less than or equal to isize::MAX. 85 | pub unsafe fn as_str_unchecked(&self) -> &'static str { 86 | std::str::from_utf8_unchecked(CStr::from_ptr(self.0).to_bytes()) 87 | } 88 | } 89 | 90 | impl From for *const i8 { 91 | fn from(sym: Symbol) -> Self { 92 | sym.0 93 | } 94 | } 95 | 96 | impl fmt::Debug for Symbol { 97 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 98 | fmt::Display::fmt(self, f) 99 | } 100 | } 101 | 102 | impl fmt::Display for Symbol { 103 | /// Display for symbol will always render a string representation of the symbol. If the 104 | /// string contains invalid characters it will strip them from the string. 105 | /// This function will allocate only if the string conatins invalid utf-8 characters. 106 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 107 | let cs = unsafe { CStr::from_ptr(self.0) }; 108 | write!(f, "{}", String::from_utf8_lossy(cs.to_bytes())) 109 | } 110 | } 111 | 112 | /// Helper for succinctly creating a symbol. If the string passed in is not a valid symbol, 113 | /// i.e. it contains embedded nuls, then this function will panic. 114 | pub fn symbol(s: &str) -> Symbol { 115 | Symbol::new(s).unwrap() 116 | } 117 | -------------------------------------------------------------------------------- /src/dictionary.rs: -------------------------------------------------------------------------------- 1 | use crate::list::List; 2 | use crate::{any::Any, k::K}; 3 | use crate::{k_type::MIXED_LIST, kapi, type_traits::KObject}; 4 | use crate::{ 5 | k_type::{KTypeCode, DICT}, 6 | kbox::KBox, 7 | type_traits::KTyped, 8 | }; 9 | use std::{mem, ops::Index}; 10 | 11 | /// A key value based dictionary. 12 | #[repr(transparent)] 13 | pub struct Dictionary { 14 | k: K, 15 | } 16 | 17 | impl Dictionary { 18 | fn key_list_mut(&mut self) -> &mut KBox> { 19 | unsafe { &mut *(&mut self.k.union.dict.k as *mut _ as *mut KBox>) } 20 | } 21 | 22 | fn value_list_mut(&mut self) -> &mut KBox> { 23 | unsafe { &mut *(&mut self.k.union.dict.v as *mut _ as *mut KBox>) } 24 | } 25 | 26 | fn key_list(&self) -> &KBox> { 27 | unsafe { &*(&self.k.union.dict.k as *const _ as *const KBox>) } 28 | } 29 | 30 | fn value_list(&self) -> &KBox> { 31 | unsafe { &*(&self.k.union.dict.v as *const _ as *const KBox>) } 32 | } 33 | 34 | /// The number of items in the dictionary. 35 | #[inline] 36 | pub fn len(&self) -> usize { 37 | self.key_list().len() 38 | } 39 | 40 | /// Returns true if the dictionary has no items. 41 | #[inline] 42 | pub fn is_empty(&self) -> bool { 43 | self.len() == 0 44 | } 45 | 46 | /// Gets a slice containing all the keys in this dictionary. 47 | #[inline] 48 | pub fn keys(&self) -> &[KBox] { 49 | &self.key_list()[..] 50 | } 51 | 52 | /// Gets a slice containing all the values in this dictionary. 53 | #[inline] 54 | pub fn values(&self) -> &[KBox] { 55 | &self.value_list()[..] 56 | } 57 | 58 | /// Insert a specified key and value at the end of the dictionary. 59 | /// No checks are done on uniqueness so duplicates are possible. 60 | #[inline] 61 | pub fn insert(&mut self, key: impl Into>, value: impl Into>) { 62 | self.key_list_mut().push(key.into()); 63 | self.value_list_mut().push(value.into()); 64 | } 65 | 66 | /// Gets a value by key. Note that KDB dictionaries are treated as unordered and hence this is an O(n) operation. 67 | #[inline] 68 | pub fn get>>(&self, key: T) -> Option<&KBox> { 69 | let key = key.into(); 70 | let index = self 71 | .keys() 72 | .iter() 73 | .enumerate() 74 | .find(|(_, k2)| unsafe { *k2.k.as_ptr() == *key.k.as_ptr() }) 75 | .map(|(i, _)| i)?; 76 | self.values().get(index) 77 | } 78 | 79 | /// An iterator through every value in the KDB object 80 | #[inline] 81 | pub fn iter(&self) -> impl Iterator, &KBox)> { 82 | self.keys().iter().zip(self.values().iter()) 83 | } 84 | } 85 | 86 | impl Index for Dictionary 87 | where 88 | for<'a> T: Into>, 89 | { 90 | type Output = Any; 91 | 92 | fn index(&self, index: T) -> &Self::Output { 93 | self.get(index).unwrap() 94 | } 95 | } 96 | 97 | impl KObject for Dictionary { 98 | #[inline] 99 | fn k_ptr(&self) -> *const K { 100 | &self.k 101 | } 102 | 103 | #[inline] 104 | fn k_ptr_mut(&mut self) -> *mut K { 105 | &mut self.k 106 | } 107 | } 108 | 109 | impl KTyped for Dictionary { 110 | const K_TYPE: KTypeCode = DICT; 111 | } 112 | 113 | impl KBox { 114 | /// Create a new empty dictionary. 115 | pub fn new_dict() -> Self { 116 | unsafe { 117 | let keys = kapi::ktn(MIXED_LIST.into(), 0) as *mut K; 118 | let values = kapi::ktn(MIXED_LIST.into(), 0) as *mut K; 119 | mem::transmute(kapi::xD(keys, values)) 120 | } 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use crate::symbol; 127 | 128 | use super::*; 129 | 130 | #[test] 131 | fn insert_appends_items_to_dictionary() { 132 | let mut dict = KBox::new_dict(); 133 | dict.insert(symbol("Hello"), symbol("World")); 134 | 135 | assert_eq!(dict.len(), 1); 136 | } 137 | 138 | #[test] 139 | fn get_retrieves_items_by_key() { 140 | let mut dict = KBox::new_dict(); 141 | dict.insert(symbol("Hello"), symbol("World")); 142 | 143 | let val = dict.get(symbol("Hello")).unwrap(); 144 | 145 | assert_eq!(*val.as_ref(), *KBox::::from(symbol("World")).as_ref()); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/k.rs: -------------------------------------------------------------------------------- 1 | use crate::date_time_types::*; 2 | use crate::k_type::KTypeCode; 3 | use crate::symbol::Symbol; 4 | use std::ffi::c_void; 5 | use std::fmt; 6 | 7 | pub type S = *const i8; 8 | pub type C = i8; 9 | pub type G = u8; 10 | pub type H = i16; 11 | pub type I = i32; 12 | pub type J = i64; 13 | pub type E = f32; 14 | pub type F = f64; 15 | pub type V = std::ffi::c_void; 16 | 17 | #[repr(C)] 18 | #[derive(Clone, Copy)] 19 | pub struct List { 20 | pub n: J, 21 | pub g0: [u8; 1], 22 | } 23 | 24 | #[cfg(feature = "uuid")] 25 | #[repr(C)] 26 | #[derive(Clone, Copy)] 27 | pub struct GuidWithLen { 28 | pub n: J, 29 | pub u: uuid::Uuid, 30 | } 31 | 32 | #[cfg(feature = "uuid")] 33 | impl From for uuid::Uuid { 34 | fn from(g: GuidWithLen) -> Self { 35 | g.u 36 | } 37 | } 38 | 39 | #[cfg(feature = "uuid")] 40 | impl<'a> From<&'a mut GuidWithLen> for &'a mut uuid::Uuid { 41 | fn from(g: &'a mut GuidWithLen) -> Self { 42 | &mut g.u 43 | } 44 | } 45 | 46 | #[repr(C)] 47 | #[derive(Clone, Copy)] 48 | pub struct Dict { 49 | pub n: J, 50 | pub k: *mut K, 51 | pub v: *mut K, 52 | } 53 | 54 | #[repr(C)] 55 | pub union KUnion { 56 | pub c: C, 57 | pub g: G, 58 | pub h: H, 59 | pub i: I, 60 | pub j: J, 61 | pub e: E, 62 | pub f: F, 63 | pub s: S, 64 | #[cfg(feature = "uuid")] 65 | pub u: GuidWithLen, 66 | pub k0: *mut K, 67 | pub list: List, 68 | // custom accessors for the wrapping types - these make the implementation macros easier 69 | // so we don't have to do special cases or lots of typecasting - we can just get the union 70 | // to do that for us! Note that all the wrapping types *must* be repr(transparent), otherwise 71 | // it's undefined behaviour as the the underlying representation in the compiler is not defined. 72 | // It's worth noting that KDB uses a bit as a boolean type, but stores it in a byte. Coincidentally 73 | // that maps exactly to a rust bool (which *must* be either 1 or 0 else behaviour is undefined). 74 | pub dict: Dict, 75 | pub bl: bool, 76 | pub sym: Symbol, 77 | pub ts: Timespan, 78 | pub t: Time, 79 | pub m: Month, 80 | pub tst: Timestamp, 81 | pub min: Minute, 82 | pub sec: Second, 83 | pub d: Date, 84 | pub dt: DateTime, 85 | } 86 | 87 | /// The core raw K type. This is exposed, but there should be no need to use it in practice as Atom and List 88 | /// are both ABI compatible with this K. 89 | #[repr(C)] 90 | pub struct K { 91 | pub m: i8, 92 | pub a: i8, 93 | pub t: KTypeCode, 94 | pub u: C, 95 | pub r: I, 96 | pub union: KUnion, 97 | } 98 | 99 | #[repr(transparent)] 100 | #[derive(Clone, Copy)] 101 | pub struct Attr(u8); 102 | 103 | impl Attr { 104 | pub fn sorted(self) -> bool { 105 | self.0 == 1 106 | } 107 | pub fn unique(self) -> bool { 108 | self.0 == 2 109 | } 110 | pub fn partioned(self) -> bool { 111 | self.0 == 3 112 | } 113 | pub fn grouped(self) -> bool { 114 | self.0 == 5 115 | } 116 | } 117 | 118 | impl std::fmt::Debug for Attr { 119 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 120 | write!(f, "Attributes(")?; 121 | if self.sorted() { 122 | write!(f, " Sorted")?; 123 | } 124 | if self.unique() { 125 | write!(f, " Unique")?; 126 | } 127 | if self.partioned() { 128 | write!(f, " Partioned")?; 129 | } 130 | if self.grouped() { 131 | write!(f, " Grouped")?; 132 | } 133 | writeln!(f, " )")?; 134 | Ok(()) 135 | } 136 | } 137 | 138 | impl fmt::Debug for K { 139 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 140 | writeln!( 141 | f, 142 | "K{{ {k_type:?}, {attrs:?}, ref_count = {r}, value = ...}}", 143 | k_type = self.t, 144 | attrs = self.u, 145 | r = self.r, 146 | )?; 147 | Ok(()) 148 | } 149 | } 150 | 151 | extern "C" { 152 | fn memcmp(cx: *const c_void, ct: *const c_void, n: usize) -> i32; 153 | } 154 | 155 | impl PartialEq for K { 156 | fn eq(&self, other: &K) -> bool { 157 | if self.t != other.t { 158 | return false; 159 | } 160 | 161 | match i32::from(self.t) { 162 | //Atoms 163 | t if t > -20 && t < 0 => unsafe { 164 | memcmp( 165 | &self.union as *const _ as _, 166 | &other.union as *const _ as _, 167 | self.t.atom_size(), 168 | ) == 0 169 | }, 170 | _ => unimplemented!("Comparison for non-atom K objects not implemented"), 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/type_traits.rs: -------------------------------------------------------------------------------- 1 | use crate::k::K; 2 | use crate::k_type::*; 3 | 4 | /// Represents a known type that can be stored in and retrieved from a list or an atom. 5 | pub trait KValue: private::Sealed { 6 | ///TYPE_CODE is *not* the K 't' field. it's a value than can be 7 | /// Converted into a list or an atom code. 8 | const TYPE_CODE: TypeCode; 9 | 10 | unsafe fn from_k(k: &K) -> Self; 11 | unsafe fn as_mutable(k: &mut K) -> &mut Self; 12 | fn into_k(self) -> *const K; 13 | } 14 | 15 | pub trait KTyped { 16 | const K_TYPE: KTypeCode; 17 | } 18 | 19 | /// Indicates a type that wraps a `K` object. This trait is sealed and can't be implemented 20 | /// from other crates. 21 | pub trait KObject: private::Sealed { 22 | /// Returns a pointer to the underlying K type 23 | fn k_ptr(&self) -> *const K; 24 | /// Returns a mutable pointer to the underlying K type 25 | fn k_ptr_mut(&mut self) -> *mut K; 26 | } 27 | 28 | /// Indicates something that can be stored in a List. Basically this is 29 | /// All KValues and the Any type. Used to provide list concatenation functions. 30 | // This trait is sealed and can't be implemented from other crates. 31 | pub trait KListable: private::Sealed { 32 | type ListItem; //: std::fmt::Debug; 33 | const LIST_TYPE_CODE: KTypeCode; 34 | unsafe fn join_to(item: Self::ListItem, k: *mut K) -> *mut K; 35 | } 36 | 37 | pub(crate) mod private { 38 | /// Dummy trait used to prevent the other traits from being implemented 39 | /// for types outside of this crate. 40 | pub trait Sealed {} 41 | } 42 | 43 | mod k_type_impls { 44 | use super::*; 45 | use crate::k_error::KError; 46 | use crate::kapi; 47 | use crate::symbol::Symbol; 48 | use crate::{any::Any, dictionary::Dictionary}; 49 | use crate::{date_time_types::*, table::Table}; 50 | 51 | #[cfg(feature = "uuid")] 52 | use uuid::Uuid; 53 | 54 | macro_rules! impl_k_value { 55 | ($type:ident, Code = $typecode: ident, Ctor = $ctor:ident, Accessor = $accessor:ident) => { 56 | impl_k_value!( 57 | $type, 58 | Code = $typecode, 59 | Ctor = $ctor, 60 | Accessor = $accessor, 61 | Joiner = ja 62 | ); 63 | }; 64 | ($type:ident, Code = $typecode: ident, Ctor = $ctor:ident, Accessor = $accessor: ident, Joiner = $joiner: ident) => { 65 | impl KValue for $type { 66 | const TYPE_CODE: TypeCode = TypeCode::$typecode; 67 | 68 | unsafe fn from_k(k: &K) -> Self { 69 | k.union.$accessor.into() 70 | } 71 | 72 | unsafe fn as_mutable(k: &mut K) -> &mut Self { 73 | (&mut k.union.$accessor).into() 74 | } 75 | 76 | fn into_k(self) -> *const K { 77 | unsafe { kapi::$ctor(self.into()) } 78 | } 79 | } 80 | 81 | impl KListable for $type { 82 | type ListItem = $type; 83 | const LIST_TYPE_CODE: KTypeCode = TypeCode::$typecode.as_list(); 84 | 85 | unsafe fn join_to(item: Self::ListItem, mut k: *mut K) -> *mut K { 86 | kapi::$joiner(&mut k, &item as *const _ as *const _) 87 | } 88 | } 89 | 90 | impl private::Sealed for $type {} 91 | }; 92 | } 93 | 94 | impl_k_value! {u8, Code = BYTE, Ctor = kg, Accessor = g } 95 | impl_k_value! {i8, Code = CHAR, Ctor = kc, Accessor = c } 96 | impl_k_value! {i16, Code = SHORT, Ctor = kh, Accessor = h } 97 | impl_k_value! {i32, Code = INT, Ctor = ki, Accessor = i } 98 | impl_k_value! {i64, Code = LONG, Ctor = kj, Accessor = j } 99 | 100 | impl_k_value! {f32, Code = REAL, Ctor = ke, Accessor = e } 101 | impl_k_value! {f64, Code = FLOAT, Ctor = kf, Accessor = f } 102 | impl_k_value! {bool, Code = BOOLEAN, Ctor = kb, Accessor = bl } 103 | 104 | impl_k_value! {Second, Code = SECOND, Ctor = ksec, Accessor = sec } 105 | impl_k_value! {Minute, Code = MINUTE, Ctor = kmin, Accessor = min } 106 | impl_k_value! {Month, Code = MONTH, Ctor = kmonth, Accessor = m } 107 | impl_k_value! {Time, Code = TIME, Ctor = kt, Accessor = t } 108 | impl_k_value! {Date, Code = DATE, Ctor = kd, Accessor = d } 109 | impl_k_value! {DateTime, Code = DATE_TIME, Ctor = kz, Accessor = dt } 110 | impl_k_value! {Symbol, Code = SYMBOL, Ctor = ks, Accessor = sym, Joiner = js } 111 | #[cfg(feature = "uuid")] 112 | impl_k_value! {Uuid, Code = GUID, Ctor = ku, Accessor = u } 113 | impl_k_value! {Timestamp, Code = TIMESTAMP, Ctor = tst, Accessor = tst } 114 | impl_k_value! {Timespan, Code = TIMESPAN, Ctor = tsp, Accessor = ts } 115 | 116 | impl private::Sealed for Any {} 117 | 118 | impl private::Sealed for Dictionary {} 119 | 120 | impl private::Sealed for Table {} 121 | 122 | impl private::Sealed for KError {} 123 | } 124 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! rust-kdb is an idiomatic Rust wrapper around the KDB+ C API. 2 | //! It supports manipulating K objects efficiently using zero cost 3 | //! abstractions, conversion to and from rust native types, 4 | //! and creation and editing of K lists/dictionaries using iterators. 5 | //! 6 | //! The design goals of the library: 7 | //! 1. Performance 8 | //! 2. Provide an idiomatic rust API 9 | //! 3. Safety 10 | //! 11 | //! # Why you might like it 12 | //! 13 | //! 1. It's really fast. The abstractions around the KDB types have zero overhead 14 | //! so while it feels like you are using rust types with all the trimmings, it is as fast as calling 15 | //! the raw C API directly. 16 | //! 2. It's safe! Using the C API is full of pitfalls that cause undefined behaviour. 17 | //! The abstractions in rust-kdb manage these for you, so that you don't need to worry about 18 | //! freeing your KDB objects or weird behaviour. 19 | //! 3. It's just like using other rust data types. We've worked hard to make Lists work like 20 | //! Rust vectors, and to add similar convenience functions to dictionaries. 21 | //! 22 | //! # Why you might not like it 23 | //! 24 | //! 1. Type conversions are common. It's not unusual to see rust-kdb code littered with `.try_into().unwrap()` 25 | //! Especially when working with mixed lists. This is a consequence of retrofitting the kdb type system into 26 | //! Rust. These type conversions are cheap (from/into are free, try_from/try_into are a single check of the KDB type code) 27 | //! But they do dirty the code somewhat. 28 | //! 2. It's incomplete. It currently lacks support for tables and not all functions have been included. The plan is to include these features 29 | //! in time. 30 | //! 31 | //! # Creating KDB types 32 | //! 33 | //! KDB deals with atoms and lists. 34 | //! rust-kdb maps this to the Atom And List generic types. 35 | //! We create a kdb type using a `KBox`. Just like a Rust `Box` wraps 36 | //! Rust heap managed pointers, KBox wraps KDB managed pointers. 37 | //! 38 | //! To create a new 32-bit integer atom: 39 | //! ``` 40 | //! use kdb::KBox; 41 | //! let a = KBox::new_atom(42); 42 | //! assert_eq!(a.value(), 42); 43 | //! ``` 44 | //! To create a list of bytes: 45 | //! ``` 46 | //! # use kdb::*; 47 | //! let mut l = KBox::>::new_list(); 48 | //! for i in 1..=10 { 49 | //! l.push(i); 50 | //! } 51 | //! assert_eq!(l.len(), 10); 52 | //! ``` 53 | //! 54 | //! # Working with unknown types 55 | //! 56 | //! KDB is dynamically typed, in that each type is an instance of a k object 57 | //! This is similar to variant types in C++. In rust-kdb, we manage this with 58 | //! the `Any` type. 59 | //! 60 | //! The `Any` type can be used in place of any valid KDB value (atom or list). 61 | //! You can't do much with it, except try to convert it to a different type, using 62 | //! the `cast!` and `try_cast!` macros. 63 | //! 64 | //! ``` 65 | //! use kdb::{cast, Any, Atom, KBox}; 66 | //! 67 | //! let a = KBox::new_atom(42); 68 | //! let b: KBox = a.into(); 69 | //! let c = cast!(b; Atom); 70 | //! ``` 71 | //! 72 | //! # Writing embedded KDB plug-ins 73 | //! 74 | //! Writing embedded plugins is straightforward using rust-kdb. 75 | //! Youll need to use the "embedded" feature for your plugin to use the correct 76 | //! library bindings. 77 | //! 78 | //! Below is an example of a simple KDB plugin 79 | //! ``` 80 | //! use kdb::*; 81 | //! use std::convert::TryFrom; 82 | //! use std::f64::consts::PI; 83 | //! 84 | //! /// Calculates the circumference of a circle. Returns a null value if the radius is not a real number. 85 | //! #[no_mangle] 86 | //! pub extern "C" fn circumference(radius: &Any) -> Option>> { 87 | //! if let Ok(r) = try_cast!(radius; Atom) { 88 | //! return Some(KBox::new_atom(r.value() * r.value() * PI)); 89 | //! } 90 | //! None 91 | //! } 92 | //! ``` 93 | //! 94 | //! Key points: 95 | //! 1. Note that KDB parameters in extern "C" functions are references to KDB types, rather than a KBox. In KDB, the caller owns the parameters. 96 | //! Using a KBox here will likely cause a segfault. 97 | //! 2. The return type is always either a KBox or Option>. This is equivalent to returning a K pointer. Always return an owned type. 98 | //! 3. You can use typed atoms for parameters, not just Any. Bear in mind that this is unsafe as it is possible for q code to call the function 99 | //! with a type other than that one. Any is always safest. 100 | 101 | #![warn(missing_docs)] // warn if there are missing docs 102 | 103 | mod any; 104 | mod atom; 105 | mod callbacks; 106 | mod connection; 107 | mod date_time_types; 108 | mod dictionary; 109 | mod error; 110 | mod k; 111 | mod k_error; 112 | mod k_type; 113 | pub mod kapi; 114 | mod kbox; 115 | mod list; 116 | mod serialization; 117 | mod symbol; 118 | mod table; 119 | mod type_traits; 120 | 121 | pub use any::{Any, KdbCast}; 122 | pub use array_iterator; 123 | pub use atom::Atom; 124 | pub use callbacks::*; 125 | pub use connection::Connection; 126 | pub use date_time_types::*; 127 | pub use dictionary::Dictionary; 128 | pub use error::{ConnectionError, ConversionError, Error}; 129 | pub use k_error::KError; 130 | pub use kbox::KBox; 131 | pub use list::List; 132 | pub use serialization::*; 133 | pub use symbol::{symbol, Symbol}; 134 | pub use table::Table; 135 | 136 | /// [not-embedded] Initialize the kdb memory subsystem. this is required when using generator functions 137 | /// in a standalone kdb application. This is equivalent to calling `khp("", -1)` in the C api. 138 | #[cfg(not(feature = "embedded"))] 139 | pub fn init() { 140 | let empty = std::ffi::CString::new("").unwrap(); 141 | unsafe { 142 | crate::kapi::khp(empty.as_ptr(), -1); 143 | } 144 | } 145 | 146 | #[cfg(feature = "uuid")] 147 | pub use uuid; 148 | -------------------------------------------------------------------------------- /src/k_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 3 | pub struct TypeCode(pub(crate) u8); 4 | 5 | impl TypeCode { 6 | pub const BOOLEAN: Self = TypeCode(1); 7 | pub const GUID: Self = TypeCode(2); 8 | pub const BYTE: Self = TypeCode(4); 9 | pub const SHORT: Self = TypeCode(5); 10 | pub const INT: Self = TypeCode(6); 11 | pub const LONG: Self = TypeCode(7); 12 | pub const REAL: Self = TypeCode(8); 13 | pub const FLOAT: Self = TypeCode(9); 14 | pub const CHAR: Self = TypeCode(10); 15 | pub const SYMBOL: Self = TypeCode(11); 16 | pub const TIMESTAMP: Self = TypeCode(12); 17 | pub const MONTH: Self = TypeCode(13); 18 | pub const DATE: Self = TypeCode(14); 19 | pub const DATE_TIME: Self = TypeCode(15); 20 | pub const TIMESPAN: Self = TypeCode(16); 21 | pub const MINUTE: Self = TypeCode(17); 22 | pub const SECOND: Self = TypeCode(18); 23 | pub const TIME: Self = TypeCode(19); 24 | 25 | pub const fn as_list(self) -> KTypeCode { 26 | KTypeCode(self.0 as i8) 27 | } 28 | pub const fn as_atom(self) -> KTypeCode { 29 | KTypeCode(-(self.0 as i8)) 30 | } 31 | } 32 | 33 | #[repr(transparent)] 34 | #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] 35 | pub struct KTypeCode(i8); 36 | 37 | impl From for i32 { 38 | fn from(kt: KTypeCode) -> i32 { 39 | kt.0 as i32 40 | } 41 | } 42 | 43 | impl fmt::Display for KTypeCode { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | match *self { 46 | MIXED_LIST => write!(f, "mixed list"), 47 | BOOLEAN_ATOM => write!(f, "boolean atom"), 48 | GUID_ATOM => write!(f, "guid atom"), 49 | BYTE_ATOM => write!(f, "byte atom"), 50 | SHORT_ATOM => write!(f, "short atom"), 51 | INT_ATOM => write!(f, "int atom"), 52 | LONG_ATOM => write!(f, "long atom"), 53 | REAL_ATOM => write!(f, "real atom"), 54 | FLOAT_ATOM => write!(f, "float atom"), 55 | CHAR_ATOM => write!(f, "char atom"), 56 | SYMBOL_ATOM => write!(f, "symbol atom"), 57 | TIMESTAMP_ATOM => write!(f, "timestamp atom"), 58 | MONTH_ATOM => write!(f, "month atom"), 59 | DATE_ATOM => write!(f, "date atom"), 60 | DATE_TIME_ATOM => write!(f, "dateTime atom"), 61 | TIMESPAN_ATOM => write!(f, "timespan atom"), 62 | MINUTE_ATOM => write!(f, "minute atom"), 63 | SECOND_ATOM => write!(f, "second atom"), 64 | TIME_ATOM => write!(f, "time atom"), 65 | BOOLEAN_LIST => write!(f, "boolean list"), 66 | GUID_LIST => write!(f, "guid list"), 67 | BYTE_LIST => write!(f, "byte list"), 68 | SHORT_LIST => write!(f, "short list"), 69 | INT_LIST => write!(f, "int list"), 70 | LONG_LIST => write!(f, "long list"), 71 | REAL_LIST => write!(f, "real list"), 72 | FLOAT_LIST => write!(f, "float list"), 73 | CHAR_LIST => write!(f, "char list"), 74 | SYMBOL_LIST => write!(f, "symbol list"), 75 | TIMESTAMP_LIST => write!(f, "timestamp list"), 76 | MONTH_LIST => write!(f, "month list"), 77 | DATE_LIST => write!(f, "date list"), 78 | DATE_TIME_LIST => write!(f, "datetime list"), 79 | TIMESPAN_LIST => write!(f, "timespan list"), 80 | MINUTE_LIST => write!(f, "minute list"), 81 | SECOND_LIST => write!(f, "second list"), 82 | TIME_LIST => write!(f, "time list"), 83 | TABLE => write!(f, "table"), 84 | DICT => write!(f, "dict"), 85 | ERROR => write!(f, "error"), 86 | _ => write!(f, "Unknown"), 87 | } 88 | } 89 | } 90 | 91 | impl KTypeCode { 92 | pub(crate) fn atom_size(self) -> usize { 93 | match KTypeCode(self.0.abs()) { 94 | BOOLEAN_LIST | BYTE_LIST | CHAR_LIST => 1, 95 | SHORT_LIST => 2, 96 | INT_LIST | REAL_LIST | DATE_LIST | MINUTE_LIST | SECOND_LIST | MONTH_LIST | TIME_LIST => 4, 97 | LONG_LIST | FLOAT_LIST | DATE_TIME_LIST | TIMESTAMP_LIST | TIMESPAN_LIST => 8, 98 | GUID_LIST => 24, // Guid has an 8 byte length as well as 16 bytes for the guid 99 | SYMBOL_LIST | MIXED_LIST | TABLE | DICT | ERROR => std::mem::size_of::<*const u8>(), 100 | _ => panic!("Unknown K type: {}", self.0), 101 | } 102 | } 103 | } 104 | 105 | impl fmt::Debug for KTypeCode { 106 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 107 | write!(f, "{}({})", self, self.0) 108 | } 109 | } 110 | 111 | pub const MIXED_LIST: KTypeCode = KTypeCode(0); 112 | pub const BOOLEAN_ATOM: KTypeCode = KTypeCode(-1); 113 | pub const GUID_ATOM: KTypeCode = KTypeCode(-2); 114 | pub const BYTE_ATOM: KTypeCode = KTypeCode(-4); 115 | pub const SHORT_ATOM: KTypeCode = KTypeCode(-5); 116 | pub const INT_ATOM: KTypeCode = KTypeCode(-6); 117 | pub const LONG_ATOM: KTypeCode = KTypeCode(-7); 118 | pub const REAL_ATOM: KTypeCode = KTypeCode(-8); 119 | pub const FLOAT_ATOM: KTypeCode = KTypeCode(-9); 120 | pub const CHAR_ATOM: KTypeCode = KTypeCode(-10); 121 | pub const SYMBOL_ATOM: KTypeCode = KTypeCode(-11); 122 | pub const TIMESTAMP_ATOM: KTypeCode = KTypeCode(-12); 123 | pub const MONTH_ATOM: KTypeCode = KTypeCode(-13); 124 | pub const DATE_ATOM: KTypeCode = KTypeCode(-14); 125 | pub const DATE_TIME_ATOM: KTypeCode = KTypeCode(-15); 126 | pub const TIMESPAN_ATOM: KTypeCode = KTypeCode(-16); 127 | pub const MINUTE_ATOM: KTypeCode = KTypeCode(-17); 128 | pub const SECOND_ATOM: KTypeCode = KTypeCode(-18); 129 | pub const TIME_ATOM: KTypeCode = KTypeCode(-19); 130 | pub const BOOLEAN_LIST: KTypeCode = KTypeCode(1); 131 | pub const GUID_LIST: KTypeCode = KTypeCode(2); 132 | pub const BYTE_LIST: KTypeCode = KTypeCode(4); 133 | pub const SHORT_LIST: KTypeCode = KTypeCode(5); 134 | pub const INT_LIST: KTypeCode = KTypeCode(6); 135 | pub const LONG_LIST: KTypeCode = KTypeCode(7); 136 | pub const REAL_LIST: KTypeCode = KTypeCode(8); 137 | pub const FLOAT_LIST: KTypeCode = KTypeCode(9); 138 | pub const CHAR_LIST: KTypeCode = KTypeCode(10); 139 | pub const SYMBOL_LIST: KTypeCode = KTypeCode(11); 140 | pub const TIMESTAMP_LIST: KTypeCode = KTypeCode(12); 141 | pub const MONTH_LIST: KTypeCode = KTypeCode(13); 142 | pub const DATE_LIST: KTypeCode = KTypeCode(14); 143 | pub const DATE_TIME_LIST: KTypeCode = KTypeCode(15); 144 | pub const TIMESPAN_LIST: KTypeCode = KTypeCode(16); 145 | pub const MINUTE_LIST: KTypeCode = KTypeCode(17); 146 | pub const SECOND_LIST: KTypeCode = KTypeCode(18); 147 | pub const TIME_LIST: KTypeCode = KTypeCode(19); 148 | pub const TABLE: KTypeCode = KTypeCode(98); 149 | pub const DICT: KTypeCode = KTypeCode(99); 150 | pub const ERROR: KTypeCode = KTypeCode(-128); 151 | -------------------------------------------------------------------------------- /src/any.rs: -------------------------------------------------------------------------------- 1 | use crate::atom::Atom; 2 | use crate::kapi; 3 | use crate::kbox::KBox; 4 | use crate::type_traits::*; 5 | use crate::{error::ConversionError, Dictionary}; 6 | use crate::{k::K, Table}; 7 | use crate::{k_type::KTypeCode, k_type::MIXED_LIST}; 8 | use crate::{list::List, KError}; 9 | use std::fmt; 10 | use std::mem; 11 | 12 | /// Any represents any KDB value regardless of type. 13 | /// Unlike atoms or lists you can't do anything with it, except for convert it into an atom or a list. 14 | /// It is ABI compatible with a K object, so it can be safely used as a parameter or return type for a function. 15 | /// See the chapter on embedded functions for more information. 16 | #[repr(transparent)] 17 | #[derive(PartialEq)] 18 | pub struct Any { 19 | k: K, 20 | } 21 | 22 | impl fmt::Debug for Any { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | write!(f, "Any(Type={})", self.k.t) 25 | } 26 | } 27 | 28 | impl AsRef for Atom { 29 | fn as_ref(&self) -> &Any { 30 | unsafe { &*(self as *const _ as *const _) } 31 | } 32 | } 33 | 34 | impl AsRef for List { 35 | fn as_ref(&self) -> &Any { 36 | unsafe { &*(self as *const _ as *const _) } 37 | } 38 | } 39 | 40 | impl AsRef for Dictionary { 41 | fn as_ref(&self) -> &Any { 42 | unsafe { &*(self as *const _ as *const _) } 43 | } 44 | } 45 | 46 | impl AsRef for Table { 47 | fn as_ref(&self) -> &Any { 48 | unsafe { &*(self as *const _ as *const _) } 49 | } 50 | } 51 | 52 | impl AsRef for KError { 53 | fn as_ref(&self) -> &Any { 54 | unsafe { &*(self as *const _ as *const _) } 55 | } 56 | } 57 | 58 | impl From> for KBox { 59 | fn from(value: KBox) -> Self { 60 | unsafe { mem::transmute(value) } 61 | } 62 | } 63 | 64 | impl From> for KBox { 65 | fn from(value: KBox) -> Self { 66 | unsafe { mem::transmute(value) } 67 | } 68 | } 69 | 70 | impl From> for KBox { 71 | fn from(value: KBox) -> Self { 72 | unsafe { mem::transmute(value) } 73 | } 74 | } 75 | 76 | impl From>> for KBox { 77 | fn from(value: KBox>) -> Self { 78 | unsafe { mem::transmute(value) } 79 | } 80 | } 81 | 82 | impl From>> for KBox { 83 | fn from(value: KBox>) -> Self { 84 | unsafe { mem::transmute(value) } 85 | } 86 | } 87 | 88 | impl From for KBox { 89 | fn from(value: T) -> Self { 90 | unsafe { mem::transmute(KBox::new_atom(value)) } 91 | } 92 | } 93 | 94 | impl KObject for Any { 95 | #[inline] 96 | fn k_ptr(&self) -> *const K { 97 | &self.k 98 | } 99 | 100 | #[inline] 101 | fn k_ptr_mut(&mut self) -> *mut K { 102 | &mut self.k 103 | } 104 | } 105 | 106 | impl AsRef for KBox> { 107 | fn as_ref(&self) -> &Any { 108 | unsafe { &*(self as *const _ as *const _) } 109 | } 110 | } 111 | 112 | impl AsRef for KBox> { 113 | fn as_ref(&self) -> &Any { 114 | unsafe { &*(self as *const _ as *const _) } 115 | } 116 | } 117 | 118 | impl AsRef for KBox { 119 | fn as_ref(&self) -> &Any { 120 | unsafe { &*(self as *const _ as *const _) } 121 | } 122 | } 123 | 124 | impl AsRef for KBox
{ 125 | fn as_ref(&self) -> &Any { 126 | unsafe { &*(self as *const _ as *const _) } 127 | } 128 | } 129 | 130 | impl KListable for Any { 131 | const LIST_TYPE_CODE: KTypeCode = MIXED_LIST; 132 | type ListItem = KBox; 133 | 134 | unsafe fn join_to(item: Self::ListItem, mut k: *mut K) -> *mut K { 135 | // don't r0 this - it's owned by the list now. 136 | kapi::jk(&mut k, mem::ManuallyDrop::new(item).k_ptr()) 137 | } 138 | } 139 | 140 | #[doc(hidden)] 141 | pub trait KdbCast { 142 | type Output; 143 | fn try_cast(self) -> Result; 144 | } 145 | 146 | impl KdbCast for KBox { 147 | type Output = KBox; 148 | fn try_cast(self) -> Result { 149 | unsafe { 150 | if (*self.k_ptr()).t != T::K_TYPE { 151 | Err(ConversionError::InvalidKCast { 152 | from: (*self.k_ptr()).t, 153 | to: T::K_TYPE, 154 | }) 155 | } else { 156 | #[allow(clippy::clippy::transmute_ptr_to_ptr)] 157 | Ok(mem::transmute(self)) 158 | } 159 | } 160 | } 161 | } 162 | 163 | impl<'a, T: 'a + KObject + KTyped> KdbCast for &'a KBox { 164 | type Output = &'a KBox; 165 | fn try_cast(self) -> Result { 166 | unsafe { 167 | if (*self.k_ptr()).t != T::K_TYPE { 168 | Err(ConversionError::InvalidKCast { 169 | from: (*self.k_ptr()).t, 170 | to: T::K_TYPE, 171 | }) 172 | } else { 173 | #[allow(clippy::clippy::transmute_ptr_to_ptr)] 174 | Ok(mem::transmute(self)) 175 | } 176 | } 177 | } 178 | } 179 | 180 | impl<'a, T: 'a + KObject + KTyped> KdbCast for &'a Any { 181 | type Output = &'a T; 182 | fn try_cast(self) -> Result { 183 | unsafe { 184 | if (*self.k_ptr()).t != T::K_TYPE { 185 | Err(ConversionError::InvalidKCast { 186 | from: (*self.k_ptr()).t, 187 | to: T::K_TYPE, 188 | }) 189 | } else { 190 | #[allow(clippy::clippy::transmute_ptr_to_ptr)] 191 | Ok(mem::transmute(self)) 192 | } 193 | } 194 | } 195 | } 196 | 197 | /// Tries to cast a KBox, &KBox or &Any to another KDB type. 198 | /// 199 | /// returns a result containing the KDB type, or a `ConversionError`. 200 | /// 201 | /// # Example 202 | /// 203 | /// ``` 204 | /// # use kdb::{try_cast, symbol, Any, Atom, KBox, Symbol}; 205 | /// let a: KBox = symbol("Hello").into(); 206 | /// assert_eq!(symbol("Hello"), try_cast!(a; Atom).unwrap().value()); 207 | /// ``` 208 | #[macro_export] 209 | macro_rules! try_cast { 210 | ($ex:expr; $t:ty) => { 211 | $crate::KdbCast::<$t>::try_cast($ex) 212 | }; 213 | } 214 | 215 | /// Cast a KBox, &KBox or &Any to another KDB type. 216 | /// 217 | /// If the KDB type is anything other than the expected one, the cast will panic. 218 | /// 219 | /// # Example 220 | /// 221 | /// ``` 222 | /// # use kdb::{cast, symbol, Any, Atom, KBox, Symbol}; 223 | /// let a: KBox = symbol("Hello").into(); 224 | /// assert_eq!(symbol("Hello"), cast!(a; Atom).value()); 225 | /// ``` 226 | #[macro_export] 227 | macro_rules! cast { 228 | ($ex:expr; $t:ty) => { 229 | $crate::KdbCast::<$t>::try_cast($ex).unwrap() 230 | }; 231 | } 232 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | //! Connecting to KDB is pretty straightforward. 2 | //! If you are using it embedded, then you can call new() with no parameters to connect to the 3 | //! unerlying instance of KDB. 4 | //! If you are using it externally, the call connect, which takes a host, port, credentials and an optional timeout. 5 | 6 | use crate::any::Any; 7 | use crate::error::*; 8 | use crate::k::K; 9 | use crate::k_error::KError; 10 | use crate::k_type::ERROR; 11 | use crate::kapi; 12 | use crate::kbox::KBox; 13 | 14 | use std::ffi::CString; 15 | use std::ptr; 16 | 17 | /// null pointer with type inferred as *const K. 18 | fn null() -> *const K { 19 | ptr::null() 20 | } 21 | 22 | macro_rules! evaluate { 23 | ($conn: expr, $func: expr $(, $param:expr)*,) => { 24 | evaluate!($conn, $func $(, $expr)*); 25 | }; 26 | ($conn: expr, $func: expr $(, $param:expr)*) => { 27 | { 28 | let result = unsafe { kapi::k($conn, CString::new($func).unwrap().as_ptr() $(, $param.into_raw() as *const K)*, null()) }; 29 | if result.is_null() { 30 | Err(Error::NetworkError) 31 | } else if $conn > 0 && unsafe { (*result).t == ERROR } { 32 | let err = unsafe{ KBox::::from_raw(result) }.into(); 33 | Err(err) 34 | } else { 35 | Ok(result) 36 | } 37 | } 38 | }; 39 | } 40 | 41 | fn from_raw(k: *mut K) -> KBox { 42 | unsafe { KBox::from_raw(k) } 43 | } 44 | 45 | /// Represents a connection to a remote or embedded KDB instance, 46 | /// which can be used to send and query data on that instance. 47 | pub struct Connection(i32); 48 | 49 | impl Connection { 50 | /// [non-embedded only] Connect to a remote instance of KDB. 51 | #[cfg(not(feature = "embedded"))] 52 | pub fn connect( 53 | hostname: &str, 54 | port: u16, 55 | credentials: &str, 56 | timeout: Option, 57 | ) -> Result { 58 | let c_hostname = CString::new(hostname).unwrap(); 59 | let c_credentials = CString::new(credentials).unwrap(); 60 | 61 | let result = if let Some(duration) = timeout { 62 | let secs = duration.as_secs() as i32; 63 | unsafe { kapi::khpun(c_hostname.as_ptr(), port as i32, c_credentials.as_ptr(), secs) } 64 | } else { 65 | unsafe { kapi::khpu(c_hostname.as_ptr(), port as i32, c_credentials.as_ptr()) } 66 | }; 67 | match result { 68 | 0 => Err(ConnectionError::BadCredentials), 69 | -1 => Err(ConnectionError::CouldNotConnect), 70 | -2 => Err(ConnectionError::Timeout), 71 | x => Ok(Self(x)), 72 | } 73 | } 74 | 75 | /// [embedded only] Connect to an embedded KDB instance. 76 | #[cfg(any(feature = "embedded", doc))] 77 | pub fn new() -> Self { 78 | Connection(0) 79 | } 80 | 81 | /// [non-embedded only] Publish a value asynchronously to KDB. 82 | #[cfg(any(not(feature = "embedded"), doc))] 83 | pub fn publish( 84 | &self, 85 | callback: &str, 86 | topic: impl Into>, 87 | object: impl Into>, 88 | ) -> Result<(), Error> { 89 | // Note that when sending asynchronously, we shouldn't call r0 on the return value - it's 90 | // not an owned K type. 91 | evaluate!(-self.0, callback, topic.into(), object.into()).map(|_| ()) 92 | } 93 | 94 | /// Evaluate a q expression with no parameters and return a result. 95 | pub fn eval(&self, query: &str) -> Result, Error> { 96 | evaluate!(self.0, query).map(from_raw) 97 | } 98 | 99 | /// Evaluate a q function with a single parameter and return the result. 100 | pub fn eval_1(&self, function: &str, param: impl Into>) -> Result, Error> { 101 | evaluate!(self.0, function, param.into()).map(from_raw) 102 | } 103 | 104 | /// Evaluate a q function with two parameters and return the result. 105 | pub fn eval_2( 106 | &self, 107 | function: &str, 108 | param: impl Into>, 109 | param_2: impl Into>, 110 | ) -> Result, Error> { 111 | evaluate!(self.0, function, param.into(), param_2.into()).map(from_raw) 112 | } 113 | 114 | /// Evaluate a q function with three parameters and return the result. 115 | pub fn eval_3( 116 | &self, 117 | function: &str, 118 | param: impl Into>, 119 | param_2: impl Into>, 120 | param_3: impl Into>, 121 | ) -> Result, Error> { 122 | evaluate!(self.0, function, param.into(), param_2.into(), param_3.into()).map(from_raw) 123 | } 124 | 125 | /// Evaluate a q function with four parameters and return the result. 126 | pub fn eval_4( 127 | &self, 128 | function: &str, 129 | param: impl Into>, 130 | param_2: impl Into>, 131 | param_3: impl Into>, 132 | param_4: impl Into>, 133 | ) -> Result, Error> { 134 | evaluate!( 135 | self.0, 136 | function, 137 | param.into(), 138 | param_2.into(), 139 | param_3.into(), 140 | param_4.into() 141 | ) 142 | .map(from_raw) 143 | } 144 | 145 | /// Evaluate a q function with five parameters and return the result. 146 | pub fn eval_5( 147 | &self, 148 | function: &str, 149 | param: impl Into>, 150 | param_2: impl Into>, 151 | param_3: impl Into>, 152 | param_4: impl Into>, 153 | param_5: impl Into>, 154 | ) -> Result, Error> { 155 | evaluate!( 156 | self.0, 157 | function, 158 | param.into(), 159 | param_2.into(), 160 | param_3.into(), 161 | param_4.into(), 162 | param_5.into() 163 | ) 164 | .map(from_raw) 165 | } 166 | 167 | /// Evaluate a q function with six parameters and return the result. 168 | #[allow(clippy::clippy::too_many_arguments)] 169 | pub fn eval_6( 170 | &self, 171 | function: &str, 172 | param: impl Into>, 173 | param_2: impl Into>, 174 | param_3: impl Into>, 175 | param_4: impl Into>, 176 | param_5: impl Into>, 177 | param_6: impl Into>, 178 | ) -> Result, Error> { 179 | evaluate!( 180 | self.0, 181 | function, 182 | param.into(), 183 | param_2.into(), 184 | param_3.into(), 185 | param_4.into(), 186 | param_5.into(), 187 | param_6.into() 188 | ) 189 | .map(from_raw) 190 | } 191 | 192 | /// Evaluate a q function with seven parameters and return the result. 193 | #[allow(clippy::clippy::too_many_arguments)] 194 | pub fn eval_7( 195 | &self, 196 | function: &str, 197 | param: impl Into>, 198 | param_2: impl Into>, 199 | param_3: impl Into>, 200 | param_4: impl Into>, 201 | param_5: impl Into>, 202 | param_6: impl Into>, 203 | param_7: impl Into>, 204 | ) -> Result, Error> { 205 | evaluate!( 206 | self.0, 207 | function, 208 | param.into(), 209 | param_2.into(), 210 | param_3.into(), 211 | param_4.into(), 212 | param_5.into(), 213 | param_6.into(), 214 | param_7.into() 215 | ) 216 | .map(from_raw) 217 | } 218 | 219 | /// See above and add one parameter. 220 | #[allow(clippy::clippy::too_many_arguments)] 221 | pub fn eval_8( 222 | &self, 223 | function: &str, 224 | param: impl Into>, 225 | param_2: impl Into>, 226 | param_3: impl Into>, 227 | param_4: impl Into>, 228 | param_5: impl Into>, 229 | param_6: impl Into>, 230 | param_7: impl Into>, 231 | param_8: impl Into>, 232 | ) -> Result, Error> { 233 | evaluate!( 234 | self.0, 235 | function, 236 | param.into(), 237 | param_2.into(), 238 | param_3.into(), 239 | param_4.into(), 240 | param_5.into(), 241 | param_6.into(), 242 | param_7.into(), 243 | param_8.into() 244 | ) 245 | .map(from_raw) 246 | } 247 | } 248 | 249 | impl Drop for Connection { 250 | #[cfg(not(feature = "embedded"))] 251 | fn drop(&mut self) { 252 | unsafe { 253 | kapi::kclose(self.0); 254 | } 255 | } 256 | #[cfg(feature = "embedded")] 257 | fn drop(&mut self) {} 258 | } 259 | 260 | #[cfg(feature = "embedded")] 261 | impl Default for Connection { 262 | fn default() -> Self { 263 | Connection(0) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/date_time_types.rs: -------------------------------------------------------------------------------- 1 | use crate::kapi; 2 | use std::convert::TryFrom; 3 | use std::fmt; 4 | use std::time::{Duration, SystemTime}; 5 | 6 | pub(crate) const K_NANO_OFFSET: i64 = 946_684_800_000_000_000; 7 | pub(crate) const K_SEC_OFFSET: i64 = K_NANO_OFFSET / 1_000_000_000; 8 | pub(crate) const K_DAY_OFFSET: i32 = (K_SEC_OFFSET / 86_400) as i32; 9 | 10 | /// Represents the number of seconds since midnight (00:00) 11 | #[repr(transparent)] 12 | #[derive(Clone, Copy, PartialEq, Eq)] 13 | pub struct Second(i32); 14 | 15 | impl Second { 16 | /// Creates a new Second from the specified number. 17 | pub fn new(seconds_since_midnight: i32) -> Self { 18 | Second(seconds_since_midnight) 19 | } 20 | } 21 | 22 | impl From for Second { 23 | fn from(val: i32) -> Second { 24 | Second(val) 25 | } 26 | } 27 | 28 | impl From for i32 { 29 | fn from(val: Second) -> i32 { 30 | val.0 31 | } 32 | } 33 | 34 | impl fmt::Display for Second { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | write!(f, "{} seconds", self.0) 37 | } 38 | } 39 | 40 | impl fmt::Debug for Second { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | fmt::Display::fmt(self, f) 43 | } 44 | } 45 | 46 | /// Represents the number of minutes since midnight (00:00). 47 | #[repr(transparent)] 48 | #[derive(Clone, Copy, PartialEq, Eq)] 49 | pub struct Minute(i32); 50 | 51 | impl Minute { 52 | /// Creates a new Minute from the specified number. 53 | pub fn new(minutes_since_midnight: i32) -> Self { 54 | Minute(minutes_since_midnight) 55 | } 56 | } 57 | 58 | impl From for Minute { 59 | fn from(val: i32) -> Minute { 60 | Minute(val) 61 | } 62 | } 63 | 64 | impl From for i32 { 65 | fn from(val: Minute) -> i32 { 66 | val.0 67 | } 68 | } 69 | 70 | impl fmt::Display for Minute { 71 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 72 | write!(f, "{} minutes", self.0) 73 | } 74 | } 75 | 76 | impl fmt::Debug for Minute { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 | fmt::Display::fmt(self, f) 79 | } 80 | } 81 | 82 | /// Represents the number of days since 1 Jan 2000. 83 | #[repr(transparent)] 84 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 85 | pub struct Date(i32); 86 | 87 | impl Date { 88 | /// Creates a new date. 89 | pub fn new(year: i32, month: i32, day: i32) -> Self { 90 | Date(unsafe { kapi::ymd(year, month, day) }) 91 | } 92 | 93 | /// Returns the date as the number of days since 1 Jan 2000. 94 | pub fn as_raw(&self) -> i32 { 95 | self.0 96 | } 97 | } 98 | 99 | impl From for Date { 100 | fn from(val: i32) -> Date { 101 | Date(val) 102 | } 103 | } 104 | 105 | impl From for i32 { 106 | fn from(val: Date) -> i32 { 107 | val.0 108 | } 109 | } 110 | 111 | impl From for Date { 112 | fn from(st: SystemTime) -> Date { 113 | let dur = st.duration_since(SystemTime::UNIX_EPOCH).unwrap(); 114 | let dur_secs = (dur.as_secs() / 86400) as i64; 115 | Date(dur_secs as i32 - K_DAY_OFFSET) 116 | } 117 | } 118 | 119 | impl From for SystemTime { 120 | fn from(date: Date) -> SystemTime { 121 | let secs = date.0 as i64 * 86400 + K_SEC_OFFSET; 122 | SystemTime::UNIX_EPOCH + Duration::from_secs(secs as u64) 123 | } 124 | } 125 | 126 | /// The number of months since January 2000. 127 | #[repr(transparent)] 128 | #[derive(Clone, Copy, PartialEq, Eq)] 129 | pub struct Month(i32); 130 | 131 | impl Month { 132 | /// Creates a new month from the specified number of months. 133 | pub fn new(months_since_millenium: i32) -> Self { 134 | Month(months_since_millenium) 135 | } 136 | } 137 | 138 | impl From for Month { 139 | fn from(val: i32) -> Month { 140 | Month(val) 141 | } 142 | } 143 | 144 | impl From for i32 { 145 | fn from(val: Month) -> i32 { 146 | val.0 147 | } 148 | } 149 | 150 | impl fmt::Display for Month { 151 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 152 | write!(f, "{} months", self.0) 153 | } 154 | } 155 | 156 | impl fmt::Debug for Month { 157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 158 | fmt::Display::fmt(self, f) 159 | } 160 | } 161 | 162 | /// The number of milliseconds since midnight. 163 | #[repr(transparent)] 164 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 165 | pub struct Time(i32); 166 | 167 | impl Time { 168 | /// Creates a Time from the specified number of milliseconds. 169 | pub fn new(millis_since_midnight: i32) -> Self { 170 | Time(millis_since_midnight) 171 | } 172 | } 173 | 174 | impl From for Time { 175 | fn from(val: i32) -> Time { 176 | Time(val) 177 | } 178 | } 179 | 180 | impl From