From 9c951e733cfce95ed957b65d53586ea9e73aba6f Mon Sep 17 00:00:00 2001 From: Adam Cimarosti Date: Mon, 29 Jan 2024 16:10:23 +0000 Subject: [PATCH] feat: draft impl of C ffi --- .github/workflows/ci.yaml | 6 + .gitignore | 3 +- Cargo.toml | 17 +- README.md | 63 +----- questdb-confstr-ffi/Cargo.toml | 16 ++ questdb-confstr-ffi/LICENSE | 201 ++++++++++++++++++ questdb-confstr-ffi/README.md | 5 + questdb-confstr-ffi/cpp_test/CMakeLists.txt | 7 + questdb-confstr-ffi/cpp_test/run | 4 + questdb-confstr-ffi/cpp_test/test.cpp | 126 +++++++++++ .../include/questdb/conf_str.h | 40 ++++ questdb-confstr-ffi/src/lib.rs | 137 ++++++++++++ questdb-confstr/Cargo.toml | 10 + questdb-confstr/LICENSE | 201 ++++++++++++++++++ questdb-confstr/README.md | 60 ++++++ {src => questdb-confstr/src}/lib.rs | 0 {src => questdb-confstr/src}/peekable2.rs | 0 {tests => questdb-confstr/tests}/tests.rs | 0 18 files changed, 826 insertions(+), 70 deletions(-) create mode 100644 questdb-confstr-ffi/Cargo.toml create mode 100644 questdb-confstr-ffi/LICENSE create mode 100644 questdb-confstr-ffi/README.md create mode 100644 questdb-confstr-ffi/cpp_test/CMakeLists.txt create mode 100755 questdb-confstr-ffi/cpp_test/run create mode 100644 questdb-confstr-ffi/cpp_test/test.cpp create mode 100644 questdb-confstr-ffi/include/questdb/conf_str.h create mode 100644 questdb-confstr-ffi/src/lib.rs create mode 100644 questdb-confstr/Cargo.toml create mode 100644 questdb-confstr/LICENSE create mode 100644 questdb-confstr/README.md rename {src => questdb-confstr/src}/lib.rs (100%) rename {src => questdb-confstr/src}/peekable2.rs (100%) rename {tests => questdb-confstr/tests}/tests.rs (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d229de5..6b2d8b4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,3 +44,9 @@ jobs: with: command: clippy args: --all-targets --all-features -- -D warnings + + - name: Run FFI tests + run: | + cd questdb-confstr-ffi + cd cpp_test + ./run diff --git a/.gitignore b/.gitignore index 84f5c4d..d42192d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ Cargo.lock tarpaulin-report.html .idea -.vscode \ No newline at end of file +.vscode +questdb-confstr-ffi/cpp_test/build diff --git a/Cargo.toml b/Cargo.toml index 88bc865..7fb9fdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,7 @@ -[package] -name = "questdb-confstr" -version = "0.1.0" -edition = "2021" -license = "Apache-2.0" -description = "A parser for a configuration string format handling service names and parameters" -repository = "https://github.com/questdb/questdb-confstr-rs" -keywords = ["questdb", "configuration", "parser"] -categories = ["config", "parser-implementations"] -authors = ["Adam Cimarosti "] +[workspace] + +members = [ + "questdb-confstr", "questdb-confstr-ffi", +] + +resolver = "2" diff --git a/README.md b/README.md index 2a89f85..e4ff33b 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,5 @@ -# `questdb-confstr` +# QuestDB configuration string parser -## Format - -Parser for a configuration string format used by -[QuestDB clients](https://questdb.io/docs/reference/clients/overview/). - -The format is as follows: - -```plain -service::key1=value1;key2=value2;key3=value3; -``` - -A few rules: -* The last semicolon is mandatory. -* Service name and keys are case-insensitive. -* Keys are ASCII alphanumeric and start with a letter. -* Values are case-sensitive unicode strings which can contain any characters, - * Except control characters (`0x00..=0x1f` and `0x7f..=0x9f`). - * If semicolons `;` appears in a value, these are escaped as double semicolon `;;`. - -## Grammar - -```plain -conf_str ::= service "::" params | service -service ::= identifier -params ::= param (";" param)* ";" -param ::= key "=" value -key ::= identifier -value ::= { value_char } - -identifier ::= alpha { alphanumeric } -alpha ::= "a".."z" | "A".."Z" -alphanumeric ::= "a".."z" | "A".."Z" | "0".."9" -value_char ::= non_semicolon_char | escaped_semicolon -escaped_semicolon ::= ";;" -non_semicolon_char ::= ? any unicode character except ';', 0x00..=0x1f and 0x7f..=0x9f ? -``` - -## Usage - -### Add dependency to `Cargo.toml` - -```shell -cargo add questdb-confstr -``` - -### Usage - -Use the `parse_conf_str` function to parse into a `ConfStr` struct. - -You can then access the service name as `&str` and parameters as a `&HashMap`. - -### Where we use it - -We use this config parsing format in our [Rust, C, C++](https://github.com/questdb/c-questdb-client) and -[Python](https://github.com/questdb/py-questdb-client) clients. - -We also use it to configure object stores for -[database replication](https://questdb.io/docs/operations/replication/#core-replication-settings). +This is for: +* The [questdb-confstr](./questdb-confstr) crate +* and its bindings for C [questdb-confstr-ffi](./questdb-confstr-ffi) diff --git a/questdb-confstr-ffi/Cargo.toml b/questdb-confstr-ffi/Cargo.toml new file mode 100644 index 0000000..240f470 --- /dev/null +++ b/questdb-confstr-ffi/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "questdb-confstr-ffi" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +description = "C FFI bindings for questdb-confstr" +repository = "https://github.com/questdb/questdb-confstr-rs" +keywords = ["questdb", "configuration", "parser"] +categories = ["config", "parser-implementations"] +authors = ["Adam Cimarosti "] + +[dependencies] +questdb-confstr = { path = "../questdb-confstr", version = "0.1.0" } + +[lib] +crate-type = ["lib", "staticlib"] \ No newline at end of file diff --git a/questdb-confstr-ffi/LICENSE b/questdb-confstr-ffi/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/questdb-confstr-ffi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/questdb-confstr-ffi/README.md b/questdb-confstr-ffi/README.md new file mode 100644 index 0000000..e9fa509 --- /dev/null +++ b/questdb-confstr-ffi/README.md @@ -0,0 +1,5 @@ +# C bindings for `questdb-confstr` + +## Usage + +See `include/questdb/confstr.h` for the C API. diff --git a/questdb-confstr-ffi/cpp_test/CMakeLists.txt b/questdb-confstr-ffi/cpp_test/CMakeLists.txt new file mode 100644 index 0000000..78e688c --- /dev/null +++ b/questdb-confstr-ffi/cpp_test/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.10) +project(FfiTest) +set(CMAKE_CXX_STANDARD 17) +add_executable(test test.cpp) +target_include_directories(test PRIVATE ../include) +target_link_directories(test PRIVATE ../../target/debug) +target_link_libraries(test "questdb_confstr_ffi") \ No newline at end of file diff --git a/questdb-confstr-ffi/cpp_test/run b/questdb-confstr-ffi/cpp_test/run new file mode 100755 index 0000000..3a755a2 --- /dev/null +++ b/questdb-confstr-ffi/cpp_test/run @@ -0,0 +1,4 @@ +rm -fR ./build > /dev/null 2>&1 && \ +cmake -B ./build -S . && \ +cmake --build ./build && \ +./build/test \ No newline at end of file diff --git a/questdb-confstr-ffi/cpp_test/test.cpp b/questdb-confstr-ffi/cpp_test/test.cpp new file mode 100644 index 0000000..025ac91 --- /dev/null +++ b/questdb-confstr-ffi/cpp_test/test.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (c) 2014-2019 Appsicle + * Copyright (c) 2019-2024 QuestDB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include +#include +#include + +#include + + +class parse_err { +public: + parse_err(questdb_conf_str_parse_err* impl) : _impl(impl) {} + + std::string_view msg() const noexcept { + return { _impl->msg, _impl->msg_len }; + } + + size_t pos() const noexcept { + return _impl->pos; + } + + ~parse_err() noexcept { + questdb_conf_str_parse_err_free(_impl); + } + +private: + questdb_conf_str_parse_err* _impl; +}; + + +class conf_str { +public: + static conf_str parse(std::string_view str) { + questdb_conf_str_parse_err* err = nullptr; + auto res = ::questdb_conf_str_parse(str.data(), str.size(), &err); + if (res != nullptr) { + return conf_str{res}; + } else { + throw parse_err(err); + } + } + + std::string_view service() const noexcept { + size_t service_len = 0; + auto str = ::questdb_conf_str_service(_impl, &service_len); + return { str, service_len }; + } + + std::optional get(std::string_view key) const noexcept { + size_t val_len = 0; + auto str = ::questdb_conf_str_get(_impl, key.data(), key.size(), &val_len); + if (str != nullptr) { + return { { str, val_len } }; + } + return {}; + } + + ~conf_str() noexcept { + ::questdb_conf_str_free(_impl); + } + +private: + conf_str(::questdb_conf_str* impl) : _impl(impl) {} + ::questdb_conf_str* _impl; +}; + + +static void t1() { + const auto c1 = conf_str::parse("http"); + assert(c1.service() == "http"); +} + +static void t2() { + const auto c1 = conf_str::parse("http::host=localhost;port=9000;"); + assert(c1.service() == "http"); + assert(c1.get("host") == "localhost"); + assert(c1.get("port") == "9000"); +} + +static void t3() { + try { + const auto c1 = conf_str::parse("http;port=9000"); + abort(); + } catch (const parse_err& e) { + assert(e.msg() == "bad separator, expected ':' got ';' at position 4"); + assert(e.pos() == 4); + } +} + +int main() { + std::cerr << "Running tests" << std::endl; + + std::cerr << "t1" << std::endl; + t1(); + + std::cerr << "t2" << std::endl; + t2(); + + std::cerr << "t3" << std::endl; + t3(); + + std::cerr << "All tests passed" << std::endl; + return 0; +} \ No newline at end of file diff --git a/questdb-confstr-ffi/include/questdb/conf_str.h b/questdb-confstr-ffi/include/questdb/conf_str.h new file mode 100644 index 0000000..68c8faa --- /dev/null +++ b/questdb-confstr-ffi/include/questdb/conf_str.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct questdb_conf_str questdb_conf_str; + +struct questdb_conf_str_parse_err { + const char* msg; + size_t msg_len; + size_t pos; +}; + +typedef struct questdb_conf_str_parse_err questdb_conf_str_parse_err; + +void questdb_conf_str_parse_err_free(questdb_conf_str_parse_err* err); + +questdb_conf_str* questdb_conf_str_parse( + const char* str, + size_t len, + questdb_conf_str_parse_err** err_out); + +const char* questdb_conf_str_service( + const questdb_conf_str* conf_str, + size_t* len_out); + +const char* questdb_conf_str_get( + const questdb_conf_str* conf_str, + const char* key, + size_t key_len, + size_t* val_len_out); + +void questdb_conf_str_free(questdb_conf_str* str); + +#if defined(__cplusplus) +} +#endif diff --git a/questdb-confstr-ffi/src/lib.rs b/questdb-confstr-ffi/src/lib.rs new file mode 100644 index 0000000..bf2f96c --- /dev/null +++ b/questdb-confstr-ffi/src/lib.rs @@ -0,0 +1,137 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (c) 2014-2019 Appsicle + * Copyright (c) 2019-2024 QuestDB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#![doc = include_str!("../README.md")] + +#![allow(clippy::missing_safety_doc)] + +use questdb_confstr::{parse_conf_str, ConfStr}; +use std::os::raw::c_char; +use std::ptr; +use std::slice; + +#[repr(C)] +pub struct questdb_conf_str { + inner: ConfStr, +} + +#[repr(C)] +pub struct questdb_conf_str_parse_err { + msg: *const c_char, + msg_len: usize, + pos: usize, +} + +fn new_err(msg: String, pos: usize) -> *mut questdb_conf_str_parse_err { + let msg_len = msg.len(); + let msg = Box::into_raw(msg.into_boxed_str()) as *const c_char; + Box::into_raw(Box::new(questdb_conf_str_parse_err { msg, msg_len, pos })) +} + +#[no_mangle] +pub unsafe extern "C" fn questdb_conf_str_parse_err_free(err: *mut questdb_conf_str_parse_err) { + if !err.is_null() { + let err = Box::from_raw(err); + drop(Box::from_raw(err.msg as *mut c_char)); + drop(err); + } +} + +#[no_mangle] +pub unsafe extern "C" fn questdb_conf_str_parse( + str: *const c_char, + len: usize, + err_out: *mut *mut questdb_conf_str_parse_err, +) -> *mut questdb_conf_str { + let input = slice::from_raw_parts(str as *const u8, len); + let input_str = match std::str::from_utf8(input) { + Ok(s) => s, + Err(utf8err) => { + let first_bad_byte = utf8err.valid_up_to(); + *err_out = new_err( + format!("invalid UTF-8 sequence at position {}", first_bad_byte), + first_bad_byte, + ); + return ptr::null_mut(); + } + }; + + match parse_conf_str(input_str) { + Ok(conf_str) => Box::into_raw(Box::new(questdb_conf_str { inner: conf_str })), + Err(err) => { + *err_out = new_err(err.to_string(), err.position()); + ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn questdb_conf_str_service( + conf_str: *const questdb_conf_str, + len_out: *mut usize, +) -> *const c_char { + if conf_str.is_null() { + return ptr::null(); + } + + let conf_str = &(*conf_str).inner; + let service = conf_str.service(); + *len_out = service.len(); + service.as_ptr() as *const c_char +} + +#[no_mangle] +pub unsafe extern "C" fn questdb_conf_str_get( + conf_str: *const questdb_conf_str, + key: *const c_char, + key_len: usize, + val_len_out: *mut usize, +) -> *const c_char { + if conf_str.is_null() || key.is_null() { + return ptr::null(); + } + + let conf_str = &(*conf_str).inner; + let key = slice::from_raw_parts(key as *const u8, key_len); + let key_str = match std::str::from_utf8(key) { + Ok(s) => s, + Err(_) => return ptr::null(), + }; + + match conf_str.get(key_str) { + Some(val) => { + let val_str = val.as_ptr() as *const c_char; + *val_len_out = val.len(); + val_str + } + None => ptr::null(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn questdb_conf_str_free(conf_str: *mut questdb_conf_str) { + if !conf_str.is_null() { + drop(Box::from_raw(conf_str)); + } +} diff --git a/questdb-confstr/Cargo.toml b/questdb-confstr/Cargo.toml new file mode 100644 index 0000000..88bc865 --- /dev/null +++ b/questdb-confstr/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "questdb-confstr" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +description = "A parser for a configuration string format handling service names and parameters" +repository = "https://github.com/questdb/questdb-confstr-rs" +keywords = ["questdb", "configuration", "parser"] +categories = ["config", "parser-implementations"] +authors = ["Adam Cimarosti "] diff --git a/questdb-confstr/LICENSE b/questdb-confstr/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/questdb-confstr/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/questdb-confstr/README.md b/questdb-confstr/README.md new file mode 100644 index 0000000..2a89f85 --- /dev/null +++ b/questdb-confstr/README.md @@ -0,0 +1,60 @@ +# `questdb-confstr` + +## Format + +Parser for a configuration string format used by +[QuestDB clients](https://questdb.io/docs/reference/clients/overview/). + +The format is as follows: + +```plain +service::key1=value1;key2=value2;key3=value3; +``` + +A few rules: +* The last semicolon is mandatory. +* Service name and keys are case-insensitive. +* Keys are ASCII alphanumeric and start with a letter. +* Values are case-sensitive unicode strings which can contain any characters, + * Except control characters (`0x00..=0x1f` and `0x7f..=0x9f`). + * If semicolons `;` appears in a value, these are escaped as double semicolon `;;`. + +## Grammar + +```plain +conf_str ::= service "::" params | service +service ::= identifier +params ::= param (";" param)* ";" +param ::= key "=" value +key ::= identifier +value ::= { value_char } + +identifier ::= alpha { alphanumeric } +alpha ::= "a".."z" | "A".."Z" +alphanumeric ::= "a".."z" | "A".."Z" | "0".."9" +value_char ::= non_semicolon_char | escaped_semicolon +escaped_semicolon ::= ";;" +non_semicolon_char ::= ? any unicode character except ';', 0x00..=0x1f and 0x7f..=0x9f ? +``` + +## Usage + +### Add dependency to `Cargo.toml` + +```shell +cargo add questdb-confstr +``` + +### Usage + +Use the `parse_conf_str` function to parse into a `ConfStr` struct. + +You can then access the service name as `&str` and parameters as a `&HashMap`. + +### Where we use it + +We use this config parsing format in our [Rust, C, C++](https://github.com/questdb/c-questdb-client) and +[Python](https://github.com/questdb/py-questdb-client) clients. + +We also use it to configure object stores for +[database replication](https://questdb.io/docs/operations/replication/#core-replication-settings). diff --git a/src/lib.rs b/questdb-confstr/src/lib.rs similarity index 100% rename from src/lib.rs rename to questdb-confstr/src/lib.rs diff --git a/src/peekable2.rs b/questdb-confstr/src/peekable2.rs similarity index 100% rename from src/peekable2.rs rename to questdb-confstr/src/peekable2.rs diff --git a/tests/tests.rs b/questdb-confstr/tests/tests.rs similarity index 100% rename from tests/tests.rs rename to questdb-confstr/tests/tests.rs