├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── src ├── arg.rs ├── bindings.rs ├── error.rs ├── format.rs ├── lib.rs ├── log_level.rs ├── query_result.rs └── session.rs ├── tests ├── examples.rs └── logs.csv ├── update_libchdb.sh └── wrapper.h /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Fetch library 18 | run: | 19 | #wget https://github.com/chdb-io/chdb/releases/latest/download/linux-x86_64-libchdb.tar.gz 20 | #Fix libchdb version to v2.1.1 until libchdb v3 based chdb-rust refactor 21 | wget https://github.com/chdb-io/chdb/releases/download/v2.1.1/linux-x86_64-libchdb.tar.gz 22 | tar -xzf linux-x86_64-libchdb.tar.gz 23 | sudo mv libchdb.so /usr/lib/libchdb.so 24 | sudo ldconfig 25 | - name: Build 26 | run: cargo build --verbose 27 | env: 28 | RUST_BACKTRACE: full 29 | - name: Run tests 30 | run: cargo test 31 | env: 32 | RUST_BACKTRACE: full 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.tar.gz 4 | *.so 5 | chdb.h 6 | var 7 | udf 8 | *.parquet 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chdb-rust" 3 | version = "1.0.0" 4 | edition = "2021" 5 | keywords = ["clickhouse", "chdb", "database", "embedded", "analytics"] 6 | 7 | [dependencies] 8 | thiserror = "1" 9 | 10 | [build-dependencies] 11 | bindgen = "0.70.1" 12 | 13 | [dev-dependencies] 14 | tempdir = "0.3.7" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Rust](https://github.com/chdb-io/chdb-rust/actions/workflows/rust.yml/badge.svg)](https://github.com/chdb-io/chdb-rust/actions/workflows/rust.yml) 4 | 5 | # chdb-rust 6 | 7 | Experimental [chDB](https://github.com/chdb-io/chdb) FFI bindings for Rust 8 | 9 | ## Status 10 | 11 | - Experimental, unstable, subject to changes 12 | - Requires [`libchdb`](https://github.com/chdb-io/chdb) on the system. You can install the compatible version from 13 | `install_libchdb.sh` 14 | 15 | ## Usage 16 | 17 | ### Install libchdb 18 | 19 | You can install it system-wide 20 | 21 | ```bash 22 | ./update_libchdb.sh --global 23 | ``` 24 | 25 | or use it in a local directory 26 | 27 | ```bash 28 | ./update_libchdb.sh --local 29 | ``` 30 | 31 | ### Build 32 | 33 | ```bash 34 | RUST_BACKTRACE=full cargo build --verbose 35 | 36 | ``` 37 | 38 | ### Run tests 39 | 40 | `cargo test` 41 | 42 | ### Examples 43 | 44 | See `tests` directory. 45 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | // Tell cargo to look for shared libraries in the specified directory 5 | println!("cargo:rustc-link-search=./"); 6 | 7 | // Tell cargo to tell rustc to link the system chdb library. 8 | println!("cargo:rustc-link-lib=chdb"); 9 | 10 | // Tell cargo to invalidate the built crate whenever the wrapper changes. 11 | println!("cargo:rerun-if-changed=chdb.h"); 12 | 13 | // The bindgen::Builder is the main entry point 14 | // to bindgen, and lets you build up options for 15 | // the resulting bindings. 16 | let bindings = bindgen::Builder::default() 17 | // The input header we would like to generate 18 | // bindings for. 19 | .header("wrapper.h") 20 | // Tell cargo to invalidate the built crate whenever any of the 21 | // included header files changed. 22 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 23 | // Finish the builder and generate the bindings. 24 | .generate() 25 | // Unwrap the Result and panic on failure. 26 | .expect("Unable to generate bindings"); 27 | 28 | // Write the bindings to the $OUT_DIR/bindings.rs file. 29 | let out_path = PathBuf::from("./src/"); 30 | bindings 31 | .write_to_file(out_path.join("bindings.rs")) 32 | .expect("Couldn't write bindings!"); 33 | } 34 | -------------------------------------------------------------------------------- /src/arg.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::ffi::CString; 3 | 4 | use crate::error::Error; 5 | use crate::format::OutputFormat; 6 | use crate::log_level::LogLevel; 7 | 8 | #[derive(Debug)] 9 | pub enum Arg<'a> { 10 | /// --config-file= 11 | ConfigFilePath(Cow<'a, str>), 12 | /// --log-level= 13 | LogLevel(LogLevel), 14 | /// --output-format= 15 | OutputFormat(OutputFormat), 16 | /// --multiquery 17 | MultiQuery, 18 | /// Custom argument. 19 | /// 20 | /// "--path=/tmp/chdb" translates into one of the following: 21 | /// 1. Arg::Custom("path".to_string().into(), Some("/tmp/chdb".to_string().into())). 22 | /// 2. Arg::Custom("path".into(), Some("/tmp/chdb".into())). 23 | /// 24 | /// "--multiline" translates into one of the following: 25 | /// 1. Arg::Custom("multiline".to_string().into(), None). 26 | /// 2. Arg::Custom("multiline".into(), None). 27 | /// 28 | /// We should tell user where to look for officially supported arguments. 29 | /// Here is some hint for now: https://github.com/fixcik/chdb-rs/blob/master/OPTIONS.md . 30 | Custom(Cow<'a, str>, Option>), 31 | } 32 | 33 | impl<'a> Arg<'a> { 34 | pub(crate) fn to_cstring(&self) -> Result { 35 | Ok(match self { 36 | Self::ConfigFilePath(v) => CString::new(format!("--config-file={}", v)), 37 | Self::LogLevel(v) => CString::new(format!("--log-level={}", v.as_str())), 38 | Self::OutputFormat(v) => CString::new(format!("--output-format={}", v.as_str())), 39 | Self::MultiQuery => CString::new("-n"), 40 | Self::Custom(k, v) => match v { 41 | None => CString::new(k.as_ref()), 42 | Some(v) => CString::new(format!("--{}={}", k, v)), 43 | }, 44 | }?) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.70.1 */ 2 | 3 | pub const _STDINT_H: u32 = 1; 4 | pub const _FEATURES_H: u32 = 1; 5 | pub const _DEFAULT_SOURCE: u32 = 1; 6 | pub const __GLIBC_USE_ISOC2X: u32 = 0; 7 | pub const __USE_ISOC11: u32 = 1; 8 | pub const __USE_ISOC99: u32 = 1; 9 | pub const __USE_ISOC95: u32 = 1; 10 | pub const __USE_POSIX_IMPLICITLY: u32 = 1; 11 | pub const _POSIX_SOURCE: u32 = 1; 12 | pub const _POSIX_C_SOURCE: u32 = 200809; 13 | pub const __USE_POSIX: u32 = 1; 14 | pub const __USE_POSIX2: u32 = 1; 15 | pub const __USE_POSIX199309: u32 = 1; 16 | pub const __USE_POSIX199506: u32 = 1; 17 | pub const __USE_XOPEN2K: u32 = 1; 18 | pub const __USE_XOPEN2K8: u32 = 1; 19 | pub const _ATFILE_SOURCE: u32 = 1; 20 | pub const __WORDSIZE: u32 = 64; 21 | pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; 22 | pub const __SYSCALL_WORDSIZE: u32 = 64; 23 | pub const __TIMESIZE: u32 = 64; 24 | pub const __USE_MISC: u32 = 1; 25 | pub const __USE_ATFILE: u32 = 1; 26 | pub const __USE_FORTIFY_LEVEL: u32 = 0; 27 | pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; 28 | pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; 29 | pub const __GLIBC_USE_C2X_STRTOL: u32 = 0; 30 | pub const _STDC_PREDEF_H: u32 = 1; 31 | pub const __STDC_IEC_559__: u32 = 1; 32 | pub const __STDC_IEC_60559_BFP__: u32 = 201404; 33 | pub const __STDC_IEC_559_COMPLEX__: u32 = 1; 34 | pub const __STDC_IEC_60559_COMPLEX__: u32 = 201404; 35 | pub const __STDC_ISO_10646__: u32 = 201706; 36 | pub const __GNU_LIBRARY__: u32 = 6; 37 | pub const __GLIBC__: u32 = 2; 38 | pub const __GLIBC_MINOR__: u32 = 39; 39 | pub const _SYS_CDEFS_H: u32 = 1; 40 | pub const __glibc_c99_flexarr_available: u32 = 1; 41 | pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0; 42 | pub const __HAVE_GENERIC_SELECTION: u32 = 1; 43 | pub const __GLIBC_USE_LIB_EXT2: u32 = 0; 44 | pub const __GLIBC_USE_IEC_60559_BFP_EXT: u32 = 0; 45 | pub const __GLIBC_USE_IEC_60559_BFP_EXT_C2X: u32 = 0; 46 | pub const __GLIBC_USE_IEC_60559_EXT: u32 = 0; 47 | pub const __GLIBC_USE_IEC_60559_FUNCS_EXT: u32 = 0; 48 | pub const __GLIBC_USE_IEC_60559_FUNCS_EXT_C2X: u32 = 0; 49 | pub const __GLIBC_USE_IEC_60559_TYPES_EXT: u32 = 0; 50 | pub const _BITS_TYPES_H: u32 = 1; 51 | pub const _BITS_TYPESIZES_H: u32 = 1; 52 | pub const __OFF_T_MATCHES_OFF64_T: u32 = 1; 53 | pub const __INO_T_MATCHES_INO64_T: u32 = 1; 54 | pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1; 55 | pub const __STATFS_MATCHES_STATFS64: u32 = 1; 56 | pub const __KERNEL_OLD_TIMEVAL_MATCHES_TIMEVAL64: u32 = 1; 57 | pub const __FD_SETSIZE: u32 = 1024; 58 | pub const _BITS_TIME64_H: u32 = 1; 59 | pub const _BITS_WCHAR_H: u32 = 1; 60 | pub const _BITS_STDINT_INTN_H: u32 = 1; 61 | pub const _BITS_STDINT_UINTN_H: u32 = 1; 62 | pub const _BITS_STDINT_LEAST_H: u32 = 1; 63 | pub const INT8_MIN: i32 = -128; 64 | pub const INT16_MIN: i32 = -32768; 65 | pub const INT32_MIN: i32 = -2147483648; 66 | pub const INT8_MAX: u32 = 127; 67 | pub const INT16_MAX: u32 = 32767; 68 | pub const INT32_MAX: u32 = 2147483647; 69 | pub const UINT8_MAX: u32 = 255; 70 | pub const UINT16_MAX: u32 = 65535; 71 | pub const UINT32_MAX: u32 = 4294967295; 72 | pub const INT_LEAST8_MIN: i32 = -128; 73 | pub const INT_LEAST16_MIN: i32 = -32768; 74 | pub const INT_LEAST32_MIN: i32 = -2147483648; 75 | pub const INT_LEAST8_MAX: u32 = 127; 76 | pub const INT_LEAST16_MAX: u32 = 32767; 77 | pub const INT_LEAST32_MAX: u32 = 2147483647; 78 | pub const UINT_LEAST8_MAX: u32 = 255; 79 | pub const UINT_LEAST16_MAX: u32 = 65535; 80 | pub const UINT_LEAST32_MAX: u32 = 4294967295; 81 | pub const INT_FAST8_MIN: i32 = -128; 82 | pub const INT_FAST16_MIN: i64 = -9223372036854775808; 83 | pub const INT_FAST32_MIN: i64 = -9223372036854775808; 84 | pub const INT_FAST8_MAX: u32 = 127; 85 | pub const INT_FAST16_MAX: u64 = 9223372036854775807; 86 | pub const INT_FAST32_MAX: u64 = 9223372036854775807; 87 | pub const UINT_FAST8_MAX: u32 = 255; 88 | pub const UINT_FAST16_MAX: i32 = -1; 89 | pub const UINT_FAST32_MAX: i32 = -1; 90 | pub const INTPTR_MIN: i64 = -9223372036854775808; 91 | pub const INTPTR_MAX: u64 = 9223372036854775807; 92 | pub const UINTPTR_MAX: i32 = -1; 93 | pub const PTRDIFF_MIN: i64 = -9223372036854775808; 94 | pub const PTRDIFF_MAX: u64 = 9223372036854775807; 95 | pub const SIG_ATOMIC_MIN: i32 = -2147483648; 96 | pub const SIG_ATOMIC_MAX: u32 = 2147483647; 97 | pub const SIZE_MAX: i32 = -1; 98 | pub const WINT_MIN: u32 = 0; 99 | pub const WINT_MAX: u32 = 4294967295; 100 | pub type wchar_t = ::std::os::raw::c_int; 101 | #[repr(C)] 102 | #[repr(align(16))] 103 | #[derive(Debug, Copy, Clone)] 104 | pub struct max_align_t { 105 | pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, 106 | pub __bindgen_padding_0: u64, 107 | pub __clang_max_align_nonce2: u128, 108 | } 109 | #[allow(clippy::unnecessary_operation, clippy::identity_op)] 110 | const _: () = { 111 | ["Size of max_align_t"][::std::mem::size_of::() - 32usize]; 112 | ["Alignment of max_align_t"][::std::mem::align_of::() - 16usize]; 113 | ["Offset of field: max_align_t::__clang_max_align_nonce1"] 114 | [::std::mem::offset_of!(max_align_t, __clang_max_align_nonce1) - 0usize]; 115 | ["Offset of field: max_align_t::__clang_max_align_nonce2"] 116 | [::std::mem::offset_of!(max_align_t, __clang_max_align_nonce2) - 16usize]; 117 | }; 118 | pub type __u_char = ::std::os::raw::c_uchar; 119 | pub type __u_short = ::std::os::raw::c_ushort; 120 | pub type __u_int = ::std::os::raw::c_uint; 121 | pub type __u_long = ::std::os::raw::c_ulong; 122 | pub type __int8_t = ::std::os::raw::c_schar; 123 | pub type __uint8_t = ::std::os::raw::c_uchar; 124 | pub type __int16_t = ::std::os::raw::c_short; 125 | pub type __uint16_t = ::std::os::raw::c_ushort; 126 | pub type __int32_t = ::std::os::raw::c_int; 127 | pub type __uint32_t = ::std::os::raw::c_uint; 128 | pub type __int64_t = ::std::os::raw::c_long; 129 | pub type __uint64_t = ::std::os::raw::c_ulong; 130 | pub type __int_least8_t = __int8_t; 131 | pub type __uint_least8_t = __uint8_t; 132 | pub type __int_least16_t = __int16_t; 133 | pub type __uint_least16_t = __uint16_t; 134 | pub type __int_least32_t = __int32_t; 135 | pub type __uint_least32_t = __uint32_t; 136 | pub type __int_least64_t = __int64_t; 137 | pub type __uint_least64_t = __uint64_t; 138 | pub type __quad_t = ::std::os::raw::c_long; 139 | pub type __u_quad_t = ::std::os::raw::c_ulong; 140 | pub type __intmax_t = ::std::os::raw::c_long; 141 | pub type __uintmax_t = ::std::os::raw::c_ulong; 142 | pub type __dev_t = ::std::os::raw::c_ulong; 143 | pub type __uid_t = ::std::os::raw::c_uint; 144 | pub type __gid_t = ::std::os::raw::c_uint; 145 | pub type __ino_t = ::std::os::raw::c_ulong; 146 | pub type __ino64_t = ::std::os::raw::c_ulong; 147 | pub type __mode_t = ::std::os::raw::c_uint; 148 | pub type __nlink_t = ::std::os::raw::c_ulong; 149 | pub type __off_t = ::std::os::raw::c_long; 150 | pub type __off64_t = ::std::os::raw::c_long; 151 | pub type __pid_t = ::std::os::raw::c_int; 152 | #[repr(C)] 153 | #[derive(Debug, Copy, Clone)] 154 | pub struct __fsid_t { 155 | pub __val: [::std::os::raw::c_int; 2usize], 156 | } 157 | #[allow(clippy::unnecessary_operation, clippy::identity_op)] 158 | const _: () = { 159 | ["Size of __fsid_t"][::std::mem::size_of::<__fsid_t>() - 8usize]; 160 | ["Alignment of __fsid_t"][::std::mem::align_of::<__fsid_t>() - 4usize]; 161 | ["Offset of field: __fsid_t::__val"][::std::mem::offset_of!(__fsid_t, __val) - 0usize]; 162 | }; 163 | pub type __clock_t = ::std::os::raw::c_long; 164 | pub type __rlim_t = ::std::os::raw::c_ulong; 165 | pub type __rlim64_t = ::std::os::raw::c_ulong; 166 | pub type __id_t = ::std::os::raw::c_uint; 167 | pub type __time_t = ::std::os::raw::c_long; 168 | pub type __useconds_t = ::std::os::raw::c_uint; 169 | pub type __suseconds_t = ::std::os::raw::c_long; 170 | pub type __suseconds64_t = ::std::os::raw::c_long; 171 | pub type __daddr_t = ::std::os::raw::c_int; 172 | pub type __key_t = ::std::os::raw::c_int; 173 | pub type __clockid_t = ::std::os::raw::c_int; 174 | pub type __timer_t = *mut ::std::os::raw::c_void; 175 | pub type __blksize_t = ::std::os::raw::c_long; 176 | pub type __blkcnt_t = ::std::os::raw::c_long; 177 | pub type __blkcnt64_t = ::std::os::raw::c_long; 178 | pub type __fsblkcnt_t = ::std::os::raw::c_ulong; 179 | pub type __fsblkcnt64_t = ::std::os::raw::c_ulong; 180 | pub type __fsfilcnt_t = ::std::os::raw::c_ulong; 181 | pub type __fsfilcnt64_t = ::std::os::raw::c_ulong; 182 | pub type __fsword_t = ::std::os::raw::c_long; 183 | pub type __ssize_t = ::std::os::raw::c_long; 184 | pub type __syscall_slong_t = ::std::os::raw::c_long; 185 | pub type __syscall_ulong_t = ::std::os::raw::c_ulong; 186 | pub type __loff_t = __off64_t; 187 | pub type __caddr_t = *mut ::std::os::raw::c_char; 188 | pub type __intptr_t = ::std::os::raw::c_long; 189 | pub type __socklen_t = ::std::os::raw::c_uint; 190 | pub type __sig_atomic_t = ::std::os::raw::c_int; 191 | pub type int_least8_t = __int_least8_t; 192 | pub type int_least16_t = __int_least16_t; 193 | pub type int_least32_t = __int_least32_t; 194 | pub type int_least64_t = __int_least64_t; 195 | pub type uint_least8_t = __uint_least8_t; 196 | pub type uint_least16_t = __uint_least16_t; 197 | pub type uint_least32_t = __uint_least32_t; 198 | pub type uint_least64_t = __uint_least64_t; 199 | pub type int_fast8_t = ::std::os::raw::c_schar; 200 | pub type int_fast16_t = ::std::os::raw::c_long; 201 | pub type int_fast32_t = ::std::os::raw::c_long; 202 | pub type int_fast64_t = ::std::os::raw::c_long; 203 | pub type uint_fast8_t = ::std::os::raw::c_uchar; 204 | pub type uint_fast16_t = ::std::os::raw::c_ulong; 205 | pub type uint_fast32_t = ::std::os::raw::c_ulong; 206 | pub type uint_fast64_t = ::std::os::raw::c_ulong; 207 | pub type intmax_t = __intmax_t; 208 | pub type uintmax_t = __uintmax_t; 209 | #[repr(C)] 210 | #[derive(Debug, Copy, Clone)] 211 | pub struct local_result { 212 | pub buf: *mut ::std::os::raw::c_char, 213 | pub len: usize, 214 | pub _vec: *mut ::std::os::raw::c_void, 215 | pub elapsed: f64, 216 | pub rows_read: u64, 217 | pub bytes_read: u64, 218 | } 219 | #[allow(clippy::unnecessary_operation, clippy::identity_op)] 220 | const _: () = { 221 | ["Size of local_result"][::std::mem::size_of::() - 48usize]; 222 | ["Alignment of local_result"][::std::mem::align_of::() - 8usize]; 223 | ["Offset of field: local_result::buf"][::std::mem::offset_of!(local_result, buf) - 0usize]; 224 | ["Offset of field: local_result::len"][::std::mem::offset_of!(local_result, len) - 8usize]; 225 | ["Offset of field: local_result::_vec"][::std::mem::offset_of!(local_result, _vec) - 16usize]; 226 | ["Offset of field: local_result::elapsed"] 227 | [::std::mem::offset_of!(local_result, elapsed) - 24usize]; 228 | ["Offset of field: local_result::rows_read"] 229 | [::std::mem::offset_of!(local_result, rows_read) - 32usize]; 230 | ["Offset of field: local_result::bytes_read"] 231 | [::std::mem::offset_of!(local_result, bytes_read) - 40usize]; 232 | }; 233 | #[repr(C)] 234 | #[derive(Debug, Copy, Clone)] 235 | pub struct local_result_v2 { 236 | pub buf: *mut ::std::os::raw::c_char, 237 | pub len: usize, 238 | pub _vec: *mut ::std::os::raw::c_void, 239 | pub elapsed: f64, 240 | pub rows_read: u64, 241 | pub bytes_read: u64, 242 | pub error_message: *mut ::std::os::raw::c_char, 243 | } 244 | #[allow(clippy::unnecessary_operation, clippy::identity_op)] 245 | const _: () = { 246 | ["Size of local_result_v2"][::std::mem::size_of::() - 56usize]; 247 | ["Alignment of local_result_v2"][::std::mem::align_of::() - 8usize]; 248 | ["Offset of field: local_result_v2::buf"] 249 | [::std::mem::offset_of!(local_result_v2, buf) - 0usize]; 250 | ["Offset of field: local_result_v2::len"] 251 | [::std::mem::offset_of!(local_result_v2, len) - 8usize]; 252 | ["Offset of field: local_result_v2::_vec"] 253 | [::std::mem::offset_of!(local_result_v2, _vec) - 16usize]; 254 | ["Offset of field: local_result_v2::elapsed"] 255 | [::std::mem::offset_of!(local_result_v2, elapsed) - 24usize]; 256 | ["Offset of field: local_result_v2::rows_read"] 257 | [::std::mem::offset_of!(local_result_v2, rows_read) - 32usize]; 258 | ["Offset of field: local_result_v2::bytes_read"] 259 | [::std::mem::offset_of!(local_result_v2, bytes_read) - 40usize]; 260 | ["Offset of field: local_result_v2::error_message"] 261 | [::std::mem::offset_of!(local_result_v2, error_message) - 48usize]; 262 | }; 263 | extern "C" { 264 | pub fn query_stable( 265 | argc: ::std::os::raw::c_int, 266 | argv: *mut *mut ::std::os::raw::c_char, 267 | ) -> *mut local_result; 268 | } 269 | extern "C" { 270 | pub fn free_result(result: *mut local_result); 271 | } 272 | extern "C" { 273 | pub fn query_stable_v2( 274 | argc: ::std::os::raw::c_int, 275 | argv: *mut *mut ::std::os::raw::c_char, 276 | ) -> *mut local_result_v2; 277 | } 278 | extern "C" { 279 | pub fn free_result_v2(result: *mut local_result_v2); 280 | } 281 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::NulError; 2 | use std::string::FromUtf8Error; 3 | 4 | #[derive(Debug, thiserror::Error)] 5 | pub enum Error { 6 | #[error("An unknown error has occurred")] 7 | Unknown, 8 | #[error("No result")] 9 | NoResult, 10 | #[error("Invalid data: {0}")] 11 | InvalidData(String), 12 | #[error("Invalid path")] 13 | PathError, 14 | #[error(transparent)] 15 | Io(#[from] std::io::Error), 16 | #[error(transparent)] 17 | Nul(#[from] NulError), 18 | #[error("Insufficient dir permissions")] 19 | InsufficientPermissions, 20 | #[error("Non UTF-8 sequence: {0}")] 21 | NonUtf8Sequence(FromUtf8Error), 22 | #[error("{0}")] 23 | QueryError(String), 24 | } 25 | 26 | pub type Result = std::result::Result; 27 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub enum InputFormat { 3 | TabSeparated, 4 | TabSeparatedRaw, 5 | TabSeparatedWithNames, 6 | TabSeparatedWithNamesAndTypes, 7 | TabSeparatedRawWithNames, 8 | TabSeparatedRawWithNamesAndTypes, 9 | Template, 10 | TemplateIgnoreSpaces, 11 | CSV, 12 | CSVWithNames, 13 | CSVWithNamesAndTypes, 14 | CustomSeparated, 15 | CustomSeparatedWithNames, 16 | CustomSeparatedWithNamesAndTypes, 17 | Values, 18 | JSON, 19 | JSONAsString, 20 | JSONAsObject, 21 | JSONStrings, 22 | JSONColumns, 23 | JSONColumnsWithMetadata, 24 | JSONCompact, 25 | JSONCompactColumns, 26 | JSONEachRow, 27 | JSONStringsEachRow, 28 | JSONCompactEachRow, 29 | JSONCompactEachRowWithNames, 30 | JSONCompactEachRowWithNamesAndTypes, 31 | JSONCompactStringsEachRow, 32 | JSONCompactStringsEachRowWithNames, 33 | JSONCompactStringsEachRowWithNamesAndTypes, 34 | JSONObjectEachRow, 35 | BSONEachRow, 36 | TSKV, 37 | Protobuf, 38 | ProtobufSingle, 39 | ProtobufList, 40 | Avro, 41 | AvroConfluent, 42 | Parquet, 43 | ParquetMetadata, 44 | Arrow, 45 | ArrowStream, 46 | ORC, 47 | One, 48 | Npy, 49 | RowBinary, 50 | RowBinaryWithNames, 51 | RowBinaryWithNamesAndTypes, 52 | RowBinaryWithDefaults, 53 | Native, 54 | CapnProto, 55 | LineAsString, 56 | Regexp, 57 | RawBLOB, 58 | MsgPack, 59 | MySQLDump, 60 | DWARF, 61 | Form, 62 | } 63 | 64 | #[derive(Debug, Clone, Copy)] 65 | pub enum OutputFormat { 66 | TabSeparated, 67 | TabSeparatedRaw, 68 | TabSeparatedWithNames, 69 | TabSeparatedWithNamesAndTypes, 70 | TabSeparatedRawWithNames, 71 | TabSeparatedRawWithNamesAndTypes, 72 | Template, 73 | CSV, 74 | CSVWithNames, 75 | CSVWithNamesAndTypes, 76 | CustomSeparated, 77 | CustomSeparatedWithNames, 78 | CustomSeparatedWithNamesAndTypes, 79 | Values, 80 | JSON, 81 | JSONStrings, 82 | JSONColumns, 83 | JSONColumnsWithMetadata, 84 | JSONCompact, 85 | JSONCompactStrings, 86 | JSONCompactColumns, 87 | JSONEachRow, 88 | PrettyJSONEachRow, 89 | JSONEachRowWithProgress, 90 | JSONStringsEachRow, 91 | JSONStringsEachRowWithProgress, 92 | JSONCompactEachRow, 93 | JSONCompactEachRowWithNames, 94 | JSONCompactEachRowWithNamesAndTypes, 95 | JSONCompactStringsEachRow, 96 | JSONCompactStringsEachRowWithNames, 97 | JSONCompactStringsEachRowWithNamesAndTypes, 98 | JSONObjectEachRow, 99 | BSONEachRow, 100 | TSKV, 101 | Pretty, 102 | PrettyNoEscapes, 103 | PrettyMonoBlock, 104 | PrettyNoEscapesMonoBlock, 105 | PrettyCompact, 106 | PrettyCompactNoEscapes, 107 | PrettyCompactMonoBlock, 108 | PrettyCompactNoEscapesMonoBlock, 109 | PrettySpace, 110 | PrettySpaceNoEscapes, 111 | PrettySpaceMonoBlock, 112 | PrettySpaceNoEscapesMonoBlock, 113 | Prometheus, 114 | Protobuf, 115 | ProtobufSingle, 116 | ProtobufList, 117 | Avro, 118 | Parquet, 119 | Arrow, 120 | ArrowStream, 121 | ORC, 122 | Npy, 123 | RowBinary, 124 | RowBinaryWithNames, 125 | RowBinaryWithNamesAndTypes, 126 | Native, 127 | Null, 128 | XML, 129 | CapnProto, 130 | LineAsString, 131 | RawBLOB, 132 | MsgPack, 133 | Markdown, 134 | Vertical, 135 | } 136 | 137 | impl InputFormat { 138 | pub const fn as_str(self) -> &'static str { 139 | match self { 140 | Self::TabSeparated => "TabSeparated", 141 | Self::TabSeparatedRaw => "TabSeparatedRaw", 142 | Self::TabSeparatedWithNames => "TabSeparatedWithNames", 143 | Self::TabSeparatedWithNamesAndTypes => "TabSeparatedWithNamesAndTypes", 144 | Self::TabSeparatedRawWithNames => "TabSeparatedRawWithNames", 145 | Self::TabSeparatedRawWithNamesAndTypes => "TabSeparatedRawWithNamesAndTypes", 146 | Self::Template => "Template", 147 | Self::TemplateIgnoreSpaces => "TemplateIgnoreSpaces", 148 | Self::CSV => "CSV", 149 | Self::CSVWithNames => "CSVWithNames", 150 | Self::CSVWithNamesAndTypes => "CSVWithNamesAndTypes", 151 | Self::CustomSeparated => "CustomSeparated", 152 | Self::CustomSeparatedWithNames => "CustomSeparatedWithNames", 153 | Self::CustomSeparatedWithNamesAndTypes => "CustomSeparatedWithNamesAndTypes", 154 | Self::Values => "Values", 155 | Self::JSON => "JSON", 156 | Self::JSONAsString => "JSONAsString", 157 | Self::JSONAsObject => "JSONAsObject", 158 | Self::JSONStrings => "JSONStrings", 159 | Self::JSONColumns => "JSONColumns", 160 | Self::JSONColumnsWithMetadata => "JSONColumnsWithMetadata", 161 | Self::JSONCompact => "JSONCompact", 162 | Self::JSONCompactColumns => "JSONCompactColumns", 163 | Self::JSONEachRow => "JSONEachRow", 164 | Self::JSONStringsEachRow => "JSONStringsEachRow", 165 | Self::JSONCompactEachRow => "JSONCompactEachRow", 166 | Self::JSONCompactEachRowWithNames => "JSONCompactEachRowWithNames", 167 | Self::JSONCompactEachRowWithNamesAndTypes => "JSONCompactEachRowWithNamesAndTypes", 168 | Self::JSONCompactStringsEachRow => "JSONCompactStringsEachRow", 169 | Self::JSONCompactStringsEachRowWithNames => "JSONCompactStringsEachRowWithNames", 170 | Self::JSONCompactStringsEachRowWithNamesAndTypes => { 171 | "JSONCompactStringsEachRowWithNamesAndTypes" 172 | } 173 | Self::JSONObjectEachRow => "JSONObjectEachRow", 174 | Self::BSONEachRow => "BSONEachRow", 175 | Self::TSKV => "TSKV", 176 | Self::Protobuf => "Protobuf", 177 | Self::ProtobufSingle => "ProtobufSingle", 178 | Self::ProtobufList => "ProtobufList", 179 | Self::Avro => "Avro", 180 | Self::AvroConfluent => "AvroConfluent", 181 | Self::Parquet => "Parquet", 182 | Self::ParquetMetadata => "ParquetMetadata", 183 | Self::Arrow => "Arrow", 184 | Self::ArrowStream => "ArrowStream", 185 | Self::ORC => "ORC", 186 | Self::One => "One", 187 | Self::Npy => "Npy", 188 | Self::RowBinary => "RowBinary", 189 | Self::RowBinaryWithNames => "RowBinaryWithNames", 190 | Self::RowBinaryWithNamesAndTypes => "RowBinaryWithNamesAndTypes", 191 | Self::RowBinaryWithDefaults => "RowBinaryWithDefaults", 192 | Self::Native => "Native", 193 | Self::CapnProto => "CapnProto", 194 | Self::LineAsString => "LineAsString", 195 | Self::Regexp => "Regexp", 196 | Self::RawBLOB => "RawBLOB", 197 | Self::MsgPack => "MsgPack", 198 | Self::MySQLDump => "MySQLDump", 199 | Self::DWARF => "DWARF", 200 | Self::Form => "Form", 201 | } 202 | } 203 | } 204 | 205 | impl OutputFormat { 206 | pub const fn as_str(self) -> &'static str { 207 | match self { 208 | Self::TabSeparated => "TabSeparated", 209 | Self::TabSeparatedRaw => "TabSeparatedRaw", 210 | Self::TabSeparatedWithNames => "TabSeparatedWithNames", 211 | Self::TabSeparatedWithNamesAndTypes => "TabSeparatedWithNamesAndTypes", 212 | Self::TabSeparatedRawWithNames => "TabSeparatedRawWithNames", 213 | Self::TabSeparatedRawWithNamesAndTypes => "TabSeparatedRawWithNamesAndTypes", 214 | Self::Template => "Template", 215 | Self::CSV => "CSV", 216 | Self::CSVWithNames => "CSVWithNames", 217 | Self::CSVWithNamesAndTypes => "CSVWithNamesAndTypes", 218 | Self::CustomSeparated => "CustomSeparated", 219 | Self::CustomSeparatedWithNames => "CustomSeparatedWithNames", 220 | Self::CustomSeparatedWithNamesAndTypes => "CustomSeparatedWithNamesAndTypes", 221 | Self::Values => "Values", 222 | Self::JSON => "JSON", 223 | Self::JSONStrings => "JSONStrings", 224 | Self::JSONColumns => "JSONColumns", 225 | Self::JSONColumnsWithMetadata => "JSONColumnsWithMetadata", 226 | Self::JSONCompact => "JSONCompact", 227 | Self::JSONCompactStrings => "JSONCompactStrings", 228 | Self::JSONCompactColumns => "JSONCompactColumns", 229 | Self::JSONEachRow => "JSONEachRow", 230 | Self::PrettyJSONEachRow => "PrettyJSONEachRow", 231 | Self::JSONEachRowWithProgress => "JSONEachRowWithProgress", 232 | Self::JSONStringsEachRow => "JSONStringsEachRow", 233 | Self::JSONStringsEachRowWithProgress => "JSONStringsEachRowWithProgress", 234 | Self::JSONCompactEachRow => "JSONCompactEachRow", 235 | Self::JSONCompactEachRowWithNames => "JSONCompactEachRowWithNames", 236 | Self::JSONCompactEachRowWithNamesAndTypes => "JSONCompactEachRowWithNamesAndTypes", 237 | Self::JSONCompactStringsEachRow => "JSONCompactStringsEachRow", 238 | Self::JSONCompactStringsEachRowWithNames => "JSONCompactStringsEachRowWithNames", 239 | Self::JSONCompactStringsEachRowWithNamesAndTypes => { 240 | "JSONCompactStringsEachRowWithNamesAndTypes" 241 | } 242 | Self::JSONObjectEachRow => "JSONObjectEachRow", 243 | Self::BSONEachRow => "BSONEachRow", 244 | Self::TSKV => "TSKV", 245 | Self::Pretty => "Pretty", 246 | Self::PrettyNoEscapes => "PrettyNoEscapes", 247 | Self::PrettyMonoBlock => "PrettyMonoBlock", 248 | Self::PrettyNoEscapesMonoBlock => "PrettyNoEscapesMonoBlock", 249 | Self::PrettyCompact => "PrettyCompact", 250 | Self::PrettyCompactNoEscapes => "PrettyCompactNoEscapes", 251 | Self::PrettyCompactMonoBlock => "PrettyCompactMonoBlock", 252 | Self::PrettyCompactNoEscapesMonoBlock => "PrettyCompactNoEscapesMonoBlock", 253 | Self::PrettySpace => "PrettySpace", 254 | Self::PrettySpaceNoEscapes => "PrettySpaceNoEscapes", 255 | Self::PrettySpaceMonoBlock => "PrettySpaceMonoBlock", 256 | Self::PrettySpaceNoEscapesMonoBlock => "PrettySpaceNoEscapesMonoBlock", 257 | Self::Prometheus => "Prometheus", 258 | Self::Protobuf => "Protobuf", 259 | Self::ProtobufSingle => "ProtobufSingle", 260 | Self::ProtobufList => "ProtobufList", 261 | Self::Avro => "Avro", 262 | Self::Parquet => "Parquet", 263 | Self::Arrow => "Arrow", 264 | Self::ArrowStream => "ArrowStream", 265 | Self::ORC => "ORC", 266 | Self::Npy => "Npy", 267 | Self::RowBinary => "RowBinary", 268 | Self::RowBinaryWithNames => "RowBinaryWithNames", 269 | Self::RowBinaryWithNamesAndTypes => "RowBinaryWithNamesAndTypes", 270 | Self::Native => "Native", 271 | Self::Null => "Null", 272 | Self::XML => "XML", 273 | Self::CapnProto => "CapnProto", 274 | Self::LineAsString => "LineAsString", 275 | Self::RawBLOB => "RawBLOB", 276 | Self::MsgPack => "MsgPack", 277 | Self::Markdown => "Markdown", 278 | Self::Vertical => "Vertical", 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod arg; 2 | #[allow( 3 | dead_code, 4 | unused, 5 | non_snake_case, 6 | non_camel_case_types, 7 | non_upper_case_globals 8 | )] 9 | mod bindings; 10 | pub mod error; 11 | pub mod format; 12 | pub mod log_level; 13 | pub mod query_result; 14 | pub mod session; 15 | 16 | use std::ffi::{c_char, CString}; 17 | 18 | use crate::arg::Arg; 19 | use crate::error::Error; 20 | use crate::error::Result; 21 | use crate::query_result::QueryResult; 22 | 23 | pub fn execute(query: &str, query_args: Option<&[Arg]>) -> Result { 24 | let mut argv = Vec::with_capacity(query_args.as_ref().map_or(0, |v| v.len()) + 2); 25 | argv.push(arg_clickhouse()?.into_raw()); 26 | 27 | if let Some(args) = query_args { 28 | for arg in args { 29 | argv.push(arg.to_cstring()?.into_raw()); 30 | } 31 | } 32 | 33 | argv.push(arg_query(query)?.into_raw()); 34 | call_chdb(argv) 35 | } 36 | 37 | fn call_chdb(mut argv: Vec<*mut c_char>) -> Result { 38 | let argc = argv.len() as i32; 39 | let argv = argv.as_mut_ptr(); 40 | let result_ptr = unsafe { bindings::query_stable_v2(argc, argv) }; 41 | 42 | if result_ptr.is_null() { 43 | return Err(Error::NoResult); 44 | } 45 | let result = QueryResult::new(result_ptr); 46 | let result = result.check_error()?; 47 | 48 | Ok(result) 49 | } 50 | 51 | fn arg_clickhouse() -> Result { 52 | Ok(CString::new("clickhouse")?) 53 | } 54 | 55 | fn arg_data_path(value: &str) -> Result { 56 | Ok(CString::new(format!("--path={}", value))?) 57 | } 58 | 59 | fn arg_query(value: &str) -> Result { 60 | Ok(CString::new(format!("--query={}", value))?) 61 | } 62 | -------------------------------------------------------------------------------- /src/log_level.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub enum LogLevel { 3 | Trace, 4 | Debug, 5 | Info, 6 | Warn, 7 | Error, 8 | } 9 | 10 | impl LogLevel { 11 | pub const fn as_str(self) -> &'static str { 12 | match self { 13 | Self::Trace => "trace", 14 | Self::Debug => "debug", 15 | Self::Info => "information", 16 | Self::Warn => "warning", 17 | Self::Error => "error", 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/query_result.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | use std::borrow::Cow; 3 | use std::ffi::CStr; 4 | use std::time::Duration; 5 | 6 | use crate::bindings; 7 | use crate::error::Error; 8 | use crate::error::Result; 9 | #[derive(Clone)] 10 | pub struct QueryResult { 11 | inner: *mut bindings::local_result_v2, 12 | } 13 | 14 | impl QueryResult { 15 | pub(crate) fn new(inner: *mut bindings::local_result_v2) -> Self { 16 | Self { inner } 17 | } 18 | pub fn data_utf8(&self) -> Result { 19 | let buf = self.data_ref(); 20 | 21 | String::from_utf8(buf.to_vec()).map_err(Error::NonUtf8Sequence) 22 | } 23 | 24 | pub fn data_utf8_lossy(&self) -> Cow { 25 | String::from_utf8_lossy(self.data_ref()) 26 | } 27 | 28 | pub fn data_utf8_unchecked(&self) -> String { 29 | unsafe { String::from_utf8_unchecked(self.data_ref().to_vec()) } 30 | } 31 | 32 | pub fn data_ref(&self) -> &[u8] { 33 | let inner = self.inner; 34 | let buf = unsafe { (*inner).buf }; 35 | let len = unsafe { (*inner).len }; 36 | let bytes: &[u8] = unsafe { slice::from_raw_parts(buf as *const u8, len) }; 37 | bytes 38 | } 39 | 40 | pub fn rows_read(&self) -> u64 { 41 | let inner = self.inner; 42 | unsafe { *inner }.rows_read 43 | } 44 | 45 | pub fn bytes_read(&self) -> u64 { 46 | let inner = self.inner; 47 | unsafe { *inner }.bytes_read 48 | } 49 | 50 | pub fn elapsed(&self) -> Duration { 51 | let elapsed = unsafe { (*self.inner).elapsed }; 52 | Duration::from_secs_f64(elapsed) 53 | } 54 | 55 | pub(crate) fn check_error(self) -> Result { 56 | self.check_error_ref()?; 57 | Ok(self) 58 | } 59 | pub(crate) fn check_error_ref(&self) -> Result<()> { 60 | let err_ptr = unsafe { (*self.inner).error_message }; 61 | 62 | if err_ptr.is_null() { 63 | return Ok(()); 64 | } 65 | 66 | Err(Error::QueryError(unsafe { 67 | CStr::from_ptr(err_ptr).to_string_lossy().to_string() 68 | })) 69 | } 70 | } 71 | 72 | impl Drop for QueryResult { 73 | fn drop(&mut self) { 74 | unsafe { bindings::free_result_v2(self.inner) }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/session.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | use crate::arg::Arg; 6 | use crate::arg_clickhouse; 7 | use crate::arg_data_path; 8 | use crate::arg_query; 9 | use crate::call_chdb; 10 | use crate::error::Error; 11 | use crate::query_result::QueryResult; 12 | 13 | pub struct SessionBuilder<'a> { 14 | data_path: PathBuf, 15 | default_args: Vec>, 16 | auto_cleanup: bool, 17 | } 18 | 19 | #[derive(Clone)] 20 | pub struct Session { 21 | default_args: Vec, 22 | data_path: String, 23 | auto_cleanup: bool, 24 | } 25 | 26 | impl<'a> SessionBuilder<'a> { 27 | pub fn new() -> Self { 28 | let mut data_path = std::env::current_dir().unwrap(); 29 | data_path.push("chdb"); 30 | 31 | Self { 32 | data_path, 33 | default_args: Vec::new(), 34 | auto_cleanup: false, 35 | } 36 | } 37 | 38 | pub fn with_data_path(mut self, path: impl Into) -> Self { 39 | self.data_path = path.into(); 40 | self 41 | } 42 | 43 | pub fn with_arg(mut self, arg: Arg<'a>) -> Self { 44 | self.default_args.push(arg); 45 | self 46 | } 47 | 48 | /// If set Session will delete data directory before it is dropped. 49 | pub fn with_auto_cleanup(mut self, value: bool) -> Self { 50 | self.auto_cleanup = value; 51 | self 52 | } 53 | 54 | pub fn build(self) -> Result { 55 | let data_path = self.data_path.to_str().ok_or(Error::PathError)?.to_string(); 56 | 57 | fs::create_dir_all(&self.data_path)?; 58 | if fs::metadata(&self.data_path)?.permissions().readonly() { 59 | return Err(Error::InsufficientPermissions); 60 | } 61 | 62 | let mut default_args = Vec::with_capacity(self.default_args.len() + 2); 63 | default_args.push(arg_clickhouse()?); 64 | default_args.push(arg_data_path(&data_path)?); 65 | 66 | for default_arg in self.default_args { 67 | default_args.push(default_arg.to_cstring()?); 68 | } 69 | 70 | Ok(Session { 71 | data_path, 72 | default_args, 73 | auto_cleanup: self.auto_cleanup, 74 | }) 75 | } 76 | } 77 | 78 | impl<'a> Default for SessionBuilder<'a> { 79 | fn default() -> Self { 80 | Self::new() 81 | } 82 | } 83 | 84 | impl Session { 85 | pub fn execute(&self, query: &str, query_args: Option<&[Arg]>) -> Result { 86 | let mut argv = Vec::with_capacity( 87 | self.default_args.len() + query_args.as_ref().map_or(0, |v| v.len()) + 1, 88 | ); 89 | 90 | for arg in &self.default_args { 91 | argv.push(arg.clone().into_raw()) 92 | } 93 | 94 | if let Some(args) = query_args { 95 | for arg in args { 96 | argv.push(arg.to_cstring()?.into_raw()); 97 | } 98 | } 99 | 100 | argv.push(arg_query(query)?.into_raw()); 101 | call_chdb(argv) 102 | } 103 | } 104 | 105 | impl Drop for Session { 106 | fn drop(&mut self) { 107 | if self.auto_cleanup { 108 | fs::remove_dir_all(&self.data_path).ok(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/examples.rs: -------------------------------------------------------------------------------- 1 | use chdb_rust::arg::Arg; 2 | use chdb_rust::error::Result; 3 | use chdb_rust::execute; 4 | use chdb_rust::format::InputFormat; 5 | use chdb_rust::format::OutputFormat; 6 | use chdb_rust::log_level::LogLevel; 7 | use chdb_rust::session::SessionBuilder; 8 | 9 | #[test] 10 | fn test_stateful() -> Result<()> { 11 | // 12 | // Create session. 13 | // 14 | let tmp = tempdir::TempDir::new("chdb-rust")?; 15 | let session = SessionBuilder::new() 16 | .with_data_path(tmp.path()) 17 | .with_arg(Arg::LogLevel(LogLevel::Debug)) 18 | .with_arg(Arg::Custom("priority".into(), Some("1".into()))) 19 | .with_auto_cleanup(true) 20 | .build()?; 21 | 22 | // 23 | // Create database. 24 | // 25 | 26 | session.execute("CREATE DATABASE demo; USE demo", Some(&[Arg::MultiQuery]))?; 27 | 28 | // 29 | // Create table. 30 | // 31 | 32 | session.execute( 33 | "CREATE TABLE logs (id UInt64, msg String) ENGINE = MergeTree() ORDER BY id", 34 | None, 35 | )?; 36 | 37 | // 38 | // Insert into table. 39 | // 40 | 41 | session.execute("INSERT INTO logs (id, msg) VALUES (1, 'test')", None)?; 42 | 43 | // 44 | // Select from table. 45 | // 46 | let len = session.execute( 47 | "SELECT COUNT(*) FROM logs", 48 | Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]), 49 | )?; 50 | 51 | assert_eq!(len.data_utf8_lossy(), "{\"COUNT()\":1}\n"); 52 | 53 | let result = session.execute( 54 | "SELECT * FROM logs", 55 | Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]), 56 | )?; 57 | assert_eq!(result.data_utf8_lossy(), "{\"id\":1,\"msg\":\"test\"}\n"); 58 | Ok(()) 59 | } 60 | 61 | #[test] 62 | fn test_stateless() -> Result<()> { 63 | let query = format!( 64 | "SELECT * FROM file('tests/logs.csv', {})", 65 | InputFormat::CSV.as_str() 66 | ); 67 | 68 | let result = execute( 69 | &query, 70 | Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]), 71 | )?; 72 | 73 | assert_eq!(result.data_utf8_lossy(), "{\"id\":1,\"msg\":\"test\"}\n"); 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /tests/logs.csv: -------------------------------------------------------------------------------- 1 | id,msg 2 | 1,test -------------------------------------------------------------------------------- /update_libchdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Check for necessary tools 6 | command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but it's not installed. Aborting."; exit 1; } 7 | command -v tar >/dev/null 2>&1 || { echo >&2 "tar is required but it's not installed. Aborting."; exit 1; } 8 | 9 | # Function to download and extract the file 10 | download_and_extract() { 11 | local url=$1 12 | local file="libchdb.tar.gz" 13 | 14 | echo "Attempting to download $PLATFORM from $url" 15 | 16 | # Download the file with a retry logic 17 | if curl -L -o "$file" "$url"; then 18 | echo "Download successful." 19 | 20 | # Optional: Verify download integrity here, if checksums are provided 21 | 22 | # Untar the file 23 | if tar -xzf "$file"; then 24 | echo "Extraction successful." 25 | return 0 26 | fi 27 | fi 28 | return 1 29 | } 30 | 31 | # Get the newest release version 32 | LATEST_RELEASE=v2.1.1 33 | 34 | # Select the correct package based on OS and architecture 35 | case "$(uname -s)" in 36 | Linux) 37 | if [[ $(uname -m) == "aarch64" ]]; then 38 | PLATFORM="linux-aarch64-libchdb.tar.gz" 39 | else 40 | PLATFORM="linux-x86_64-libchdb.tar.gz" 41 | fi 42 | ;; 43 | Darwin) 44 | if [[ $(uname -m) == "arm64" ]]; then 45 | PLATFORM="macos-arm64-libchdb.tar.gz" 46 | else 47 | PLATFORM="macos-x86_64-libchdb.tar.gz" 48 | fi 49 | ;; 50 | *) 51 | echo "Unsupported platform" 52 | exit 1 53 | ;; 54 | esac 55 | 56 | # Main download URL 57 | DOWNLOAD_URL="https://github.com/chdb-io/chdb/releases/download/$LATEST_RELEASE/$PLATFORM" 58 | FALLBACK_URL="https://github.com/chdb-io/chdb/releases/latest/download/$PLATFORM" 59 | 60 | # Try the main download URL first 61 | if ! download_and_extract "$DOWNLOAD_URL"; then 62 | echo "Retrying with fallback URL..." 63 | if ! download_and_extract "$FALLBACK_URL"; then 64 | echo "Both primary and fallback downloads failed. Aborting." 65 | exit 1 66 | fi 67 | fi 68 | 69 | chmod +x libchdb.so 70 | 71 | if [[ "$1" == "--global" ]]; then 72 | # If current uid is not 0, check if sudo is available and request the user to input the password 73 | if [[ $EUID -ne 0 ]]; then 74 | command -v sudo >/dev/null 2>&1 || { echo >&2 "This script requires sudo privileges but sudo is not installed. Aborting."; exit 1; } 75 | echo "Installation requires administrative access. You will be prompted for your password." 76 | fi 77 | 78 | # Define color messages if terminal supports them 79 | if [[ -t 1 ]]; then 80 | RED='\033[0;31m' 81 | GREEN='\033[0;32m' 82 | NC='\033[0m' # No Color 83 | REDECHO() { echo -e "${RED}$@${NC}"; } 84 | GREENECHO() { echo -e "${GREEN}$@${NC}"; } 85 | ENDECHO() { echo -ne "${NC}"; } 86 | else 87 | REDECHO() { echo "$@"; } 88 | GREENECHO() { echo "$@"; } 89 | ENDECHO() { :; } 90 | fi 91 | 92 | # Use sudo if not running as root 93 | SUDO='' 94 | if [[ $EUID -ne 0 ]]; then 95 | SUDO='sudo' 96 | GREENECHO "\nYou will be asked for your sudo password to install:" 97 | echo " libchdb.so to /usr/local/lib/" 98 | echo " chdb.h to /usr/local/include/" 99 | fi 100 | 101 | # Make sure the library and header directory exists 102 | ${SUDO} mkdir -p /usr/local/lib /usr/local/include || true 103 | 104 | # Install the library and header file 105 | ${SUDO} /bin/cp libchdb.so /usr/local/lib/ 106 | ${SUDO} /bin/cp chdb.h /usr/local/include/ 107 | 108 | # Set execute permission for libchdb.so 109 | ${SUDO} chmod +x /usr/local/lib/libchdb.so 110 | 111 | # Update library cache (Linux specific) 112 | if [[ "$(uname -s)" == "Linux" ]]; then 113 | ${SUDO} ldconfig 114 | fi 115 | 116 | GREENECHO "Installation completed successfully." ; ENDECHO 117 | GREENECHO "If any error occurred, please report it to:" ; ENDECHO 118 | GREENECHO " https://github.com/chdb-io/chdb/issues/new/choose" ; ENDECHO 119 | fi 120 | -------------------------------------------------------------------------------- /wrapper.h: -------------------------------------------------------------------------------- 1 | #include "chdb.h" 2 | --------------------------------------------------------------------------------