├── .cursor └── rules │ ├── duckdb_extension.mdc │ └── duckdb_secrets.mdc ├── .cursorignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── config │ └── distribution_matrix.json ├── docker │ ├── README.md │ ├── linux_amd64 │ │ └── Dockerfile │ ├── linux_amd64_gcc4 │ │ └── Dockerfile │ ├── linux_amd64_musl │ │ └── Dockerfile │ └── linux_arm64 │ │ └── Dockerfile └── workflows │ ├── MainDistributionPipeline.yml │ ├── _extension_build.yml │ └── _extension_deploy.yml ├── .gitignore ├── .gitmodules ├── .python-version ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── DEVELOPMENT.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── WEATHER.csv ├── actual_nw_demo_python.ipynb ├── epm_odp_demo_python.ipynb ├── flight_demo_node.ipynb ├── flight_demo_python.ipynb ├── flight_demo_r.ipynb ├── imgs │ └── bics_rsa1_0D_NW_C01.png └── walkthrough.md ├── extension_config.cmake ├── nwrfcsdk └── .empty ├── packages └── python-erpl │ ├── README.md │ ├── python-erpl │ └── __init__.py │ └── setup.py ├── pyproject.toml ├── rfc ├── CMakeLists.txt ├── src │ ├── duckdb_argument_helper.cpp │ ├── duckdb_serialization_helper.cpp │ ├── erpl_rfc_extension.cpp │ ├── include │ │ ├── duckdb_argument_helper.hpp │ │ ├── duckdb_serialization_helper.hpp │ │ ├── erpl_rfc_extension.hpp │ │ ├── pragma_ini.hpp │ │ ├── pragma_ping.hpp │ │ ├── pragma_set_trace.hpp │ │ ├── sap_connection.hpp │ │ ├── sap_function.hpp │ │ ├── sap_rfc.hpp │ │ ├── sap_secret.hpp │ │ ├── sap_type_conversion.hpp │ │ ├── scanner_describe_fields.hpp │ │ ├── scanner_describe_function.hpp │ │ ├── scanner_describe_references.hpp │ │ ├── scanner_invoke.hpp │ │ ├── scanner_read_table.hpp │ │ ├── scanner_show_functions.hpp │ │ ├── scanner_show_groups.hpp │ │ ├── scanner_show_tables.hpp │ │ └── telemetry.hpp │ ├── pragma_ini.cpp │ ├── pragma_ping.cpp │ ├── pragma_set_trace.cpp │ ├── sap_connection.cpp │ ├── sap_function.cpp │ ├── sap_rfc.cpp │ ├── sap_secret.cpp │ ├── sap_type_conversion.cpp │ ├── scanner_describe_fields.cpp │ ├── scanner_describe_function.cpp │ ├── scanner_describe_references.cpp │ ├── scanner_invoke.cpp │ ├── scanner_read_table.cpp │ ├── scanner_show_functions.cpp │ ├── scanner_show_groups.cpp │ ├── scanner_show_tables.cpp │ └── telemetry.cpp ├── test │ ├── CMakeLists.txt │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── test_main.cpp │ │ ├── test_serialization_helper.cpp │ │ ├── test_table_wrapper.cpp │ │ ├── test_telemetry.cpp │ │ ├── test_type_conversion.cpp │ │ └── test_value_helper.cpp │ ├── nodejs │ │ └── .empty │ ├── python │ │ └── .empty │ └── sql │ │ ├── sap_describe_fields.test │ │ ├── sap_read_table.test │ │ ├── sap_rfc_describe_function.test │ │ ├── sap_rfc_invoke.test │ │ ├── sap_rfc_invoke_decimal.test │ │ ├── sap_rfc_invoke_flight.test │ │ ├── sap_rfc_invoke_stfc.test │ │ ├── sap_rfc_ping.test │ │ ├── sap_rfc_secret.test │ │ ├── sap_rfc_show_function.test │ │ ├── sap_rfc_show_groups.test │ │ └── sap_rfc_show_tables.test └── vcpkg.json ├── scripts ├── A4H_D00_vhcala4hci.profile ├── download_and_extract_nwrfc.ps1 ├── download_and_extract_nwrfc.sh ├── extension-upload.sh ├── functions.cmake ├── lsan_suppress.txt ├── smoke-test.linux.sh ├── start-gui.sh ├── start-sap.sh └── vscode-settings │ ├── cmake-tools-kits-msys2.json │ └── settings-msys2.json ├── trace └── .empty ├── trampoline ├── CMakeLists.txt ├── Makefile ├── src │ ├── erpl_extension.cpp │ └── include │ │ └── erpl_extension.hpp └── vcpkg.json └── uv.lock /.cursor/rules/duckdb_extension.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: *.cpp,*.hpp,CMakeLists.txt,Makefile 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: 8 | globs: *.cpp,*.hpp,CMakeLists.txt,Makefile,vcpkg.json 9 | alwaysApply: false 10 | --- 11 | 12 | The backend is written in modern C++ and uses CMake, DuckDB and COW HTTP Framework. 13 | 14 | 15 | # C++ Development Rules 16 | 17 | You are a senior C++ developer with expertise in modern C++ (C++17/20), STL, and system-level programming. 18 | 19 | ## Code Style and Structure 20 | - Write concise, idiomatic C++ code with accurate examples. 21 | - Follow modern C++ conventions and best practices. 22 | - Use object-oriented, procedural, or functional programming patterns as appropriate. 23 | - Leverage STL and standard algorithms for collection operations. 24 | - Use descriptive variable and method names (e.g., 'isUserSignedIn', 'calculateTotal'). 25 | - Structure files into headers (*.hpp) and implementation files (*.cpp) with logical separation of concerns. 26 | - Do not use `malloc`, prefer the use of smart pointers. Keywords `new` and `delete` are a code smell. 27 | - Strongly prefer the use of `unique_ptr` over `shared_ptr`, only use `shared_ptr` if you **absolutely** have to. 28 | - Use `const` whenever possible. 29 | - Do **not** import namespaces (e.g. `using std`). 30 | - All functions in source files in the core (`src` directory) should be part of the `duckdb` namespace. 31 | - When overriding a virtual method, avoid repeating virtual and always use `override` or `final`. 32 | - Use `[u]int(8|16|32|64)_t` instead of `int`, `long`, `uint` etc. Use `idx_t` instead of `size_t` for offsets/indices/counts of any kind. 33 | - Prefer using references over pointers as arguments. 34 | - Use `const` references for arguments of non-trivial objects (e.g. `std::vector`, ...). 35 | - Use C++11 for loops when possible: `for (const auto& item : items) {...}` 36 | - Use braces for indenting `if` statements and loops. Avoid single-line if statements and loops, especially nested ones. 37 | - **Class Layout:** Start out with a `public` block containing the constructor and public variables, followed by a `public` block containing public methods of the class. After that follow any private functions and private variables. For example: 38 | ```cpp 39 | class MyClass { 40 | public: 41 | MyClass(); 42 | 43 | int my_public_variable; 44 | 45 | public: 46 | void MyFunction(); 47 | 48 | private: 49 | void MyPrivateFunction(); 50 | 51 | private: 52 | int my_private_variable; 53 | }; 54 | ``` 55 | - Avoid [unnamed magic numbers](mdc:https:/en.wikipedia.org/wiki/Magic_number_(programming)). Instead, use named variables that are stored in a `constexpr`. 56 | - [Return early](mdc:https:/medium.com/swlh/return-early-pattern-3d18a41bba8). Avoid deep nested branches. 57 | - Do not include commented out code blocks in pull requests. 58 | 59 | 60 | ## Naming Conventions 61 | - Use PascalCase for class names. 62 | - Use camelCase for variable names and methods. 63 | - Use SCREAMING_SNAKE_CASE for constants and macros. 64 | - Prefix member variables with an underscore or m_ (e.g., `_userId`, `m_userId`). 65 | - Use namespaces to organize code logically. 66 | - Choose descriptive names. Avoid single-letter variable names. 67 | - Files: lowercase separated by underscores, e.g., abstract_operator.cpp 68 | - Types (classes, structs, enums, typedefs, using): CamelCase starting with uppercase letter, e.g., BaseColumn 69 | - Variables: lowercase separated by underscores, e.g., chunk_size 70 | - Functions: CamelCase starting with uppercase letter, e.g., GetChunk 71 | - Avoid `i`, `j`, etc. in **nested** loops. Prefer to use e.g. **column_idx**, **check_idx**. In a **non-nested** loop it is permissible to use **i** as iterator index. 72 | - These rules are partially enforced by `clang-tidy`. 73 | 74 | ## C++ Features Usage 75 | - Prefer modern C++ features (e.g., auto, range-based loops, smart pointers). 76 | - Use `std::unique_ptr` and `std::shared_ptr` for memory management. 77 | - Prefer `std::optional`, `std::variant`, and `std::any` for type-safe alternatives. 78 | - Use `constexpr` and `const` to optimize compile-time computations. 79 | - Use `std::string_view` for read-only string operations to avoid unnecessary copies. 80 | 81 | ## Syntax and Formatting 82 | - Follow a consistent coding style, such as Google C++ Style Guide or your team’s standards. 83 | - Place braces on the same line for control structures and methods. 84 | - Use clear and consistent commenting practices. 85 | 86 | ## Error Handling and Validation 87 | - Use exceptions for error handling (e.g., `std::runtime_error`, `std::invalid_argument`). 88 | - Use RAII for resource management to avoid memory leaks. 89 | - Validate inputs at function boundaries. 90 | - Log errors using a logging library (e.g., crows logging facility). 91 | - Use exceptions **only** when an error is encountered that terminates a query (e.g. parser error, table not found). Exceptions should only be used for **exceptional** situations. For regular errors that do not break the execution flow (e.g. errors you **expect** might occur) use a return value instead. 92 | - Try to add test cases that trigger exceptions. If an exception cannot be easily triggered using a test case then it should probably be an assertion. This is not always true (e.g. out of memory errors are exceptions, but are very hard to trigger). 93 | - Use `D_ASSERT` to assert. Use **assert** only when failing the assert means a programmer error. Assert should never be triggered by user input. Avoid code like `D_ASSERT(a > b + 3);` without comments or context. 94 | - Assert liberally, but make it clear with comments next to the assert what went wrong when the assert is triggered. 95 | 96 | ## Performance Optimization 97 | - Avoid unnecessary heap allocations; prefer stack-based objects where possible. 98 | - Use `std::move` to enable move semantics and avoid copies. 99 | - Optimize loops with algorithms from `` (e.g., `std::sort`, `std::for_each`). 100 | - Profile and optimize critical sections with tools like Valgrind or Perf. 101 | 102 | ## Key Conventions 103 | - Use smart pointers over raw pointers for better memory safety. 104 | - Avoid global variables; use singletons sparingly. 105 | - Use `enum class` for strongly typed enumerations. 106 | - Separate interface from implementation in classes. 107 | - Use templates and metaprogramming judiciously for generic solutions. 108 | 109 | ## Testing 110 | - Write unit tests using frameworks like Google Test (GTest) or Catch2. 111 | - Mock dependencies with libraries like Google Mock. 112 | - Implement integration tests for system components. 113 | 114 | ## Security 115 | - Use secure coding practices to avoid vulnerabilities (e.g., buffer overflows, dangling pointers). 116 | - Prefer `std::array` or `std::vector` over raw arrays. 117 | - Avoid C-style casts; use `static_cast`, `dynamic_cast`, or `reinterpret_cast` when necessary. 118 | - Enforce const-correctness in functions and member variables. 119 | 120 | ## Documentation 121 | - Write clear comments for classes, methods, and critical logic. 122 | - Use Doxygen for generating API documentation. 123 | - Document assumptions, constraints, and expected behavior of code. 124 | 125 | Follow the official ISO C++ standards and guidelines for best practices in modern C++ development. 126 | -------------------------------------------------------------------------------- /.cursorignore: -------------------------------------------------------------------------------- 1 | duckdb/ 2 | vcpkg/ 3 | build/ 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/config/distribution_matrix.json: -------------------------------------------------------------------------------- 1 | { 2 | "linux": { 3 | "include": [ 4 | { 5 | "duckdb_arch": "linux_amd64", 6 | "container": "ubuntu:18.04", 7 | "vcpkg_triplet": "x64-linux" 8 | }, 9 | { 10 | "duckdb_arch": "linux_amd64_gcc4", 11 | "container": "quay.io/pypa/manylinux2014_x86_64", 12 | "vcpkg_triplet": "x64-linux" 13 | } 14 | ] 15 | }, 16 | "osx": { 17 | "include": [ 18 | { 19 | "duckdb_arch": "osx_amd64", 20 | "osx_build_arch": "x86_64", 21 | "vcpkg_triplet": "x64-osx" 22 | }, 23 | { 24 | "duckdb_arch": "osx_arm64", 25 | "osx_build_arch": "arm64", 26 | "vcpkg_triplet": "arm64-osx" 27 | } 28 | ] 29 | }, 30 | "windows": { 31 | "include": [ 32 | { 33 | "duckdb_arch": "windows_amd64", 34 | "vcpkg_triplet": "x64-windows" 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /.github/docker/README.md: -------------------------------------------------------------------------------- 1 | # DuckDB docker images 2 | DuckDB uses Docker images to build linux binaries in a flexible and reproducible way. These images can be used 3 | to compile a DuckDB binary (both extensions and the duckdb shell). For more details on how to use these Dockerfiles, 4 | check out the `.github/workflows/_extension_distribution.yml` workflow, which invokes the dockerfiles as part of the 5 | build process. -------------------------------------------------------------------------------- /.github/docker/linux_amd64/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ### 4 | # Base image setup 5 | ### 6 | 7 | # Setup the basic necessities 8 | ENV DEBIAN_FRONTEND noninteractive 9 | 10 | RUN apt-get update -y -qq 11 | RUN apt-get install -y -qq software-properties-common 12 | RUN apt-get install -y -qq --fix-missing awscli ninja-build make gcc-multilib g++-multilib libssl-dev wget openjdk-8-jdk zip maven unixodbc-dev libc6-dev-i386 lib32readline6-dev libssl-dev libcurl4-gnutls-dev libexpat1-dev gettext unzip build-essential checkinstall libffi-dev curl libz-dev openssh-client pkg-config autoconf 13 | RUN apt-get install -y -qq ccache 14 | 15 | # Install cmake 3.31 16 | RUN mkdir /cmake_3_31 && \ 17 | cd /cmake_3_31 && \ 18 | wget https://github.com/Kitware/CMake/releases/download/v3.31.3/cmake-3.31.3-linux-x86_64.sh && \ 19 | chmod +x cmake-3.31.3-linux-x86_64.sh && \ 20 | ./cmake-3.31.3-linux-x86_64.sh --skip-license --prefix=/usr/local && \ 21 | cmake --version 22 | 23 | # Install GIT 24 | RUN wget https://github.com/git/git/archive/refs/tags/v2.25.0.tar.gz && \ 25 | tar xvf v2.25.0.tar.gz && \ 26 | cd git-2.25.0 && \ 27 | make && \ 28 | make prefix=/usr install 29 | 30 | # Setup VCPKG n a mounted volume TODO: figure out how to cache this 31 | ARG vcpkg_url 32 | ARG vcpkg_commit 33 | RUN mkdir /vcpkg && \ 34 | cd /vcpkg && \ 35 | git init && \ 36 | git remote add origin $vcpkg_url && \ 37 | git fetch origin $vcpkg_commit && \ 38 | git checkout $vcpkg_commit && \ 39 | ./bootstrap-vcpkg.sh 40 | ENV VCPKG_ROOT=/vcpkg 41 | ENV VCPKG_TOOLCHAIN_PATH=/vcpkg/scripts/buildsystems/vcpkg.cmake 42 | 43 | # Common environment variables 44 | ENV GEN=ninja 45 | 46 | # Specify where we expect the extension to be mounted and use that as working dir 47 | VOLUME /duckdb_build_dir 48 | WORKDIR /duckdb_build_dir 49 | 50 | # Mount for ccache to allow restoring ccache in GH actions 51 | VOLUME /ccache_dir 52 | ENV CCACHE_DIR=/ccache_dir 53 | ENV CCACHE_COMPRESS=TRUE 54 | ENV CCACHE_COMPRESSLEVEL=6 55 | ENV CCACHE_MAXSIZE=400M 56 | 57 | ### 58 | # Conditionally configure some extra dependencies 59 | ### 60 | # a `;` separated list of extra toolchains to install (passed in like this to makes things easier through GitHub Actions) 61 | # Note that it should start and end with a `;` e.g. `;rust;parser_tools;` 62 | ARG extra_toolchains 63 | 64 | # NOTE: the weird case conditionals are because of bash limitations in the ubuntu image used (see https://stackoverflow.com/questions/229551/how-to-check-if-a-string-contains-a-substring-in-bash) 65 | 66 | # Install Parser tools 67 | RUN case "$extra_toolchains" in \ 68 | *\;parser_tools\;*) \ 69 | apt-get install -y -qq bison flex \ 70 | ;; \ 71 | esac 72 | 73 | # Install Fortran 74 | RUN case "$extra_toolchains" in \ 75 | *\;fortran\;*) \ 76 | apt-get install -y -qq gfortran gfortran-aarch64-linux-gnu \ 77 | ;; \ 78 | esac 79 | 80 | # Configure Rust 81 | RUN case "$extra_toolchains" in \ 82 | *\;rust\;*) \ 83 | curl https://sh.rustup.rs -sSf | bash -s -- -y \ 84 | ;; \ 85 | esac 86 | ENV PATH="/root/.cargo/bin:${PATH}" 87 | 88 | # Configure go 89 | RUN case "$extra_toolchains" in \ 90 | *\;go\;*) \ 91 | apt-get install -y -qq golang-go \ 92 | ;; \ 93 | esac 94 | ENV PATH="/usr/local/go/bin:${PATH}" 95 | 96 | # Install Python3 97 | RUN case "$extra_toolchains" in \ 98 | *\;python3\;*) \ 99 | apt-get install -y -qq python3 \ 100 | ;; \ 101 | esac 102 | -------------------------------------------------------------------------------- /.github/docker/linux_amd64_gcc4/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux2014_x86_64:latest 2 | 3 | RUN yum install -y epel-release -y pkgconfig 4 | 5 | # TODO do we need this? 6 | RUN git config --global --add safe.directory '*' 7 | 8 | # TODO do we need these? 9 | RUN yum install -y gcc-c++ 10 | RUN yum install -y nodejs 11 | 12 | # Setup the basic necessities 13 | RUN yum install -y curl zip unzip tar autoconf 14 | RUN yum install -y ninja-build 15 | RUN yum install -y perl-IPC-Cmd 16 | RUN yum install -y ccache 17 | RUN yum install -y java-11-openjdk-devel maven 18 | RUN yum install -y libgcc*i686 libstdc++*i686 glibc*i686 libgfortran*i686 19 | RUN yum install -y awscli 20 | 21 | # Setup VCPKG n a mounted volume TODO: figure out how to cache this 22 | ARG vcpkg_url 23 | ARG vcpkg_commit 24 | RUN mkdir /vcpkg && \ 25 | cd /vcpkg && \ 26 | git init && \ 27 | git remote add origin $vcpkg_url && \ 28 | git fetch origin $vcpkg_commit && \ 29 | git checkout $vcpkg_commit && \ 30 | ./bootstrap-vcpkg.sh 31 | ENV VCPKG_ROOT=/vcpkg 32 | ENV VCPKG_TOOLCHAIN_PATH=/vcpkg/scripts/buildsystems/vcpkg.cmake 33 | 34 | # Common environment variables 35 | ENV GEN=ninja 36 | 37 | # Specify where we expect the extension to be mounted and use that as working dir 38 | VOLUME /duckdb_build_dir 39 | WORKDIR /duckdb_build_dir 40 | 41 | # Mount for ccache to allow restoring ccache in GH actions 42 | VOLUME /ccache_dir 43 | ENV CCACHE_DIR=/ccache_dir 44 | ENV CCACHE_COMPRESS=TRUE 45 | ENV CCACHE_COMPRESSLEVEL=6 46 | ENV CCACHE_MAXSIZE=400M 47 | 48 | ### 49 | # Conditionally configure some extra dependencies 50 | ### 51 | # a `;` separated list of extra toolchains to install (passed in like this to makes things easier through GitHub Actions) 52 | # Note that it should start and end with a `;` e.g. `;rust;parser_tools;` 53 | ARG extra_toolchains 54 | 55 | RUN echo "$extra_toolchains" > ~/extra_toolchains.txt 56 | 57 | # Install Parser tools 58 | RUN if [[ $extra_toolchains == *";parser_tools;"* ]]; then \ 59 | yum install -y bison flex;\ 60 | fi 61 | 62 | # Configure Rust 63 | RUN if [[ $extra_toolchains == *";rust;"* ]] || [ "$enable_rust" = "1" ]; then \ 64 | curl https://sh.rustup.rs -sSf | bash -s -- -y ;\ 65 | fi 66 | ENV PATH="/root/.cargo/bin:${PATH}" 67 | 68 | # Configure go 69 | RUN if [[ $extra_toolchains == *";go;"* ]]; then \ 70 | yum install -y wget; \ 71 | wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz; \ 72 | tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz; \ 73 | fi 74 | ENV PATH="/usr/local/go/bin:${PATH}" 75 | 76 | # Install Python3 77 | RUN case "$extra_toolchains" in \ 78 | *\;python3\;*) \ 79 | yum install -y python3; \ 80 | ;; \ 81 | esac 82 | -------------------------------------------------------------------------------- /.github/docker/linux_amd64_musl/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | ### 4 | # Base image setup 5 | ### 6 | 7 | # Setup the basic necessities 8 | RUN apk update --y -qq 9 | RUN apk add -qq ccache cmake git ninja ninja-build clang19 gcc libssl3 wget bash zip gettext unzip build-base curl make libffi-dev zlib openssh autoconf linux-headers 10 | 11 | # Setup VCPKG n a mounted volume TODO: figure out how to cache this 12 | ARG vcpkg_url 13 | ARG vcpkg_commit 14 | RUN mkdir /vcpkg && \ 15 | cd /vcpkg && \ 16 | git init && \ 17 | git remote add origin $vcpkg_url && \ 18 | git fetch origin $vcpkg_commit && \ 19 | git checkout $vcpkg_commit && \ 20 | VCPKG_FORCE_SYSTEM_BINARIES=1 ./bootstrap-vcpkg.sh 21 | ENV VCPKG_ROOT=/vcpkg 22 | ENV VCPKG_TOOLCHAIN_PATH=/vcpkg/scripts/buildsystems/vcpkg.cmake 23 | 24 | # Common environment variables 25 | ENV GEN=ninja 26 | ENV DUCKDB_PLATFORM=linux_amd64_musl 27 | ENV VCPKG_FORCE_SYSTEM_BINARIES=1 28 | 29 | # Specify where we expect the extension to be mounted and use that as working dir 30 | VOLUME /duckdb_build_dir 31 | WORKDIR /duckdb_build_dir 32 | 33 | # Mount for ccache to allow restoring ccache in GH actions 34 | VOLUME /ccache_dir 35 | ENV CCACHE_DIR=/ccache_dir 36 | ENV CCACHE_COMPRESS=TRUE 37 | ENV CCACHE_COMPRESSLEVEL=6 38 | ENV CCACHE_MAXSIZE=400M 39 | 40 | ### 41 | # Conditionally configure some extra dependencies 42 | ### 43 | # a `;` separated list of extra toolchains to install (passed in like this to makes things easier through GitHub Actions) 44 | # Note that it should start and end with a `;` e.g. `;rust;parser_tools;` 45 | ARG extra_toolchains 46 | 47 | # NOTE: the weird case conditionals are because of bash limitations in the ubuntu image used (see https://stackoverflow.com/questions/229551/how-to-check-if-a-string-contains-a-substring-in-bash) 48 | 49 | # Install Parser tools 50 | RUN case "$extra_toolchains" in \ 51 | *\;parser_tools\;*) \ 52 | apk add -qq bison flex \ 53 | ;; \ 54 | esac 55 | 56 | # Install Fortran 57 | RUN case "$extra_toolchains" in \ 58 | *\;fortran\;*) \ 59 | apk add -qq gfortran \ 60 | ;; \ 61 | esac 62 | 63 | # Configure Rust 64 | RUN case "$extra_toolchains" in \ 65 | *\;rust\;*) \ 66 | curl https://sh.rustup.rs -sSf | bash -s -- -y \ 67 | ;; \ 68 | esac 69 | ENV PATH="/root/.cargo/bin:${PATH}" 70 | 71 | # Configure go 72 | RUN case "$extra_toolchains" in \ 73 | *\;go\;*) \ 74 | apk add -qq go \ 75 | ;; \ 76 | esac 77 | ENV PATH="/usr/local/go/bin:${PATH}" 78 | 79 | # Install Python3 80 | RUN case "$extra_toolchains" in \ 81 | *\;python3\;*) \ 82 | apk add -qq python3 \ 83 | ;; \ 84 | esac 85 | -------------------------------------------------------------------------------- /.github/docker/linux_arm64/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | # Setup the basic necessities 4 | RUN apt-get update -y -qq 5 | RUN apt-get install -y -qq software-properties-common 6 | RUN apt-get install -y -qq --fix-missing ninja-build make gcc-multilib g++-multilib libssl-dev wget openjdk-8-jdk zip maven unixodbc-dev libc6-dev-i386 lib32readline6-dev libssl-dev libcurl4-gnutls-dev libexpat1-dev gettext unzip build-essential checkinstall libffi-dev curl libz-dev openssh-client pkg-config 7 | RUN apt-get install -y -qq ccache autoconf 8 | 9 | # Setup cross compiler because GH actions does not have open source arm runners yet 10 | RUN apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 11 | 12 | # Install cmake 3.31 13 | RUN mkdir /cmake_3_31 && \ 14 | cd /cmake_3_31 && \ 15 | wget https://github.com/Kitware/CMake/releases/download/v3.31.3/cmake-3.31.3-linux-x86_64.sh && \ 16 | chmod +x cmake-3.31.3-linux-x86_64.sh && \ 17 | ./cmake-3.31.3-linux-x86_64.sh --skip-license --prefix=/usr/local && \ 18 | cmake --version 19 | 20 | # Install GIT 21 | RUN wget https://github.com/git/git/archive/refs/tags/v2.25.0.tar.gz && \ 22 | tar xvf v2.25.0.tar.gz && \ 23 | cd git-2.25.0 && \ 24 | make && \ 25 | make prefix=/usr install 26 | 27 | # Setup VCPKG n a mounted volume TODO: figure out how to cache this 28 | ARG vcpkg_url 29 | ARG vcpkg_commit 30 | RUN mkdir /vcpkg && \ 31 | cd /vcpkg && \ 32 | git init && \ 33 | git remote add origin $vcpkg_url && \ 34 | git fetch origin $vcpkg_commit && \ 35 | git checkout $vcpkg_commit && \ 36 | ./bootstrap-vcpkg.sh 37 | ENV VCPKG_ROOT=/vcpkg 38 | ENV VCPKG_TOOLCHAIN_PATH=/vcpkg/scripts/buildsystems/vcpkg.cmake 39 | 40 | # Common environment variables 41 | ENV GEN=ninja 42 | 43 | # Specify where we expect the extension to be mounted and use that as working dir 44 | VOLUME /duckdb_build_dir 45 | WORKDIR /duckdb_build_dir 46 | 47 | # Mount for ccache to allow restoring ccache in GH actions 48 | VOLUME /ccache_dir 49 | ENV CCACHE_DIR=/ccache_dir 50 | ENV CCACHE_COMPRESS=TRUE 51 | ENV CCACHE_COMPRESSLEVEL=6 52 | ENV CCACHE_MAXSIZE=400M 53 | 54 | ### 55 | # Conditionally configure some extra dependencies 56 | ### 57 | # a `;` separated list of extra toolchains to install (passed in like this to makes things easier through GitHub Actions) 58 | # Note that it should start and end with a `;` e.g. `;rust;parser_tools;` 59 | ARG extra_toolchains 60 | 61 | # NOTE: the weird case conditionals are because of bash limitations in the ubuntu image used (see https://stackoverflow.com/questions/229551/how-to-check-if-a-string-contains-a-substring-in-bash) 62 | 63 | # Install Parser tools 64 | RUN case "$extra_toolchains" in \ 65 | *\;parser_tools\;*) \ 66 | apt-get install -y -qq bison flex \ 67 | ;; \ 68 | esac 69 | 70 | # Install Fortran 71 | RUN case "$extra_toolchains" in \ 72 | *\;fortran\;*) \ 73 | apt-get install -y -qq gfortran gfortran-aarch64-linux-gnu \ 74 | ;; \ 75 | esac 76 | 77 | # Configure Rust 78 | ENV PATH="/root/.cargo/bin:${PATH}" 79 | RUN case "$extra_toolchains" in \ 80 | *\;rust\;*) \ 81 | curl https://sh.rustup.rs -sSf | bash -s -- -y; \ 82 | rustup target add aarch64-unknown-linux-gnu \ 83 | ;; \ 84 | esac 85 | 86 | # Configure go 87 | RUN case "$extra_toolchains" in \ 88 | *\;go\;*) \ 89 | apt-get install -y -qq golang-go \ 90 | ;; \ 91 | esac 92 | 93 | # Install Python3 94 | RUN case "$extra_toolchains" in \ 95 | *\;python3\;*) \ 96 | apt-get install -y -qq python3 \ 97 | ;; \ 98 | esac 99 | -------------------------------------------------------------------------------- /.github/workflows/MainDistributionPipeline.yml: -------------------------------------------------------------------------------- 1 | name: Main Extension Distribution Pipeline 2 | on: 3 | push: 4 | paths-ignore: 5 | - '**/README.md' 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | permissions: 10 | id-token: write # This is required for requesting the JWT 11 | contents: read # This is required for actions/checkout 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }}-${{ github.ref != 'refs/heads/main' || github.sha }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | duckdb-stable-build-v130: 19 | name: Build v.1.3.0 extension binaries 20 | uses: ./.github/workflows/_extension_build.yml 21 | secrets: inherit 22 | with: 23 | duckdb_version: v1.3.0 24 | extension_name: erpl 25 | exclude_archs: linux_arm64;windows_amd64_rtools;wasm_mvp;wasm_eh;wasm_threads; 26 | vcpkg_commit: 9edb1b8e590cc086563301d735cae4b6e732d2d2 27 | 28 | duckdb-stable-deploy-v130: 29 | name: Deploy extension binaries 30 | needs: duckdb-stable-build-v130 31 | uses: ./.github/workflows/_extension_deploy.yml 32 | secrets: inherit 33 | with: 34 | duckdb_version: v1.3.0 35 | extension_name: erpl 36 | exclude_archs: linux_arm64;windows_amd64_rtools;wasm_mvp;wasm_eh;wasm_threads; 37 | deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master' }} 38 | deploy_versioned: ${{ !(startsWith(github.ref, 'refs/tags/v')) && !(github.ref == 'refs/heads/master') }} 39 | 40 | duckdb-stable-build-v122: 41 | name: Build v.1.2.2 extension binaries 42 | uses: ./.github/workflows/_extension_build.yml 43 | secrets: inherit 44 | with: 45 | duckdb_version: v1.2.2 46 | extension_name: erpl 47 | exclude_archs: linux_arm64;windows_amd64_rtools;wasm_mvp;wasm_eh;wasm_threads; 48 | vcpkg_commit: 9edb1b8e590cc086563301d735cae4b6e732d2d2 49 | 50 | duckdb-stable-deploy-v122: 51 | name: Deploy extension binaries 52 | needs: duckdb-stable-build-v122 53 | uses: ./.github/workflows/_extension_deploy.yml 54 | secrets: inherit 55 | with: 56 | duckdb_version: v1.2.2 57 | extension_name: erpl 58 | exclude_archs: linux_arm64;windows_amd64_rtools;wasm_mvp;wasm_eh;wasm_threads; 59 | deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master' }} 60 | deploy_versioned: ${{ !(startsWith(github.ref, 'refs/tags/v')) && !(github.ref == 'refs/heads/master') }} -------------------------------------------------------------------------------- /.github/workflows/_extension_deploy.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Reusable workflow that deploys the artifacts produced by github.com/duckdb/duckdb/.github/workflows/_extension_distribution.yml 3 | # 4 | # note: this workflow needs to be located in the extension repository, as it requires secrets to be passed to the 5 | # deploy script. However, it should generally not be necessary to modify this workflow in your extension repository, as 6 | # this workflow can be configured to use a custom deploy script. 7 | 8 | 9 | name: Extension Deployment 10 | on: 11 | workflow_call: 12 | inputs: 13 | # The name of the extension 14 | extension_name: 15 | required: true 16 | type: string 17 | # DuckDB version to build against 18 | duckdb_version: 19 | required: true 20 | type: string 21 | # ';' separated list of architectures to exclude, for example: 'linux_amd64;osx_arm64' 22 | exclude_archs: 23 | required: false 24 | type: string 25 | default: "" 26 | # Whether to upload this deployment as the latest. This may overwrite a previous deployment. 27 | deploy_latest: 28 | required: false 29 | type: boolean 30 | default: false 31 | # Whether to upload this deployment under a versioned path. These will not be deleted automatically 32 | deploy_versioned: 33 | required: false 34 | type: boolean 35 | default: false 36 | # Postfix added to artifact names. Can be used to guarantee unique names when this workflow is called multiple times 37 | artifact_postfix: 38 | required: false 39 | type: string 40 | default: "" 41 | # Override the default deploy script with a custom script 42 | deploy_script: 43 | required: false 44 | type: string 45 | default: "./scripts/extension-upload.sh" 46 | # Override the default matrix parse script with a custom script 47 | matrix_parse_script: 48 | required: false 49 | type: string 50 | default: "./duckdb/scripts/modify_distribution_matrix.py" 51 | 52 | jobs: 53 | generate_matrix: 54 | name: Generate matrix 55 | runs-on: ubuntu-latest 56 | outputs: 57 | deploy_matrix: ${{ steps.parse-matrices.outputs.deploy_matrix }} 58 | steps: 59 | - name: Generate GitHub token 60 | id: generate_token 61 | uses: actions/create-github-app-token@v1 62 | with: 63 | app-id: ${{ secrets.REPO_READONLY_GITHUB_APP_ID }} 64 | private-key: ${{ secrets.REPO_READONLY_GITHUB_APP_KEY }} 65 | repositories: "erpl,erpl-bics,erpl-odp" 66 | 67 | - uses: actions/checkout@v3 68 | with: 69 | fetch-depth: 0 70 | submodules: 'true' 71 | token: ${{ steps.generate_token.outputs.token }} 72 | 73 | - name: Checkout DuckDB to version 74 | run: | 75 | cd duckdb 76 | git checkout ${{ inputs.duckdb_version }} 77 | 78 | - id: parse-matrices 79 | run: | 80 | python3 ${{ inputs.matrix_parse_script }} --input ./.github/config/distribution_matrix.json --deploy_matrix --output deploy_matrix.json --exclude "${{ inputs.exclude_archs }}" --pretty 81 | deploy_matrix="`cat deploy_matrix.json`" 82 | echo deploy_matrix=$deploy_matrix >> $GITHUB_OUTPUT 83 | echo `cat $GITHUB_OUTPUT` 84 | 85 | deploy: 86 | name: Deploy 87 | runs-on: ubuntu-latest 88 | needs: generate_matrix 89 | if: ${{ needs.generate_matrix.outputs.deploy_matrix != '{}' && needs.generate_matrix.outputs.deploy_matrix != '' }} 90 | strategy: 91 | matrix: ${{fromJson(needs.generate_matrix.outputs.deploy_matrix)}} 92 | 93 | steps: 94 | - name: Generate GitHub token 95 | id: generate_token 96 | uses: actions/create-github-app-token@v1 97 | with: 98 | app-id: ${{ secrets.REPO_READONLY_GITHUB_APP_ID }} 99 | private-key: ${{ secrets.REPO_READONLY_GITHUB_APP_KEY }} 100 | repositories: "erpl,erpl-bics,erpl-odp" 101 | 102 | - uses: actions/checkout@v3 103 | with: 104 | fetch-depth: 0 105 | submodules: 'true' 106 | token: ${{ steps.generate_token.outputs.token }} 107 | 108 | - name: Configure AWS Credentials 109 | uses: aws-actions/configure-aws-credentials@v1 110 | with: 111 | role-to-assume: arn:aws:iam::331993160594:role/ErplGithubOicdRole 112 | role-session-name: ErplGithubOicdSession 113 | aws-region: eu-west-1 114 | 115 | - name: Checkout DuckDB to version 116 | run: | 117 | cd duckdb 118 | git checkout ${{ inputs.duckdb_version }} 119 | 120 | - uses: actions/download-artifact@v4 121 | with: 122 | name: ${{ inputs.extension_name }}-${{ inputs.duckdb_version }}-extension-${{matrix.duckdb_arch}}${{inputs.artifact_postfix}} 123 | path: | 124 | /tmp/extension 125 | 126 | - name: Deploy 127 | shell: bash 128 | env: 129 | BUCKET_NAME: ${{ vars.DEPLOY_S3_BUCKET }} 130 | DUCKDB_EXTENSION_SIGNING_PK: ${{ secrets.DUCKDB_EXTENSION_SIGNING_PK }} 131 | run: | 132 | pwd 133 | python3 -m pip install pip awscli 134 | git config --global --add safe.directory '*' 135 | cd duckdb 136 | git fetch --tags 137 | export DUCKDB_VERSION=`git tag --points-at HEAD` 138 | export DUCKDB_VERSION=${DUCKDB_VERSION:=`git log -1 --format=%h`} 139 | cd .. 140 | git fetch --tags 141 | export EXT_VERSION=`git tag --points-at HEAD` 142 | export EXT_VERSION=${EXT_VERSION:=`git log -1 --format=%h`} 143 | ${{ inputs.deploy_script }} ${{ inputs.extension_name }} $EXT_VERSION $DUCKDB_VERSION ${{ matrix.duckdb_arch }} $BUCKET_NAME ${{inputs.deploy_latest || 'true' && 'false'}} ${{inputs.deploy_versioned || 'true' && 'false'}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | node_modules/ 7 | dist/ 8 | build/ 9 | duckdb_unittest_tempdir/ 10 | nwrfcsdk/ 11 | .cache/ 12 | *.egg-info/ 13 | *.whl 14 | packages/python-erpl/erpl/ 15 | 16 | .ipynb_checkpoints/ 17 | 18 | # Compiled Java class files 19 | *.class 20 | 21 | # Compiled Python bytecode 22 | *.py[cod] 23 | 24 | # Log files 25 | *.log 26 | 27 | # Package files 28 | *.jar 29 | 30 | # Maven 31 | target/ 32 | dist/ 33 | 34 | # JetBrains IDE 35 | .idea/ 36 | 37 | # Unit test reports 38 | TEST*.xml 39 | 40 | # Generated by MacOS 41 | .DS_Store 42 | 43 | # Generated by Windows 44 | Thumbs.db 45 | 46 | # Applications 47 | *.app 48 | *.exe 49 | *.war 50 | 51 | # Large media files 52 | *.mp4 53 | *.tiff 54 | *.avi 55 | *.flv 56 | *.mov 57 | *.wmv 58 | 59 | # Smoke test 60 | smoke/* 61 | 62 | # Disabled tests 63 | **/test/disabled/* 64 | 65 | # SAP Trace files 66 | dev_rfc.log 67 | rfc*.trc 68 | cpic*.trc 69 | 70 | # SAP License files 71 | A4H*.txt 72 | 73 | # Venv 74 | .pixi 75 | python-env/* 76 | 77 | # Doc Traces 78 | **/doc/**/*.xml 79 | **/doc/**/*.json 80 | 81 | # Test builds of extension 82 | *.duckdb_extension 83 | 84 | # Experiments 85 | /examples/experiments/* 86 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "duckdb"] 2 | path = duckdb 3 | url = https://github.com/duckdb/duckdb.git 4 | 5 | [submodule "bics"] 6 | path = bics 7 | url = git@github.com:DataZooDE/erpl-bics.git 8 | 9 | [submodule "odp"] 10 | path = odp 11 | url = git@github.com:DataZooDE/erpl-odp.git 12 | 13 | [submodule "vcpkg"] 14 | path = vcpkg 15 | url = https://github.com/microsoft/vcpkg 16 | 17 | [submodule "extension-ci-tools"] 18 | path = extension-ci-tools 19 | url = git@github.com:duckdb/extension-ci-tools.git 20 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.sourceDirectory": "${workspaceFolder}/duckdb", 3 | "cmake.buildDirectory": "${workspaceFolder}/build/debug/", 4 | "cmake.configureSettings": { 5 | "CMAKE_TOOLCHAIN_FILE": "${workspaceFolder}/vcpkg/scripts/buildsystems/vcpkg.cmake", 6 | "VCPKG_MANIFEST_DIR": "${workspaceFolder}/rfc/", 7 | "VCPKG_BUILD": "1", 8 | "GEN": "ninja", 9 | }, 10 | 11 | "cmake.configureArgs": [ 12 | "-DDUCKDB_EXTENSION_CONFIGS=${workspaceFolder}/extension_config.cmake", 13 | "-DEXTENSION_STATIC_BUILD=1", 14 | "-DBUILD_PARQUET_EXTENSION=1", 15 | "-DCMAKE_BUILD_TYPE=Debug", 16 | "-Wno-unused-variable" 17 | ], 18 | "files.associations": { 19 | "cctype": "cpp", 20 | "clocale": "cpp", 21 | "cmath": "cpp", 22 | "csetjmp": "cpp", 23 | "csignal": "cpp", 24 | "cstdarg": "cpp", 25 | "cstddef": "cpp", 26 | "cstdio": "cpp", 27 | "cstdlib": "cpp", 28 | "cstring": "cpp", 29 | "ctime": "cpp", 30 | "cwchar": "cpp", 31 | "cwctype": "cpp", 32 | "any": "cpp", 33 | "array": "cpp", 34 | "atomic": "cpp", 35 | "hash_map": "cpp", 36 | "strstream": "cpp", 37 | "bit": "cpp", 38 | "*.tcc": "cpp", 39 | "bitset": "cpp", 40 | "cfenv": "cpp", 41 | "charconv": "cpp", 42 | "chrono": "cpp", 43 | "cinttypes": "cpp", 44 | "codecvt": "cpp", 45 | "compare": "cpp", 46 | "complex": "cpp", 47 | "concepts": "cpp", 48 | "condition_variable": "cpp", 49 | "coroutine": "cpp", 50 | "cstdint": "cpp", 51 | "deque": "cpp", 52 | "forward_list": "cpp", 53 | "list": "cpp", 54 | "map": "cpp", 55 | "set": "cpp", 56 | "string": "cpp", 57 | "unordered_map": "cpp", 58 | "unordered_set": "cpp", 59 | "vector": "cpp", 60 | "exception": "cpp", 61 | "algorithm": "cpp", 62 | "functional": "cpp", 63 | "iterator": "cpp", 64 | "memory": "cpp", 65 | "memory_resource": "cpp", 66 | "numeric": "cpp", 67 | "optional": "cpp", 68 | "random": "cpp", 69 | "ratio": "cpp", 70 | "regex": "cpp", 71 | "source_location": "cpp", 72 | "string_view": "cpp", 73 | "system_error": "cpp", 74 | "tuple": "cpp", 75 | "type_traits": "cpp", 76 | "utility": "cpp", 77 | "format": "cpp", 78 | "fstream": "cpp", 79 | "future": "cpp", 80 | "initializer_list": "cpp", 81 | "iomanip": "cpp", 82 | "iosfwd": "cpp", 83 | "iostream": "cpp", 84 | "istream": "cpp", 85 | "limits": "cpp", 86 | "mutex": "cpp", 87 | "new": "cpp", 88 | "numbers": "cpp", 89 | "ostream": "cpp", 90 | "semaphore": "cpp", 91 | "span": "cpp", 92 | "sstream": "cpp", 93 | "stdexcept": "cpp", 94 | "stdfloat": "cpp", 95 | "stop_token": "cpp", 96 | "streambuf": "cpp", 97 | "thread": "cpp", 98 | "typeindex": "cpp", 99 | "typeinfo": "cpp", 100 | "valarray": "cpp", 101 | "variant": "cpp", 102 | "*.ipp": "cpp", 103 | "shared_mutex": "cpp", 104 | "ios": "cpp", 105 | "locale": "cpp", 106 | "queue": "cpp", 107 | "ranges": "cpp", 108 | "stack": "cpp", 109 | "xfacet": "cpp", 110 | "xhash": "cpp", 111 | "xiosbase": "cpp", 112 | "xlocale": "cpp", 113 | "xlocbuf": "cpp", 114 | "xlocinfo": "cpp", 115 | "xlocmes": "cpp", 116 | "xlocmon": "cpp", 117 | "xlocnum": "cpp", 118 | "xloctime": "cpp", 119 | "xmemory": "cpp", 120 | "xstring": "cpp", 121 | "xtr1common": "cpp", 122 | "xtree": "cpp", 123 | "xutility": "cpp", 124 | "filesystem": "cpp", 125 | "hash_set": "cpp", 126 | "text_encoding": "cpp" 127 | }, 128 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 129 | "cmake.autoSelectActiveFolder": true, 130 | "cmake.configureOnOpen": true 131 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cmake", 6 | "label": "CMake: build", 7 | "command": "build", 8 | "targets": [ 9 | "all" 10 | ], 11 | "group": "build", 12 | "problemMatcher": [], 13 | "detail": "CMake template build task", 14 | "options": { 15 | "env": { 16 | "VCPKG_TOOLCHAIN_PATH": "${workspaceFolder}/vcpkg/scripts/buildsystems/vcpkg.cmake", 17 | "CMAKE_TOOLCHAIN_FILE": "${workspaceFolder}/vcpkg/scripts/buildsystems/vcpkg.cmake" 18 | } 19 | } 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ## Build on windows 2 | 3 | Creating a DuckDB extension on Windows using MinGW on MSYS2 requires several steps, including setting up your environment, obtaining the necessary source code and dependencies, and compiling the extension. Here’s a detailed guide: 4 | 5 | Sources 6 | 7 | - https://duckdb.org/dev/building.html 8 | - https://github.com/duckdb/duckdb/blob/v0.8.1/.github/workflows/Windows.yml#L158 9 | 10 | 11 | Set environmet variable 12 | 13 | 1. **Install MSYS2 and MinGW:** 14 | 15 | - Download MSYS2 installer from the [official website](https://www.msys2.org/). 16 | - Run the installer and follow the instructions to install MSYS2 on your machine. 17 | - Open MSYS2 terminal and update the package database and core system packages with: `pacman -Syu`. 18 | - Install MinGW-w64 by running: `pacman -S git mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja`. 19 | 20 | 2. **Setup Environment:** 21 | 22 | - Close and reopen MSYS2 terminal. 23 | - Add MinGW to your system PATH: `export PATH="/mingw64/bin:$PATH"`. 24 | 25 | 3. **Download Extension Source Code:** 26 | 27 | - Clone the ERPL repository: `git clone https://github.com/duckdb/duckdb.git`. 28 | - Fetch the `duckdb` submodule after clone: `git submodule update --init --recursive`. 29 | (To make this work under windows I had to first `git config --global core.sshCommand 'C:/Windows/System32/OpenSSH/ssh.exe'`) 30 | - Navigate to the duckdb directory: `cd duckdb` and checkout the `v0.9.0` tag: `git checkout v0.9.0`. 31 | 32 | 4. **Download sapnwrfc Library:** 33 | 34 | - Download the sapnwrfc library from the [official website](https://support.sap.com/en/product/connectors/nwrfcsdk.html) (or the Zip from our Google Drive). 35 | - Extract the downloaded archive and copy the `sapnwrfc` directory to the `sapnwrfc` directory in the erpl repository. 36 | 37 | 5. **Configure and Build:** 38 | 39 | - Run the following command in the root directory of the extension: `mingw32-make release`. 40 | 41 | 6. **Testing Your Extension:** 42 | 43 | - Create a directory (e.g. `test`). 44 | - Copy the following artifacts to that directory. 45 | - The built duckdb executable (e.g. `.\build\release\duckdb.exe`). 46 | - The built erpl extension (e.g. `.\build\release\extension\erpl\erpl.duckdb_extension`). 47 | - All `*.dll` files from `.\sapnwrfc\lib`. 48 | - Run duckdb with the following command: `.\duckdb.exe -unsigned`. 49 | - In the DuckDB shell_ 50 | - First install the erpl-extension with: `INSTALL 'erpl.duckdb_extension';`. 51 | - And then load the extension with: `LOAD 'erpl';`. 52 | 53 | 54 | ## 📦 Deploying the extension (TODO) 55 | To deploy the extension, simply run: 56 | ```sh ./scripts/extension_upload.sh``` 57 | 58 | 59 | ## Debuging SAP behavior 60 | 61 | ### Relevant transctions 62 | 63 | - [SM04](https://help.sap.com/doc/saphelp_nw74/7.4.16/en-us/02/3b3ad97b0b4526839377f4c2112f33/content.htm?no_cache=true) Login List 64 | - [SM50](https://help.sap.com/doc/saphelp_nw74/7.4.16/en-us/19/28d51a81c748b399947f3e354d2ffb/content.htm?no_cache=true) Work Process Overview 65 | - [ST02](https://help.sap.com/docs/ABAP_PLATFORM_BW4HANA/f146e75588924fa4987b6c8f1a7a8c7e/ce7a5224577d4713b1d695bdf9baf656.html) Memory Statistics / Tune Summary 66 | - [ST03](https://help.sap.com/saphelp_gbt10/helpdata/EN/2d/b8be3befaefc75e10000000a114084/frameset.htm) Workload and Performance Statistics 67 | - [SM21](https://help.sap.com/doc/saphelp_nw75/7.5.5/de-DE/b1/f4652c0f4d4e8fa04e165d161e386f/content.htm?no_cache=true) System Log 68 | - [DBACOCKPIT]() DBA Cockpit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved. 2 | “Business Source License” is a trademark of MariaDB Corporation Ab. 3 | 4 | Parameters: 5 | ----------- 6 | License Statement: Business Source License (BSL) Version 1.1 7 | Licensor: DataZoo GmbH 8 | Licensed Work: ERPL data connector 9 | Additional Use Grant: You may make production use of the Licensed Work. However, such use is restricted 10 | to the extent that it cannot be offered to third parties on a hosted or embedded basis. 11 | Change Date: The Change Date is exactly five years from the date the Licensed Work is first published. 12 | Change License: MPL 2.0 13 | 14 | 15 | Terms of Business Source License 1.1 16 | ------------------------------------ 17 | The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production 18 | use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited 19 | production use. Effective on the Change Date, or the fourth anniversary of the first publicly available 20 | distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor 21 | hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above 22 | terminate. If your use of the Licensed Work does not comply with the requirements currently in effect as described 23 | in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized 24 | resellers, or you must refrain from using the Licensed Work. All copies of the original and modified Licensed Work, 25 | and derivative works of the Licensed Work, are subject to this License. This License applies separately for each 26 | version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor. 27 | You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive 28 | the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License 29 | apply to your use of that work. Any use of the Licensed Work in violation of this License will automatically 30 | terminate your rights under this License for the current and all other versions of the Licensed Work. 31 | This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided 32 | that you may use a trademark or logo of Licensor as expressly required by this License). 33 | 34 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. 35 | LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) 36 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. 37 | 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | EXT_CONFIG=${PROJ_DIR}extension_config.cmake 4 | 5 | .PHONY: all clean format debug release pull update wasm_mvp wasm_eh wasm_threads 6 | 7 | all: release 8 | 9 | TEST_PATH="/test/unittest" 10 | DUCKDB_PATH="/duckdb" 11 | 12 | # For non-MinGW windows the path is slightly different 13 | ifeq ($(OS),Windows_NT) 14 | ifneq ($(CXX),g++) 15 | TEST_PATH="/test/Release/unittest.exe" 16 | DUCKDB_PATH="/Release/duckdb.exe" 17 | endif 18 | endif 19 | 20 | #### OSX config 21 | OSX_BUILD_FLAG= 22 | ifneq (${OSX_BUILD_ARCH}, "") 23 | OSX_BUILD_FLAG=-DOSX_BUILD_ARCH=${OSX_BUILD_ARCH} 24 | endif 25 | 26 | #### VCPKG config 27 | VCPKG_TOOLCHAIN_PATH?=$(PROJ_DIR)vcpkg/scripts/buildsystems/vcpkg.cmake 28 | ifneq ("${VCPKG_TOOLCHAIN_PATH}", "") 29 | TOOLCHAIN_FLAGS:=${TOOLCHAIN_FLAGS} -DVCPKG_MANIFEST_DIR='${PROJ_DIR}rfc' -DVCPKG_BUILD=1 -DCMAKE_TOOLCHAIN_FILE='${VCPKG_TOOLCHAIN_PATH}' 30 | endif 31 | ifneq ("${VCPKG_TARGET_TRIPLET}", "") 32 | TOOLCHAIN_FLAGS:=${TOOLCHAIN_FLAGS} -DVCPKG_TARGET_TRIPLET='${VCPKG_TARGET_TRIPLET}' -DVCPKG_HOST_TRIPLET='${VCPKG_TARGET_TRIPLET}' 33 | endif 34 | 35 | #### Enable Ninja as generator 36 | ifeq ($(GEN),ninja) 37 | GENERATOR=-G "Ninja" -DFORCE_COLORED_OUTPUT=1 38 | endif 39 | 40 | #### Configuration for the extensions 41 | EXTENSION_FLAGS=-DDUCKDB_EXTENSION_CONFIGS='${EXT_CONFIG}' 42 | BUILD_FLAGS=-DEXTENSION_STATIC_BUILD=1 $(EXTENSION_FLAGS) ${EXT_FLAGS} $(OSX_BUILD_FLAG) $(TOOLCHAIN_FLAGS) -DDUCKDB_EXPLICIT_PLATFORM='${DUCKDB_PLATFORM}' 43 | 44 | #### Main build 45 | debug: 46 | ifeq ($(OS),Windows_NT) 47 | powershell.exe -Command "if (!(Test-Path -Path ./build/debug/)) { New-Item -ItemType Directory -Path ./build/debug/ }" 48 | else 49 | mkdir -p ./build/debug/ 50 | endif 51 | cmake $(GENERATOR) $(BUILD_FLAGS) -DBUILD_UNITTESTS=ON -DCMAKE_BUILD_TYPE=Debug -S ./duckdb/ -B ./build/debug/ 52 | cmake --build ./build/debug/ --config Debug 53 | 54 | release: 55 | ifeq ($(OS),Windows_NT) 56 | powershell.exe -Command "if (!(Test-Path -Path ./build/release/)) { New-Item -ItemType Directory -Path ./build/release/$(EXTENSION_NAME) }" 57 | else 58 | mkdir -p ./build/release/ 59 | endif 60 | cmake $(GENERATOR) $(BUILD_FLAGS) -DBUILD_UNITTESTS=OFF -DCMAKE_BUILD_TYPE=Release -S ./duckdb/ -B ./build/release/ 61 | cmake --build ./build/release/ --config Release 62 | 63 | clean: 64 | ifeq ($(OS),Windows_NT) 65 | powershell.exe -Command "if (Test-Path -Path ./build) { Remove-Item -Recurse -Force ./build }" 66 | powershell.exe -Command "if (Test-Path -Path ./testext) { Remove-Item -Recurse -Force ./testext }" 67 | powershell.exe -Command "if (Test-Path -Path ./duckdb/build) { Remove-Item -Recurse -Force ./duckdb/build }" 68 | else 69 | rm -rf ./build 70 | rm -rf ./testext 71 | cd ./duckdb && make clean 72 | cd ./duckdb && make clean-python 73 | endif 74 | 75 | configure_ci: 76 | @echo "configure_ci step is skipped for this extension build..." 77 | -------------------------------------------------------------------------------- /examples/WEATHER.csv: -------------------------------------------------------------------------------- 1 | FLDATE,COUNTRY,CITY,TEMPERATURE,CONDITION 2 | 2017-10-01,US,SAN FRANCISCO,12.543148643609129,moderate rain 3 | 2016-11-17,DE,FRANKFURT,24.464855258654513,heavy snow 4 | 2017-02-01,US,NEW YORK,13.405611540164557,thunderstorm with rain 5 | 2017-04-24,SG,SINGAPORE,20.286671749030134,light rain and snow 6 | 2017-07-13,US,NEW YORK,24.742682230891393,light intensity shower rain 7 | 2016-11-18,DE,FRANKFURT,11.733208615093504,moderate rain 8 | 2017-07-15,US,NEW YORK,14.118568060856697,extreme rain 9 | 2017-04-25,SG,SINGAPORE,26.19413899620389,scattered clouds 10 | 2017-10-01,DE,FRANKFURT,12.519221253952216,rain 11 | 2017-05-28,DE,FRANKFURT,21.524925651596284,sleet 12 | 2017-07-16,DE,BERLIN,17.615076918643766,thunderstorm with drizzle 13 | 2017-12-23,DE,BERLIN,13.644540497901673,thunderstorm with heavy rain 14 | 2017-07-14,US,SAN FRANCISCO,21.45018502500226,rain and snow 15 | 2017-12-20,SG,SINGAPORE,14.23353373301801,light intensity drizzle 16 | 2016-11-17,JP,TOKYO,6.166133400021479,thunderstorm with drizzle 17 | 2016-11-15,US,SAN FRANCISCO,26.26881091367732,few clouds 18 | 2017-02-03,US,SAN FRANCISCO,18.096613101404806,heavy intensity rain 19 | 2017-12-20,US,SAN FRANCISCO,23.400640072310424,light intensity drizzle rain 20 | 2017-04-24,DE,FRANKFURT,29.89057166305808,rain 21 | 2017-02-03,IT,ROME,33.04367579169861,drizzle 22 | 2017-12-20,IT,ROME,29.747313054802525,moderate rain 23 | 2016-11-13,DE,FRANKFURT,24.612256017436316,heavy intensity shower rain 24 | 2017-02-01,DE,FRANKFURT,21.91248395335235,very heavy rain 25 | 2017-02-06,US,NEW YORK,13.835037761620054,thunderstorm with heavy rain 26 | 2017-12-23,US,NEW YORK,19.395040639626387,heavy intensity drizzle rain 27 | 2017-04-28,DE,FRANKFURT,22.261036252612904,thunderstorm with light rain 28 | 2017-07-11,US,NEW YORK,8.954636123888202,thunderstorm with light rain 29 | 2017-02-06,DE,BERLIN,19.703171982623978,sleet 30 | 2017-10-04,DE,BERLIN,13.190570952118005,heavy shower snow 31 | 2016-11-16,US,SAN FRANCISCO,19.82649139667873,rain 32 | 2016-11-15,JP,TOKYO,22.390178309008267,heavy snow 33 | 2017-02-03,JP,TOKYO,25.295142132545315,extreme rain 34 | 2016-11-17,SG,SINGAPORE,23.268776467553437,heavy shower snow 35 | 2017-02-05,SG,SINGAPORE,29.94836967002109,ragged shower rain 36 | 2017-10-03,SG,SINGAPORE,22.648910182787837,mist 37 | 2016-11-16,DE,FRANKFURT,23.567807061521375,rain and snow 38 | 2017-07-13,US,SAN FRANCISCO,9.38345054773764,heavy intensity drizzle 39 | 2017-02-03,DE,FRANKFURT,24.678749636110034,extreme rain 40 | 2017-07-13,DE,FRANKFURT,24.614004946301378,rain 41 | 2017-04-22,DE,FRANKFURT,13.12391877978088,drizzle 42 | 2017-07-11,DE,FRANKFURT,19.990348576461052,thunderstorm with light drizzle 43 | 2017-10-03,DE,FRANKFURT,24.596991840084016,light thunderstorm 44 | 2017-12-22,DE,FRANKFURT,20.03171001537388,rain and snow 45 | 2017-07-16,JP,TOKYO,18.582522898943147,freezing rain 46 | 2017-09-29,US,NEW YORK,14.362359855488647,shower rain and drizzle 47 | 2017-12-18,US,NEW YORK,21.94054001992479,heavy snow 48 | 2016-11-18,DE,BERLIN,20.793000874490225,thunderstorm with rain 49 | 2017-10-01,US,NEW YORK,15.811686565498539,ragged thunderstorm 50 | 2017-04-24,JP,TOKYO,21.76063748125239,light intensity drizzle rain 51 | 2017-02-05,JP,TOKYO,15.833734776839218,shower rain and drizzle 52 | 2017-10-03,JP,TOKYO,22.13698764087932,thunderstorm with light rain 53 | 2017-10-03,US,NEW YORK,30.602508330689272,very heavy rain 54 | 2017-12-21,SG,SINGAPORE,24.48952592530147,mist 55 | 2017-02-04,DE,FRANKFURT,26.03005597355562,drizzle 56 | 2017-04-24,US,SAN FRANCISCO,10.336087625014212,heavy intensity drizzle 57 | 2016-11-15,IT,ROME,18.618112523745637,extreme rain 58 | 2017-04-24,IT,ROME,15.697301829514693,light intensity drizzle rain 59 | 2017-09-29,DE,FRANKFURT,18.08882164182221,thunderstorm with heavy rain 60 | 2017-10-04,JP,TOKYO,25.521905359151358,rain and snow 61 | 2017-04-28,US,NEW YORK,18.662339384079374,snow 62 | 2017-04-27,DE,BERLIN,8.471221886286234,shower sleet 63 | 2017-05-28,US,SAN FRANCISCO,11.078502191938455,light intensity drizzle rain 64 | 2016-11-17,US,NEW YORK,15.133118972258917,heavy intensity drizzle rain 65 | 2017-02-05,US,NEW YORK,15.94533433418638,shower sleet 66 | 2017-12-22,US,NEW YORK,19.05287558502378,ragged shower rain 67 | 2017-12-22,SG,SINGAPORE,23.209105953368503,thunderstorm with drizzle 68 | 2017-10-02,SG,SINGAPORE,12.50545504524016,sleet 69 | 2016-11-15,DE,FRANKFURT,21.96839532368773,shower snow 70 | 2017-10-01,IT,ROME,28.577347566525116,drizzle rain 71 | 2017-04-26,DE,FRANKFURT,22.522675489485476,thunderstorm with heavy rain 72 | 2017-07-15,DE,FRANKFURT,16.582178011681,ragged shower rain 73 | 2017-07-16,US,NEW YORK,28.14806910991834,light rain and snow 74 | 2016-11-13,US,NEW YORK,18.49924799018678,heavy shower snow 75 | 2017-05-28,DE,BERLIN,19.081645624173056,light thunderstorm 76 | 2017-02-04,US,SAN FRANCISCO,23.759686877362416,thunderstorm with light rain 77 | 2017-04-25,US,SAN FRANCISCO,21.634168679571,thunderstorm with heavy rain 78 | 2017-02-03,SG,SINGAPORE,18.020857444259192,thunderstorm with light rain 79 | 2017-10-01,SG,SINGAPORE,24.456889436804463,moderate rain 80 | 2017-12-23,JP,TOKYO,24.852882100827564,thunderstorm with light rain 81 | 2017-12-20,JP,TOKYO,22.579783816388556,thunderstorm with heavy drizzle 82 | 2017-07-16,DE,FRANKFURT,20.699299146472224,shower sleet 83 | 2017-12-23,DE,FRANKFURT,27.646117115173983,shower drizzle 84 | 2017-02-04,SG,SINGAPORE,23.3843211874314,thunderstorm with rain 85 | 2017-07-14,DE,FRANKFURT,19.476092923821334,scattered clouds 86 | 2017-10-02,DE,FRANKFURT,16.72319389633152,light intensity shower rain 87 | 2017-07-13,IT,ROME,20.604574227055565,heavy intensity drizzle 88 | 2016-11-18,JP,TOKYO,10.131433863297255,light intensity drizzle 89 | 2017-02-06,JP,TOKYO,26.417546918293766,light snow 90 | 2017-04-27,JP,TOKYO,23.08044672603348,light snow 91 | 2016-11-18,US,NEW YORK,21.4625516952087,thunderstorm with heavy rain 92 | 2017-04-27,US,NEW YORK,19.928824564338232,rain and snow 93 | 2017-10-04,US,NEW YORK,27.056936340606747,thunderstorm with drizzle 94 | 2017-04-28,DE,BERLIN,29.032619496544072,few clouds 95 | 2017-10-02,US,SAN FRANCISCO,17.7987079387188,heavy intensity rain 96 | 2017-12-21,US,SAN FRANCISCO,20.813778132598205,freezing rain 97 | 2016-11-15,US,NEW YORK,18.37503050686735,sleet 98 | 2017-02-03,US,NEW YORK,19.95823008145357,snow 99 | 2017-12-20,US,NEW YORK,16.049183281378788,light intensity shower rain 100 | 2017-07-13,JP,TOKYO,23.909744597645034,heavy thunderstorm 101 | 2017-07-15,JP,TOKYO,15.351200201280983,heavy intensity shower rain 102 | 2017-02-06,DE,FRANKFURT,18.538124551547003,heavy thunderstorm 103 | 2017-10-04,DE,FRANKFURT,13.218037632174244,thunderstorm with heavy rain 104 | 2017-07-14,SG,SINGAPORE,19.516627053395265,heavy snow 105 | 2017-12-20,DE,FRANKFURT,20.837344520271078,heavy snow 106 | 2017-12-18,DE,FRANKFURT,15.447754783359247,heavy shower snow 107 | 2017-02-05,DE,FRANKFURT,20.050371067438537,ragged shower rain 108 | 2017-05-28,US,NEW YORK,23.12328862117925,heavy shower snow 109 | 2017-04-22,US,NEW YORK,24.147125043957363,light intensity drizzle rain 110 | 2016-11-15,SG,SINGAPORE,19.81083557250497,shower rain 111 | 2017-07-13,SG,SINGAPORE,12.516159403373514,thunderstorm with heavy drizzle 112 | 2017-04-28,US,SAN FRANCISCO,21.352221335351953,snow 113 | 2017-04-24,US,NEW YORK,23.512407050613984,light shower sleet 114 | 2017-10-01,JP,TOKYO,18.207942403797013,shower rain 115 | 2017-04-26,JP,TOKYO,27.129795775697126,light shower sleet 116 | 2017-12-22,JP,TOKYO,19.66528621122126,light intensity drizzle rain 117 | 2017-04-27,DE,FRANKFURT,18.195718432999918,thunderstorm with light rain 118 | 2017-04-26,US,NEW YORK,13.5757164079718,scattered clouds 119 | 2017-04-26,SG,SINGAPORE,24.074595540555645,heavy intensity shower rain 120 | 2017-07-15,SG,SINGAPORE,22.55999666808,ragged thunderstorm 121 | 2016-11-16,SG,SINGAPORE,17.55146103454959,heavy intensity drizzle rain 122 | 2017-04-25,DE,FRANKFURT,13.955999089555217,thunderstorm 123 | 2017-12-21,DE,FRANKFURT,24.762159697674388,thunderstorm with heavy drizzle 124 | -------------------------------------------------------------------------------- /examples/imgs/bics_rsa1_0D_NW_C01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/examples/imgs/bics_rsa1_0D_NW_C01.png -------------------------------------------------------------------------------- /examples/walkthrough.md: -------------------------------------------------------------------------------- 1 | # Authorization 2 | 3 | ```SQL 4 | 5 | CREATE OR REPLACE PERSISTENT SECRET abap_trial ( 6 | TYPE sap_rfc, 7 | ASHOST 'localhost', 8 | SYSNR '00', 9 | CLIENT '001', 10 | USER 'DEVELOPER', 11 | PASSWD 'ABAPtr2022#01', 12 | LANG 'EN' 13 | ); 14 | 15 | ``` 16 | 17 | # Test the connection 18 | 19 | ```SQL 20 | PRAGMA sap_rfc_ping(); 21 | ``` 22 | 23 | # Overview over functions 24 | 25 | ```SQL 26 | SELECT * FROM duckdb_functions() WHERE function_name LIKE '%sap%' ORDER BY 1; 27 | ``` 28 | 29 | 30 | # Tables & functions 31 | ``` 32 | SELECT * FROM sap_describe_fields('SFLIGHT'); 33 | ``` 34 | 35 | 36 | ```SQL 37 | SELECT * FROM sap_read_table('SFLIGHT'); 38 | ``` 39 | 40 | # BW 41 | 42 | ```SQL 43 | SELECT * FROM sap_bics_show(SEARCH='*NW Demo*', OBJ_TYPE='CUBE'); 44 | ``` 45 | 46 | ```SQL 47 | SELECT unnest(characteristics, recursive:=true) as ch_name FROM sap_bics_describe('0D_NW_C01'); 48 | ``` 49 | 50 | 51 | ```SQL 52 | SELECT * FROM sap_bics_begin('0D_NW_C01', ID='q1', RETURN='RESULT');1 53 | ``` 54 | 55 | 56 | ```SQL 57 | SELECT * FROM sap_bics_rows('q1', '0CALMONTH', OP='ADD', RETURN='RESULT'); 58 | ``` 59 | 60 | 61 | ```SQL 62 | SELECT * FROM sap_bics_show(SEARCH='*NW Demo*', OBJ_TYPE='INFOAREA'); 63 | ``` -------------------------------------------------------------------------------- /extension_config.cmake: -------------------------------------------------------------------------------- 1 | if(MSVC) 2 | add_compile_options(/bigobj) 3 | endif() 4 | 5 | get_filename_component(rfc_ext "${PROJECT_SOURCE_DIR}/../rfc" REALPATH) 6 | get_filename_component(bics_ext "${PROJECT_SOURCE_DIR}/../bics" REALPATH) 7 | get_filename_component(odp_ext "${PROJECT_SOURCE_DIR}/../odp" REALPATH) 8 | get_filename_component(erpl_ext "${PROJECT_SOURCE_DIR}/../trampoline" REALPATH) 9 | 10 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 11 | duckdb_extension_load(erpl_rfc 12 | SOURCE_DIR "${rfc_ext}" 13 | DONT_LINK 14 | ) 15 | if (EXISTS "${bics_ext}/CMakeLists.txt") 16 | duckdb_extension_load(erpl_bics 17 | SOURCE_DIR "${bics_ext}" 18 | DONT_LINK 19 | ) 20 | endif() 21 | if (EXISTS "${odp_ext}/CMakeLists.txt") 22 | duckdb_extension_load(erpl_odp 23 | SOURCE_DIR "${odp_ext}" 24 | DONT_LINK 25 | ) 26 | endif() 27 | duckdb_extension_load(erpl 28 | SOURCE_DIR "${erpl_ext}" 29 | DONT_LINK 30 | ) 31 | else() 32 | duckdb_extension_load(erpl_rfc 33 | SOURCE_DIR "${rfc_ext}" 34 | ) 35 | 36 | if (EXISTS "${bics_ext}/CMakeLists.txt") 37 | duckdb_extension_load(erpl_bics 38 | SOURCE_DIR "${bics_ext}" 39 | ) 40 | endif() 41 | 42 | if (EXISTS "${odp_ext}/CMakeLists.txt") 43 | duckdb_extension_load(erpl_odp 44 | SOURCE_DIR "${odp_ext}" 45 | ) 46 | endif() 47 | endif() 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /nwrfcsdk/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/nwrfcsdk/.empty -------------------------------------------------------------------------------- /packages/python-erpl/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/packages/python-erpl/README.md -------------------------------------------------------------------------------- /packages/python-erpl/python-erpl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/packages/python-erpl/python-erpl/__init__.py -------------------------------------------------------------------------------- /packages/python-erpl/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path as osp 3 | import shutil 4 | 5 | from glob import glob 6 | from pathlib import Path 7 | from setuptools import setup, find_packages 8 | 9 | platform_name = os.sys.platform 10 | current_dir = Path(__file__).parent.absolute() 11 | tgt_dir = "erpl" 12 | 13 | def join_current_dir(*paths): 14 | return osp.abspath(osp.join(current_dir, *paths)) 15 | 16 | def clear_binaries(): 17 | shutil.rmtree(join_current_dir(tgt_dir), ignore_errors=True) 18 | 19 | def copy_binaries(*src_paths): 20 | os.makedirs(join_current_dir(tgt_dir), exist_ok=True) 21 | for sp in src_paths: 22 | for f in glob(sp): 23 | print("Copying {} to {}".format(f, join_current_dir(tgt_dir))) 24 | shutil.copy(f, join_current_dir(tgt_dir)) 25 | 26 | def copy_binaries_for_platform(platform_name): 27 | # Set the correct binary path 28 | if platform_name == "linux": 29 | copy_binaries(join_current_dir("../../nwrfcsdk/lib/*.so*"), 30 | join_current_dir("../../build/release/extension/erpl/erpl.duckdb_extension")) 31 | elif platform_name == "win32": 32 | raise Exception("Windows is not supported yet") 33 | elif platform_name == "darwin": # macOS 34 | raise Exception("macOS is not supported yet") 35 | else: 36 | raise Exception("Unsupported platform: {}".format(platform_name)) 37 | 38 | clear_binaries() 39 | copy_binaries_for_platform(platform_name) 40 | 41 | 42 | setup( 43 | name="python-erpl", 44 | version="0.1", 45 | packages=find_packages(), 46 | package_data={ 47 | "python-erpl": [join_current_dir(tgt_dir, "*")] 48 | }, 49 | include_package_data=True, 50 | install_requires=[ ], 51 | ) 52 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "erpl" 3 | version = "0.1.0" 4 | description = "Python helper for the Duckdb python extension" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "pip>=25.0.1", 9 | ] 10 | -------------------------------------------------------------------------------- /rfc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(BUILD_UNITTESTS "Build ERPL C++ Unit Tests." OFF) 2 | 3 | # Set extension name here 4 | set(TARGET_NAME erpl_rfc) 5 | set(EXTENSION_NAME ${TARGET_NAME}_extension) 6 | set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension) 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | 11 | project(${TARGET_NAME}) 12 | 13 | include(../scripts/functions.cmake) 14 | add_duckdb_version_definition() 15 | 16 | if(WIN32) 17 | default_win32_libraries() 18 | endif() 19 | 20 | if(UNIX AND NOT APPLE) 21 | default_linux_libraries() 22 | endif() 23 | 24 | if(UNIX AND APPLE) 25 | default_osx_libraries() 26 | endif() 27 | 28 | find_package(OpenSSL REQUIRED) 29 | 30 | include_directories(src/include 31 | ../duckdb/third_party/ 32 | ../duckdb/third_party/httplib 33 | ../duckdb/third_party/miniz 34 | ${SAPNWRFC_HOME}/include 35 | ${OPENSSL_INCLUDE_DIR} 36 | ) 37 | 38 | add_yyjson_from_duckdb() 39 | 40 | set(EXTENSION_SOURCES 41 | src/erpl_rfc_extension.cpp 42 | src/pragma_ping.cpp 43 | src/pragma_set_trace.cpp 44 | src/pragma_ini.cpp 45 | src/sap_secret.cpp 46 | src/sap_connection.cpp 47 | src/sap_function.cpp 48 | src/sap_type_conversion.cpp 49 | src/sap_rfc.cpp 50 | src/duckdb_argument_helper.cpp 51 | src/duckdb_serialization_helper.cpp 52 | src/scanner_invoke.cpp 53 | src/scanner_show_groups.cpp 54 | src/scanner_show_functions.cpp 55 | src/scanner_describe_function.cpp 56 | src/scanner_show_tables.cpp 57 | src/scanner_describe_fields.cpp 58 | src/scanner_describe_references.cpp 59 | src/scanner_read_table.cpp 60 | src/telemetry.cpp 61 | ${YYJSON_OBJECT_FILES} 62 | ${SAPNWRFC_LIB_OBJECTS} 63 | ) 64 | 65 | # Build loadable extension 66 | build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES}) 67 | set(PARAMETERS "-warnings") 68 | build_loadable_extension(${TARGET_NAME} ${PARAMETERS} ${EXTENSION_SOURCES}) 69 | 70 | if(WIN32) 71 | default_win32_definitions(${EXTENSION_NAME}) 72 | default_win32_definitions(${LOADABLE_EXTENSION_NAME}) 73 | endif() 74 | 75 | if(UNIX AND NOT APPLE) 76 | default_linux_definitions(${EXTENSION_NAME}) 77 | default_linux_definitions(${LOADABLE_EXTENSION_NAME}) 78 | endif() 79 | 80 | if(UNIX AND APPLE) 81 | default_osx_definitions(${EXTENSION_NAME}) 82 | default_osx_definitions(${LOADABLE_EXTENSION_NAME}) 83 | endif() 84 | 85 | target_link_libraries(${EXTENSION_NAME} ${SAPNWRFC_LIB_FILES} ${OPENSSL_LIBRARIES}) 86 | target_link_libraries(${LOADABLE_EXTENSION_NAME} ${SAPNWRFC_LIB_FILES} ${OPENSSL_LIBRARIES}) 87 | 88 | if (WIN32) 89 | target_link_libraries(${EXTENSION_NAME} iphlpapi) 90 | target_link_libraries(${LOADABLE_EXTENSION_NAME} iphlpapi) 91 | endif() 92 | 93 | if(MINGW) 94 | target_link_libraries(${EXTENSION_NAME} -lcrypt32) 95 | target_link_libraries(${LOADABLE_EXTENSION_NAME} -lcrypt32) 96 | endif() 97 | 98 | if(${BUILD_UNITTESTS}) 99 | add_subdirectory(test) 100 | endif() 101 | 102 | install( 103 | TARGETS ${EXTENSION_NAME} ${LOADABLE_EXTENSION_NAME} 104 | EXPORT "${DUCKDB_EXPORT_SET}" 105 | LIBRARY DESTINATION "${INSTALL_LIB_DIR}" 106 | ARCHIVE DESTINATION "${INSTALL_LIB_DIR}") -------------------------------------------------------------------------------- /rfc/src/erpl_rfc_extension.cpp: -------------------------------------------------------------------------------- 1 | #define DUCKDB_EXTENSION_MAIN 2 | 3 | #include "duckdb.hpp" 4 | 5 | #include "duckdb/catalog/catalog.hpp" 6 | #include "duckdb/main/extension_util.hpp" 7 | 8 | #include "erpl_rfc_extension.hpp" 9 | #include "pragma_ping.hpp" 10 | #include "pragma_set_trace.hpp" 11 | #include "pragma_ini.hpp" 12 | #include "scanner_invoke.hpp" 13 | #include "scanner_show_groups.hpp" 14 | #include "scanner_describe_function.hpp" 15 | #include "scanner_show_functions.hpp" 16 | #include "scanner_show_tables.hpp" 17 | #include "scanner_describe_fields.hpp" 18 | #include "scanner_read_table.hpp" 19 | 20 | #include "telemetry.hpp" 21 | #include "sap_connection.hpp" 22 | #include "sap_secret.hpp" 23 | 24 | namespace duckdb { 25 | 26 | 27 | static void OnTelemetryEnabled(ClientContext &context, SetScope scope, Value ¶meter) 28 | { 29 | PostHogTelemetry::Instance().SetEnabled(parameter.GetValue()); 30 | } 31 | 32 | static void OnAPIKey(ClientContext &context, SetScope scope, Value ¶meter) 33 | { 34 | PostHogTelemetry::Instance().SetAPIKey(parameter.GetValue()); 35 | } 36 | 37 | static void RegisterConfiguration(DatabaseInstance &instance) 38 | { 39 | auto &config = DBConfig::GetConfig(instance); 40 | config.AddExtensionOption("erpl_telemetry_enabled", "Enable ERPL telemetry, see https://erpl.io/telemetry for details.", 41 | LogicalType::BOOLEAN, Value(true), OnTelemetryEnabled); 42 | config.AddExtensionOption("erpl_telemetry_key", "Telemetry key, see https://erpl.io/telemetry for details.", LogicalType::VARCHAR, 43 | Value("phc_t3wwRLtpyEmLHYaZCSszG0MqVr74J6wnCrj9D41zk2t"), OnAPIKey); 44 | 45 | auto provider = make_uniq(config); 46 | provider->SetAll(); 47 | 48 | RegisterSapSecretType(instance); 49 | } 50 | 51 | static void RegisterRfcFunctions(DatabaseInstance &instance) 52 | { 53 | ExtensionUtil::RegisterFunction(instance, CreateRfcPingPragma()); 54 | ExtensionUtil::RegisterFunction(instance, CreateRfcInvokeScanFunction()); 55 | ExtensionUtil::RegisterFunction(instance, CreateRfcShowFunctionScanFunction()); 56 | ExtensionUtil::RegisterFunction(instance, CreateRfcShowGroupScanFunction()); 57 | ExtensionUtil::RegisterFunction(instance, CreateRfcDescribeFunctionScanFunction()); 58 | ExtensionUtil::RegisterFunction(instance, CreateRfcShowTablesScanFunction()); 59 | ExtensionUtil::RegisterFunction(instance, CreateRfcDescribeFieldsScanFunction()); 60 | ExtensionUtil::RegisterFunction(instance, CreateRfcReadTableScanFunction()); 61 | ExtensionUtil::RegisterFunction(instance, CreateRfcSetTraceLevelPragma()); 62 | ExtensionUtil::RegisterFunction(instance, CreateRfcSetTraceDirPragma()); 63 | ExtensionUtil::RegisterFunction(instance, CreateRfcSetMaximumTraceFileSizePragma()); 64 | ExtensionUtil::RegisterFunction(instance, CreateRfcSetMaximumStoredTraceFilesPragma()); 65 | ExtensionUtil::RegisterFunction(instance, CreateRfcSetIniPathPragma()); 66 | ExtensionUtil::RegisterFunction(instance, CreateRfcReloadIniFilePragma()); 67 | } 68 | 69 | void ErplRfcExtension::Load(DuckDB &db) 70 | { 71 | PostHogTelemetry::Instance().CaptureExtensionLoad(); 72 | 73 | RegisterConfiguration(*db.instance); 74 | RegisterRfcFunctions(*db.instance); 75 | } 76 | 77 | std::string ErplRfcExtension::Name() { 78 | return "erpl_rfc"; 79 | } 80 | 81 | } // namespace duckdb 82 | 83 | extern "C" { 84 | DUCKDB_EXTENSION_API void erpl_rfc_init(duckdb::DatabaseInstance &db) 85 | { 86 | duckdb::DuckDB db_wrapper(db); 87 | db_wrapper.LoadExtension(); 88 | } 89 | 90 | DUCKDB_EXTENSION_API const char *erpl_rfc_version() 91 | { 92 | return duckdb::DuckDB::LibraryVersion(); 93 | } 94 | } 95 | 96 | #ifndef DUCKDB_EXTENSION_MAIN 97 | #error DUCKDB_EXTENSION_MAIN not defined 98 | #endif 99 | -------------------------------------------------------------------------------- /rfc/src/include/duckdb_argument_helper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | 5 | namespace duckdb 6 | { 7 | class ArgBuilder 8 | { 9 | public: 10 | ArgBuilder(); 11 | ArgBuilder &Add(const std::string &name, const Value &value); 12 | ArgBuilder &Add(const std::string &name, Value &value); 13 | ArgBuilder &Add(const std::string &name, const ArgBuilder &builder); 14 | ArgBuilder &Add(const std::string &name, duckdb::vector &values); 15 | ArgBuilder &Add(const std::string &name, std::vector &values); 16 | ArgBuilder &Add(const std::string &name, const std::initializer_list &values); 17 | 18 | Value Build() const; 19 | std::vector BuildArgList() const; 20 | Value &operator[](const std::string &name); 21 | 22 | private: 23 | child_list_t _args; 24 | }; 25 | 26 | // ------------------------------------------------------------------------------------------------ 27 | 28 | class ValueHelper 29 | { 30 | public: 31 | ValueHelper(Value &value); 32 | ValueHelper(Value &value, const std::string &root_path); 33 | ValueHelper(Value &value, std::vector &root_path); 34 | ValueHelper(const ValueHelper &h) = default; 35 | Value operator[](const std::string &name); 36 | 37 | static std::vector ParseJsonPointer(std::string path); 38 | static Value GetValueForPath(Value &value, std::vector &tokens); 39 | 40 | static bool IsX(Value &value); 41 | static bool IsX(const Value &value); 42 | 43 | Value &Get(); 44 | void Print(); 45 | void Print(std::string path); 46 | 47 | public: 48 | static Value CreateMutatedValue(Value &old_value, Value &new_value, std::vector &tokens); 49 | static Value CreateMutatedValue(Value &old_value, Value &new_value, std::string path); 50 | static Value AddToList(Value ¤t_list, Value &new_value); 51 | static Value RemoveFromList(Value ¤t_list, Value &value_to_remove); 52 | static Value RemoveFromList(Value ¤t_list, unsigned int index_to_remove); 53 | 54 | private: 55 | Value &_value; 56 | std::vector _root_path; 57 | 58 | std::vector GetPathWithRoot(std::string path); 59 | }; 60 | 61 | bool HasParam(named_parameter_map_t &named_params, const std::string &name); 62 | Value ConvertBoolArgument(named_parameter_map_t &named_params, const std::string &name, const bool default_value); 63 | 64 | template 65 | std::vector ConvertListValueToVector(Value &value) { 66 | std::vector result; 67 | for (auto &v: ListValue::GetChildren(value)) { 68 | result.push_back(v.GetValue()); 69 | } 70 | return result; 71 | } 72 | 73 | // ------------------------------------------------------------------------------------------------ 74 | 75 | template 76 | class GenericTable 77 | { 78 | public: 79 | GenericTable(std::shared_ptr value) 80 | : _value(value) 81 | { 82 | GenerateRowsFromVal(); 83 | } 84 | 85 | GenericTable(std::shared_ptr value, std::string root_path) 86 | : _value(value), _root_path(root_path) 87 | { 88 | GenerateRowsFromRootValWithPath(); 89 | } 90 | 91 | unsigned int Count() const { 92 | return _items.size(); 93 | } 94 | 95 | std::vector Rows() const { 96 | return _items; 97 | } 98 | 99 | TRow operator[](idx_t index) const 100 | { 101 | if (index >= _items.size()) { 102 | throw std::out_of_range("Index out of range for this table"); 103 | } 104 | 105 | return _items[index]; 106 | } 107 | 108 | protected: 109 | std::shared_ptr _value; 110 | std::string _root_path; 111 | std::vector _items; 112 | 113 | void GenerateRowsFromVal() 114 | { 115 | _items.clear(); 116 | auto row_vals = ListValue::GetChildren(*_value); 117 | for (auto &row_val : row_vals) { 118 | auto item = TRow(std::make_shared(row_val)); 119 | _items.push_back(item); 120 | } 121 | } 122 | 123 | void GenerateRowsFromRootValWithPath() 124 | { 125 | _items.clear(); 126 | auto list_val = ValueHelper(*_value)[_root_path]; 127 | auto row_vals = ListValue::GetChildren(list_val); 128 | for (unsigned int i = 0; i < row_vals.size(); i++) { 129 | auto path = StringUtil::Format("%s/%d", _root_path, i); 130 | auto item = TRow(_value, path); 131 | _items.push_back(item); 132 | } 133 | } 134 | }; 135 | 136 | // ------------------------------------------------------------------------------------------------ 137 | 138 | class ValueWrapper 139 | { 140 | public: 141 | ValueWrapper(std::shared_ptr value) 142 | : _value(value) 143 | { } 144 | 145 | ValueWrapper(std::shared_ptr value, std::string root_path) 146 | : _value(value), _root_path(root_path) 147 | { } 148 | 149 | inline ValueHelper Helper() const { 150 | return ValueHelper(*_value, _root_path); 151 | } 152 | 153 | bool IsMutable() const { 154 | return ! _root_path.empty(); 155 | } 156 | 157 | protected: 158 | std::shared_ptr _value; 159 | std::string _root_path; 160 | }; 161 | 162 | // ------------------------------------------------------------------------------------------------ 163 | 164 | class TableWrapper 165 | { 166 | public: 167 | TableWrapper(std::shared_ptr value, std::string root_path = std::string()); 168 | 169 | idx_t Size() const; 170 | duckdb::LogicalType ListOrientedRowType() const; 171 | 172 | duckdb::Value operator[](const idx_t index) const; 173 | private: 174 | duckdb::LogicalType CreateListOrientedRowTypeFromStructOrientedValue(const duckdb::Value &value) const; 175 | duckdb::Value CreateListOrientedValueFromIndex(const idx_t index) const; 176 | 177 | private: 178 | std::shared_ptr _struct_oriented_value; 179 | mutable duckdb::LogicalType _list_oriented_row_type; 180 | std::string _root_path; 181 | }; 182 | 183 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/duckdb_serialization_helper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "yyjson.hpp" 5 | 6 | #ifdef DUCKDB_YYJSON_THIRDPARTY 7 | using namespace duckdb_yyjson; 8 | #endif // DUCKDB_YYJSON_THIRDPARTY 9 | 10 | namespace duckdb 11 | { 12 | class ErplSerializer 13 | { 14 | public: 15 | static std::string SerializeJson(const Value &value); 16 | static std::string SerializeJson(const Value &value, bool include_type); 17 | static Value DeserializeJson(const std::string &json); 18 | 19 | static std::string SerializeSQL(const Value &value, const bool pretty_print = false, const uint32_t indent = 0); 20 | static std::string SerializeSQLType(const LogicalType &type, const bool pretty_print = false, const uint32_t indent = 0); 21 | 22 | private: 23 | ErplSerializer() = delete; 24 | 25 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const Value &value, bool include_type); 26 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const std::string &value); 27 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const int8_t &value); 28 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const int16_t &value); 29 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const int32_t &value); 30 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const int64_t &value); 31 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const uint8_t &value); 32 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const uint16_t &value); 33 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const uint32_t &value); 34 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const uint64_t &value); 35 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const float &value); 36 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const double &value); 37 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const bool &value); 38 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const date_t &value); 39 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const dtime_t &value); 40 | static yyjson_mut_val *SerializeJson(yyjson_mut_doc *doc, const timestamp_t &value); 41 | static yyjson_mut_val *SerializeBlobJson(yyjson_mut_doc *doc, const Value &blob_value); 42 | static yyjson_mut_val *SerializeStructJson(yyjson_mut_doc *doc, const Value &struct_value, bool include_type); 43 | static yyjson_mut_val *SerializeListJson(yyjson_mut_doc *doc, const Value &list_value, bool include_type); 44 | 45 | static yyjson_mut_val *WrapType(yyjson_mut_doc *doc, const LogicalTypeId &type, yyjson_mut_val *value); 46 | 47 | static Value DeserializeJson(yyjson_val *val); 48 | static Value DeserializeJsonString(std::string &typ, yyjson_val *val); 49 | static Value DeserializeJsonInteger(std::string &typ, yyjson_val *val); 50 | static Value DeserializeJsonReal(std::string &typ, yyjson_val *val); 51 | static Value DeserializeJsonBoolean(std::string &typ, yyjson_val *val); 52 | static Value DeserializeJsonDate(std::string &typ, yyjson_val *val); 53 | static Value DeserializeJsonTime(std::string &typ, yyjson_val *val); 54 | static Value DeserializeJsonTimestamp(std::string &typ, yyjson_val *val); 55 | static Value DeserializeJsonBlob(std::string &typ, yyjson_val *val); 56 | static Value DeserializeJsonStruct(std::string &typ, yyjson_val *val); 57 | static Value DeserializeJsonList(std::string &typ, yyjson_val *val); 58 | 59 | static std::string SerializeStructSQL(const Value &value, const bool pretty_print, uint32_t indent); 60 | static std::string SerializeListSQL(const Value &value, const bool pretty_print, uint32_t indent); 61 | 62 | static std::string SerializeStructType(const LogicalType &type, const bool pretty_print, uint32_t indent); 63 | static std::string SerializeListType(const LogicalType &type, const bool pretty_print, uint32_t indent); 64 | }; 65 | } -------------------------------------------------------------------------------- /rfc/src/include/erpl_rfc_extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | 5 | namespace duckdb { 6 | 7 | class ErplRfcExtension : public Extension { 8 | public: 9 | void Load(DuckDB &db) override; 10 | std::string Name() override; 11 | }; 12 | 13 | } // namespace duckdb 14 | -------------------------------------------------------------------------------- /rfc/src/include/pragma_ini.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | #include "sap_rfc.hpp" 6 | 7 | namespace duckdb { 8 | string PragmaSetIniPath(ClientContext &context, const FunctionParameters ¶meters); 9 | PragmaFunction CreateRfcSetIniPathPragma(); 10 | 11 | string PragmaReloadIniFile(ClientContext &context, const FunctionParameters ¶meters); 12 | PragmaFunction CreateRfcReloadIniFilePragma(); 13 | } -------------------------------------------------------------------------------- /rfc/src/include/pragma_ping.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | #include "sap_rfc.hpp" 6 | 7 | namespace duckdb { 8 | string RfcPing(ClientContext &context, const FunctionParameters ¶meters); 9 | PragmaFunction CreateRfcPingPragma(); 10 | } -------------------------------------------------------------------------------- /rfc/src/include/pragma_set_trace.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | #include "sap_rfc.hpp" 6 | 7 | namespace duckdb { 8 | string PragmaSetTraceLevel(ClientContext &context, const FunctionParameters ¶meters); 9 | PragmaFunction CreateRfcSetTraceLevelPragma(); 10 | 11 | string PragmaSetTraceDir(ClientContext &context, const FunctionParameters ¶meters); 12 | PragmaFunction CreateRfcSetTraceDirPragma(); 13 | 14 | string PragmaSetMaximumTraceFileSize(ClientContext &context, const FunctionParameters ¶meters); 15 | PragmaFunction CreateRfcSetMaximumTraceFileSizePragma(); 16 | 17 | string PragmaSetMaximumStoredTraceFiles(ClientContext &context, const FunctionParameters ¶meters); 18 | PragmaFunction CreateRfcSetMaximumStoredTraceFilesPragma(); 19 | } -------------------------------------------------------------------------------- /rfc/src/include/sap_connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | #include "sap_secret.hpp" 6 | 7 | namespace duckdb 8 | { 9 | typedef struct RfcConnectionAttributes 10 | { 11 | std::string destination; 12 | std::string host; 13 | std::string partner_host; 14 | std::string sys_number; 15 | std::string client; 16 | std::string user; 17 | std::string language; 18 | std::string trace; 19 | std::string iso_language; 20 | std::string codepage; 21 | std::string partner_codepage; 22 | std::string rfc_role; 23 | std::string type; 24 | std::string partner_type; 25 | std::string release; 26 | std::string partner_release; 27 | std::string kernel_release; 28 | std::string cpic_conv_id; 29 | std::string progname; 30 | std::string partner_bytes_per_char; 31 | std::string partner_system_codepage; 32 | std::string partner_ip; 33 | std::string partner_ipv6; 34 | } RfcConnectionAttributes; 35 | 36 | /** 37 | * @brief A wrapper class for the RFC_CONNECTION_HANDLE structure. 38 | * 39 | * This class wraps the RFC_CONNECTION_HANDLE structure used in the SAP NWRFC SDK. 40 | * It manages the lifetime of the connection handle and provides convenient 41 | * member functions for accessing the connection data. 42 | */ 43 | typedef struct RfcConnection 44 | { 45 | RFC_CONNECTION_HANDLE handle; 46 | 47 | RfcConnection(RFC_CONNECTION_HANDLE handle); 48 | ~RfcConnection(); 49 | 50 | void Close(); 51 | void Ping(); 52 | RfcConnectionAttributes ConnectionAttributes(); 53 | } RfcConnection; 54 | 55 | /** 56 | * @brief A class for setting extension variables from environment variables. 57 | */ 58 | struct RfcEnvironmentCredentialsProvider 59 | { 60 | static constexpr const char *ASHOST_ENV_VAR = "SAP_ASHOST"; 61 | static constexpr const char *SYSNR_ENV_VAR = "SAP_SYSNR"; 62 | static constexpr const char *USER_ENV_VAR = "SAP_USER"; 63 | static constexpr const char *PASSWORD_ENV_VAR = "SAP_PASSWORD"; 64 | static constexpr const char *CLIENT_ENV_VAR = "SAP_CLIENT"; 65 | static constexpr const char *LANG_ENV_VAR = "SAP_LANG"; 66 | 67 | explicit RfcEnvironmentCredentialsProvider(DBConfig &config) : config(config) {}; 68 | 69 | DBConfig &config; 70 | 71 | void SetExtensionOptionValue(string key, const char *env_var); 72 | void SetAll(); 73 | }; 74 | 75 | struct RfcAuthParams { 76 | string ashost; 77 | string sysnr; 78 | string user; 79 | string password; 80 | string client; 81 | string lang; 82 | string mshost; 83 | string msserv; 84 | string sysid; 85 | string group; 86 | string snc_qop; 87 | string snc_myname; 88 | string snc_partnername; 89 | string snc_lib; 90 | string mysapsso2; 91 | 92 | static RfcAuthParams FromContext(ClientContext &context, const string &secret_name = SAP_SECRET_DEFAULT_PATH); 93 | string ToString(); 94 | std::shared_ptr Connect(); 95 | }; 96 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/sap_rfc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "duckdb.hpp" 6 | #include "duckdb/parallel/base_pipeline_event.hpp" 7 | #include "duckdb/parallel/task_executor.hpp" 8 | #include "sapnwrfc.h" 9 | 10 | #include "sap_function.hpp" 11 | 12 | namespace duckdb 13 | { 14 | string RfcFunctionDesc(ClientContext &context, const FunctionParameters ¶meters); 15 | 16 | //struct RfcReadTableGlobalState; // forward declaration 17 | //struct RfcReadTableLocalState; // forward declaration 18 | class RfcReadColumnStateMachine; // forward declaration 19 | class RfcReadColumnTask; // forward declaration 20 | 21 | typedef std::shared_ptr (* RfcConnectionFactory_t)(ClientContext &context); 22 | std::shared_ptr DefaultRfcConnectionFactory(ClientContext &context); 23 | 24 | class RfcReadTableBindData : public TableFunctionData 25 | { 26 | public: 27 | static const idx_t MAX_OPTION_LEN = 70; 28 | 29 | RfcReadTableBindData(std::string table_name, 30 | int max_read_threads, 31 | unsigned int limit, 32 | RfcConnectionFactory_t connection_factory, 33 | ClientContext &context); 34 | 35 | void InitOptionsFromWhereClause(std::string &where_clause); 36 | void AddOptionsFromWhereClause(std::string &where_clause); 37 | void InitAndVerifyFields(std::vector req_fields); 38 | 39 | void ActivateColumns(vector &column_ids); 40 | void AddOptionsFromFilters(duckdb::optional_ptr filters); 41 | 42 | std::vector GetRfcColumnNames(); 43 | duckdb::vector GetRfcColumnName(unsigned int column_idx); 44 | std::string GetProjectedColumnName(unsigned int projected_column_idx); 45 | std::vector GetReturnTypes(); 46 | RfcType GetColumnType(unsigned int column_idx); 47 | duckdb::vector GetOptions(); 48 | std::shared_ptr OpenNewConnection(); 49 | 50 | bool HasMoreResults(); 51 | void Step(ClientContext &context, DataChunk &output); 52 | 53 | std::string ToString(); 54 | double GetProgress(); 55 | 56 | public: 57 | std::string table_name; 58 | std::vector options; 59 | unsigned int limit = 0; 60 | unsigned int max_threads = 0; 61 | 62 | private: 63 | RfcConnectionFactory_t connection_factory; 64 | ClientContext &client_context; 65 | std::vector column_names; 66 | std::vector column_types; 67 | std::vector column_state_machines; 68 | 69 | std::vector CreateReadColumnStateMachines(); 70 | unsigned int NActiveStateMachines(); 71 | unsigned int FirstActiveStateMachineCardinality(); 72 | bool AreActiveStateMachineCaridnalitiesEqual(); 73 | public: 74 | static std::vector GetTableFieldMetas(std::shared_ptr connection, std::string table_name); 75 | static RfcType GetRfcTypeForFieldMeta(Value &DFIES_entry); 76 | 77 | static std::string TransformFilter(std::string &column_name, TableFilter &filter); 78 | static std::string TransformLiteral(const Value &val); 79 | static std::string TransformBlob(const std::string &val); 80 | static std::string CreateExpression(string &column_name, vector> &filters, string op); 81 | static std::string TransformComparision(ExpressionType type); 82 | }; 83 | 84 | enum class ReadTableStates { 85 | INIT, 86 | EXTRACT_FROM_SAP, 87 | LOAD_TO_DUCKDB, 88 | FINAL_LOAD_TO_DUCKDB, 89 | FINISHED 90 | }; 91 | 92 | std::string ReadTableStatesToString(ReadTableStates &state); 93 | 94 | class RfcReadColumnStateMachine 95 | { 96 | friend class RfcReadColumnTask; 97 | 98 | public: 99 | RfcReadColumnStateMachine(RfcReadTableBindData *bind_data, idx_t column_idx, unsigned int limit); 100 | RfcReadColumnStateMachine(const RfcReadColumnStateMachine& other); 101 | ~RfcReadColumnStateMachine(); 102 | 103 | bool Active(); 104 | void SetInactive(); 105 | void SetActive(idx_t projected_column_idx); 106 | bool Finished(); 107 | duckdb::unique_ptr CreateTaskForNextStep(duckdb::TaskExecutor &executor, duckdb::Vector &column_output); 108 | unsigned int GetRfcColumnIndex(); 109 | unsigned int GetProjectedColumnIndex(); 110 | std::shared_ptr GetConnection(); 111 | bool IsRowIdColumnId(); 112 | void SetRowIdColumnId(); 113 | unsigned int GetCardinality(); 114 | unsigned int GetBatchCount(); 115 | std::string ToString(); 116 | 117 | private: 118 | bool active = true; 119 | bool row_id_column_id = false; 120 | unsigned int desired_batch_size = 20*STANDARD_VECTOR_SIZE; 121 | unsigned int pending_records = 0; 122 | unsigned int cardinality = 0; 123 | unsigned int batch_count = 0; 124 | unsigned int duck_count = 0; 125 | unsigned int total_rows = 0; 126 | unsigned int limit; 127 | 128 | idx_t column_idx; 129 | idx_t projected_column_idx = DConstants::INVALID_INDEX; 130 | 131 | RfcReadTableBindData *bind_data; 132 | ReadTableStates current_state = ReadTableStates::INIT; 133 | std::shared_ptr current_result_data = nullptr; 134 | std::shared_ptr current_connection = nullptr; 135 | 136 | std::mutex thread_lock; 137 | }; 138 | 139 | class RfcReadColumnTask : public duckdb::BaseExecutorTask 140 | { 141 | public: 142 | RfcReadColumnTask(RfcReadColumnStateMachine *owning_state_machine, duckdb::TaskExecutor &executor, duckdb::Vector ¤t_column_output); 143 | void ExecuteTask(); 144 | 145 | private: 146 | unsigned int ExecuteNextTableReadForColumn(); 147 | std::vector CreateFunctionArguments(); 148 | 149 | unsigned int LoadNextBatchToDuckDBColumn(); 150 | Value ParseCsvValue(Value &orig); 151 | 152 | private: 153 | RfcReadColumnStateMachine *owning_state_machine; 154 | duckdb::Vector ¤t_column_output; 155 | }; 156 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/sap_secret.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "duckdb/main/secret/secret.hpp" 5 | #include "duckdb/main/secret/secret_manager.hpp" 6 | 7 | namespace duckdb { 8 | 9 | class RfcAuthParams; // Forward declaration 10 | 11 | static constexpr const char *SAP_SECRET_PROVIDER = "config"; 12 | static constexpr const char *SAP_SECRET_TYPE_NAME = "sap_rfc"; 13 | static constexpr const char *SAP_SECRET_DEFAULT_PATH = "*"; 14 | 15 | // Convert a DuckDB secret to an RfcAuthParams 16 | RfcAuthParams ConvertSecretToAuthParams(const KeyValueSecret &duck_secret); 17 | 18 | // Register the SAP secret type with DuckDB 19 | void RegisterSapSecretType(DatabaseInstance &instance); 20 | 21 | // Get the secret name from the named parameters 22 | std::string GetSecretNameFromParams(const TableFunctionBindInput ¶meters); 23 | std::string GetSecretNameFromParams(const FunctionParameters ¶meters); 24 | std::string GetSecretNameFromParams(const named_parameter_map_t &named_params); 25 | 26 | // Get the secret from the context 27 | RfcAuthParams GetAuthParamsFromContext(ClientContext &context, const TableFunctionBindInput ¶meters); 28 | RfcAuthParams GetAuthParamsFromContext(ClientContext &context, const FunctionParameters ¶meters); 29 | RfcAuthParams GetAuthParamsFromContext(ClientContext &context, const std::string &secret_name); 30 | 31 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/sap_type_conversion.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | #include "sap_function.hpp" 6 | 7 | #include "duckdb/common/types/time.hpp" 8 | 9 | namespace duckdb 10 | { 11 | std::string uc2std(SAP_UC *str, unsigned int len, bool rtrim); 12 | std::string uc2std(SAP_UC *str, unsigned int len); 13 | std::string uc2std(SAP_UC *str); 14 | std::string uc2std(const SAP_UC *str); 15 | 16 | Value uc2duck(SAP_UC *uc_str, unsigned int uc_str_len, bool rtrim); 17 | Value uc2duck(SAP_UC *uc_str, unsigned int uc_str_len); 18 | Value uc2duck(SAP_UC *uc_str); 19 | 20 | void std2uc(string &std_str, SAP_UC *uc_str, unsigned int uc_str_size); 21 | unique_ptr std2uc(string &std_str); 22 | unique_ptr std2uc(const string &std_str); 23 | unique_ptr duck2uc(Value &value); 24 | Value dats2duck(std::string &dats_str); 25 | Value tims2duck(std::string &tims_str); 26 | void ltrim(std::string &s); 27 | Value bcd2duck(std::string &bcd_str, unsigned int length, unsigned int decimals); 28 | 29 | Value rfc2duck(RFC_DATE &rfc_date); 30 | Value rfc2duck(const RFC_DATE &rfc_date); 31 | Value rfc2duck(RFC_TIME &rfc_time); 32 | Value rfc2duck(const RFC_TIME &rfc_time); 33 | Value rfc2duck(RFC_FLOAT &rfc_float); 34 | Value rfc2duck(const RFC_FLOAT &rfc_float); 35 | Value rfc2duck(RFC_INT &rfc_int); 36 | Value rfc2duck(const RFC_INT &rfc_int); 37 | Value rfc2duck(RFC_INT1 &rfc_char); 38 | Value rfc2duck(const RFC_INT1 &rfc_char); 39 | Value rfc2duck(RFC_INT2 &rfc_short); 40 | Value rfc2duck(const RFC_INT2 &rfc_short); 41 | Value rfc2duck(RFC_INT8 &rfc_long); 42 | Value rfc2duck(const RFC_INT8 &rfc_long); 43 | Value rfc2duck(RFC_NUM *rfc_num, unsigned int len, bool rtrim); 44 | Value rfc2duck(const RFC_NUM *rfc_num, const unsigned int len, const bool rtrim); 45 | Value rfc2duck(RFC_NUM *rfc_num, unsigned int len); 46 | Value rfc2duck(const RFC_NUM *rfc_num, const unsigned int len); 47 | Value rfc2duck(RFC_BYTE *rfc_byte, unsigned int len); 48 | Value rfc2duck(const RFC_BYTE *rfc_byte, const unsigned int len); 49 | 50 | void duck2rfc(Value &duck_value, RFC_DATE &rfc_date); 51 | void duck2rfc(Value &duck_value, RFC_TIME &rfc_time); 52 | void duck2rfc(Value &duck_value, RFC_FLOAT &rfc_float); 53 | void duck2rfc(Value &duck_value, RFC_INT &rfc_int); 54 | void duck2rfc(Value &duck_value, RFC_INT1 &rfc_char); 55 | void duck2rfc(Value &duck_value, RFC_INT2 &rfc_short); 56 | void duck2rfc(Value &duck_value, RFC_INT8 &rfc_long); 57 | void duck2rfc(Value &duck_value, RFC_NUM *rfc_num, unsigned int len); 58 | //void duck2rfc(Value &duck_value, RFC_BCD &rfc_bcd); 59 | void duck2rfc(Value &duck_value, RFC_DECF16 &rfc_dec16); 60 | void duck2rfc(Value &duck_value, RFC_DECF34 &rfc_dec34); 61 | 62 | std::string rfcrc2std(RFC_RC &rc); 63 | 64 | std::string rfctype2std(RFCTYPE &type, bool short_name = false); 65 | const std::string rfctype2std(const RFCTYPE &type, const bool short_name = false); 66 | LogicalTypeId rfctype2logicaltype(RFCTYPE rfc_type); 67 | 68 | std::string rfcdirection2std(RFC_DIRECTION &direction, bool short_name = false); 69 | const std::string rfcdirection2std(const RFC_DIRECTION &direction, const bool short_name = false); 70 | 71 | std::string rfcerrorgroup2std(RFC_ERROR_GROUP &group); 72 | std::string rfcerrorinfo2std(RFC_ERROR_INFO &error_info); 73 | 74 | } // namespace duckdb 75 | -------------------------------------------------------------------------------- /rfc/src/include/scanner_describe_fields.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb { 12 | TableFunction CreateRfcDescribeFieldsScanFunction(); 13 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_describe_function.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | #include "duckdb_argument_helper.hpp" 11 | 12 | namespace duckdb 13 | { 14 | class RfcDescribeFunctionBindData : public TableFunctionData 15 | { 16 | public: 17 | RfcDescribeFunctionBindData(std::shared_ptr function, std::shared_ptr RPY_FUNCTIONMODULE_READ) 18 | : _function(function), _RPY_FUNCTIONMODULE_READ(RPY_FUNCTIONMODULE_READ), _done(false) 19 | { } 20 | 21 | static std::unique_ptr FromFunctionHandleAndResultSet(std::shared_ptr function, 22 | std::shared_ptr result_set) 23 | { 24 | auto result_value = std::make_shared(result_set->GetResultValue()); 25 | return std::make_unique(function, result_value); 26 | } 27 | 28 | std::vector GetResultNames() 29 | { 30 | std::vector names = { 31 | "name", 32 | "text", 33 | "function_group", 34 | "remote_callable", 35 | "import", 36 | "export", 37 | "changing", 38 | "tables", 39 | "source" 40 | }; 41 | 42 | return names; 43 | } 44 | 45 | std::vector GetResultTypes() 46 | { 47 | auto param_struct = GetParamterResultType(); 48 | 49 | std::vector types = { 50 | LogicalType::VARCHAR, // name 51 | LogicalType::VARCHAR, // text 52 | LogicalType::VARCHAR, // function_group 53 | LogicalType::VARCHAR, // remote_callable 54 | LogicalType::LIST(param_struct), // import 55 | LogicalType::LIST(param_struct), // export 56 | LogicalType::LIST(param_struct), // changing 57 | LogicalType::LIST(param_struct), // tables 58 | LogicalType::VARCHAR // source 59 | }; 60 | 61 | return types; 62 | } 63 | 64 | bool HasMoreResults() 65 | { 66 | return _done == false; 67 | } 68 | 69 | void FetchNextResult(DataChunk &output) 70 | { 71 | auto param_infos = _function->GetParameterInfos(); 72 | std::vector sel_infos; 73 | 74 | // name 75 | output.SetValue(0, 0, Value(_function->GetName())); 76 | 77 | // text 78 | output.SetValue(1, 0, ShortText()); 79 | 80 | // function_group 81 | output.SetValue(2, 0, FunctionPool()); 82 | 83 | // remote_callable 84 | output.SetValue(3, 0, RemoteCall()); 85 | 86 | // import 87 | sel_infos.clear(); 88 | std::copy_if(param_infos.begin(), param_infos.end(), std::back_inserter(sel_infos), 89 | [](RfcFunctionParameterDesc &x) { return x.GetDirection() == RFC_IMPORT; }); 90 | output.SetValue(4, 0, ConvertParameterInfos(sel_infos)); 91 | 92 | // export 93 | sel_infos.clear(); 94 | std::copy_if(param_infos.begin(), param_infos.end(), std::back_inserter(sel_infos), 95 | [](RfcFunctionParameterDesc &x) { return x.GetDirection() == RFC_EXPORT; }); 96 | output.SetValue(5, 0, ConvertParameterInfos(sel_infos)); 97 | 98 | // changing 99 | sel_infos.clear(); 100 | std::copy_if(param_infos.begin(), param_infos.end(), std::back_inserter(sel_infos), 101 | [](RfcFunctionParameterDesc &x) { return x.GetDirection() == RFC_CHANGING; }); 102 | output.SetValue(6, 0, ConvertParameterInfos(sel_infos)); 103 | 104 | // tables 105 | sel_infos.clear(); 106 | std::copy_if(param_infos.begin(), param_infos.end(), std::back_inserter(sel_infos), 107 | [](RfcFunctionParameterDesc &x) { return x.GetDirection() == RFC_TABLES; }); 108 | output.SetValue(7, 0, ConvertParameterInfos(sel_infos)); 109 | 110 | // source 111 | output.SetValue(8, 0, Source()); 112 | 113 | _done = true; 114 | output.SetCardinality(1); 115 | } 116 | 117 | private: 118 | std::shared_ptr _function; 119 | std::shared_ptr _RPY_FUNCTIONMODULE_READ; 120 | bool _done; 121 | 122 | ValueHelper Helper() 123 | { 124 | return ValueHelper(*_RPY_FUNCTIONMODULE_READ); 125 | } 126 | 127 | LogicalType GetParamterResultType() 128 | { 129 | auto param_struct = LogicalType::STRUCT({ 130 | { "name", LogicalTypeId::VARCHAR }, 131 | { "text", LogicalTypeId::VARCHAR }, 132 | { "abap_type", LogicalTypeId::VARCHAR }, 133 | { "duckdb_type", LogicalTypeId::VARCHAR }, 134 | { "direction", LogicalTypeId::VARCHAR}, 135 | { "length", LogicalTypeId::INTEGER }, 136 | { "decimals", LogicalTypeId::INTEGER }, 137 | { "default_value", LogicalTypeId::VARCHAR }, 138 | { "optional", LogicalTypeId::BOOLEAN }, 139 | }); 140 | return param_struct; 141 | } 142 | 143 | Value ConvertParameterInfos(std::vector ¶m_infos) 144 | { 145 | vector param_values; 146 | for (auto ¶m_info : param_infos) { 147 | param_values.push_back(ConvertParameterInfo(param_info)); 148 | } 149 | 150 | const auto param_type = GetParamterResultType(); 151 | return Value::LIST(param_type, param_values); 152 | } 153 | 154 | Value ConvertParameterInfo(RfcFunctionParameterDesc ¶m_info) 155 | { 156 | const auto param_type = GetParamterResultType(); 157 | 158 | auto rfc_type = param_info.GetRfcType(); 159 | auto duck_type = rfc_type->CreateDuckDbType(); 160 | 161 | child_list_t param_value { 162 | make_pair("name", Value(param_info.GetName())), 163 | make_pair("text", Value(param_info.GetDescription())), 164 | make_pair("abap_type", Value(rfc_type->GetName())), 165 | make_pair("duckdb_type", Value(duck_type.ToString())), 166 | make_pair("direction", Value(param_info.GetDirectionAsString())), 167 | make_pair("length", Value::INTEGER(param_info.GetLength())), 168 | make_pair("decimals", Value::INTEGER(param_info.GetDecimals())), 169 | make_pair("default_value", Value(param_info.GetDefaultValue())), 170 | make_pair("optional", Value(param_info.IsOptional())) 171 | }; 172 | 173 | return Value::STRUCT(param_value); 174 | } 175 | 176 | Value ShortText() { return Helper()["SHORT_TEXT"]; } 177 | Value RemoteCall() { return Helper()["REMOTE_CALL"]; } 178 | Value FunctionPool() { return Helper()["FUNCTION_POOL"]; } 179 | Value Source() 180 | { 181 | std::stringstream ss; 182 | 183 | auto source_lines = ListValue::GetChildren(Helper()["SOURCE"]); 184 | for (auto &line : source_lines) 185 | { 186 | ss << ValueHelper(line)["LINE"].ToString() << std::endl; 187 | } 188 | 189 | return Value(ss.str()); 190 | } 191 | }; 192 | 193 | TableFunction CreateRfcDescribeFunctionScanFunction(); 194 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_describe_references.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb { 12 | TableFunction CreateRfcDescribeReferencesScanFunction(); 13 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_invoke.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb 12 | { 13 | TableFunction CreateRfcInvokeScanFunction(); 14 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_read_table.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb { 12 | TableFunction CreateRfcReadTableScanFunction(); 13 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_show_functions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb { 12 | TableFunction CreateRfcShowFunctionScanFunction(); 13 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_show_groups.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb { 12 | TableFunction CreateRfcShowGroupScanFunction(); 13 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/scanner_show_tables.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | #include "sapnwrfc.h" 5 | 6 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 7 | 8 | #include "sap_connection.hpp" 9 | #include "sap_function.hpp" 10 | 11 | namespace duckdb { 12 | TableFunction CreateRfcShowTablesScanFunction(); 13 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/include/telemetry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "duckdb.hpp" 16 | 17 | namespace duckdb { 18 | 19 | class PostHogEvent 20 | { 21 | public: 22 | std::string GetPropertiesJson() const; 23 | std::string GetNowISO8601() const; 24 | 25 | std::string event_name; 26 | std::string distinct_id; // user distinct id 27 | std::map properties; 28 | }; 29 | 30 | class PostHogWorker 31 | { 32 | public: 33 | PostHogWorker(std::string api_key); 34 | void Process(const PostHogEvent &event); 35 | 36 | private: 37 | std::string api_key; 38 | }; 39 | 40 | void PostHogProcess(const std::string api_key, const PostHogEvent &event); 41 | 42 | template 43 | class TelemetryTaskQueue { 44 | public: 45 | TelemetryTaskQueue(unsigned int n_threads = 1) 46 | { 47 | _stop = false; 48 | for (unsigned int i = 0; i < n_threads; ++i) { 49 | _workers.emplace_back([this] { 50 | while (true) { 51 | std::function task; 52 | { 53 | std::unique_lock lock(this->_queue_mutex); 54 | this->_condition.wait(lock, [this] { 55 | return this->_stop || !this->_tasks.empty(); 56 | }); 57 | 58 | if (this->_stop) 59 | return; 60 | 61 | task = std::move(this->_tasks.front()); 62 | this->_tasks.pop(); 63 | } 64 | 65 | task(); 66 | } 67 | }); 68 | } 69 | } 70 | 71 | ~TelemetryTaskQueue() 72 | { 73 | { 74 | std::unique_lock lock(_queue_mutex); 75 | _stop = true; 76 | } 77 | _condition.notify_all(); 78 | for (auto &worker : _workers) { 79 | worker.detach(); 80 | } 81 | } 82 | 83 | template 84 | auto EnqueueTask(F&& f, Args&&... args) 85 | -> std::future::type> { 86 | using return_type = typename std::result_of::type; 87 | 88 | auto task = std::make_shared< std::packaged_task >( 89 | std::bind(std::forward(f), std::forward(args)...) 90 | ); 91 | 92 | std::future res = task->get_future(); 93 | { 94 | std::unique_lock lock(_queue_mutex); 95 | 96 | if(_stop) 97 | throw std::runtime_error("Enqueue on stopped TelemetryTaskQueue"); 98 | 99 | _tasks.emplace([task](){ (*task)(); }); 100 | } 101 | _condition.notify_one(); 102 | return res; 103 | } 104 | 105 | private: 106 | std::vector _workers; 107 | std::queue< std::function > _tasks; 108 | 109 | std::mutex _queue_mutex; 110 | std::condition_variable _condition; 111 | bool _stop; 112 | }; 113 | 114 | class PostHogTelemetry 115 | { 116 | public: 117 | static PostHogTelemetry& Instance(); 118 | static std::string GetMacAddress(); 119 | static std::string GetMacAddressSafe(); 120 | 121 | void CaptureExtensionLoad(); 122 | void CaptureExtensionLoad(std::string extension_name); 123 | void CaptureFunctionExecution(std::string function_name); 124 | 125 | bool IsEnabled(); 126 | void SetEnabled(bool enabled); 127 | 128 | std::string GetAPIKey(); 129 | void SetAPIKey(std::string api_key); 130 | 131 | private: 132 | PostHogTelemetry(); 133 | ~PostHogTelemetry(); 134 | 135 | static bool IsPhysicalDevice(const std::string& device); 136 | static std::string FindFirstPhysicalDevice(); 137 | 138 | TelemetryTaskQueue _queue; 139 | 140 | bool _telemetry_enabled; 141 | std::string _api_key; 142 | std::mutex _thread_lock; 143 | }; 144 | 145 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/pragma_ini.cpp: -------------------------------------------------------------------------------- 1 | #include "pragma_ini.hpp" 2 | #include "duckdb/parser/parsed_data/create_pragma_function_info.hpp" 3 | #include "sapnwrfc.h" 4 | #include "telemetry.hpp" 5 | 6 | namespace duckdb 7 | { 8 | string PragmaSetIniPath(ClientContext &context, const FunctionParameters ¶meters) 9 | { 10 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_set_ini_path"); 11 | 12 | RFC_RC rc = RFC_OK; 13 | RFC_ERROR_INFO error_info; 14 | 15 | auto ini_path = std2uc(parameters.values[0].ToString()); 16 | rc = RfcSetIniPath(ini_path.get(), &error_info); 17 | if (rc != RFC_OK) { 18 | throw std::runtime_error(StringUtil::Format("Failed to set ini path: %s", uc2std(error_info.message))); 19 | } 20 | 21 | auto ip_query = StringUtil::Format(StringUtil::Format("SELECT '%s' as ini_path", uc2std(ini_path.get()))); 22 | return ip_query; 23 | } 24 | 25 | string PragmaReloadIniFile(ClientContext &context, const FunctionParameters ¶meters) 26 | { 27 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_reload_ini_file"); 28 | 29 | RFC_RC rc = RFC_OK; 30 | RFC_ERROR_INFO error_info; 31 | 32 | rc = RfcReloadIniFile(&error_info); 33 | if (rc != RFC_OK) { 34 | throw std::runtime_error(StringUtil::Format("Failed to reload ini file: %s", uc2std(error_info.message))); 35 | } 36 | 37 | auto rif_query = StringUtil::Format(StringUtil::Format("SELECT 'Reloaded ini file' as reload_ini_file")); 38 | return rif_query; 39 | } 40 | 41 | PragmaFunction CreateRfcSetIniPathPragma() 42 | { 43 | auto pragma_function = PragmaFunction::PragmaCall("sap_rfc_set_ini_path", PragmaSetIniPath, { LogicalType::VARCHAR }); 44 | return pragma_function; 45 | } 46 | 47 | PragmaFunction CreateRfcReloadIniFilePragma() 48 | { 49 | auto pragma_function = PragmaFunction::PragmaStatement("sap_rfc_reload_ini_file", PragmaReloadIniFile); 50 | return pragma_function; 51 | } 52 | } -------------------------------------------------------------------------------- /rfc/src/pragma_ping.cpp: -------------------------------------------------------------------------------- 1 | #include "pragma_ping.hpp" 2 | #include "duckdb/parser/parsed_data/create_pragma_function_info.hpp" 3 | #include "telemetry.hpp" 4 | 5 | namespace duckdb { 6 | string RfcPing(ClientContext &context, const FunctionParameters ¶meters) 7 | { 8 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_ping"); 9 | 10 | // Connect to the SAP system 11 | auto auth_params = GetAuthParamsFromContext(context, parameters); 12 | auto connection = auth_params.Connect(); 13 | connection->Ping(); 14 | 15 | auto pragma_query = StringUtil::Format("SELECT 'PONG' as msg"); 16 | return pragma_query; 17 | } 18 | 19 | PragmaFunction CreateRfcPingPragma() 20 | { 21 | auto rfc_ping_pragma = PragmaFunction::PragmaCall("sap_rfc_ping", RfcPing, {}); 22 | rfc_ping_pragma.named_parameters["secret"] = LogicalType::VARCHAR; 23 | 24 | return rfc_ping_pragma; 25 | } 26 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/pragma_set_trace.cpp: -------------------------------------------------------------------------------- 1 | #include "pragma_set_trace.hpp" 2 | #include "duckdb/parser/parsed_data/create_pragma_function_info.hpp" 3 | #include "sapnwrfc.h" 4 | #include "telemetry.hpp" 5 | 6 | namespace duckdb 7 | { 8 | std::tuple GetTraceLevelFromEnum(const Value &trace_level_enum) 9 | { 10 | auto trace_level = trace_level_enum.GetValue(); 11 | 12 | auto trace_level_vals = Vector(LogicalType::VARCHAR, 5); 13 | trace_level_vals.SetValue(0, Value("Off")); 14 | trace_level_vals.SetValue(1, Value("Brief")); 15 | trace_level_vals.SetValue(2, Value("Verbose")); 16 | trace_level_vals.SetValue(3, Value("Detailed")); 17 | trace_level_vals.SetValue(4, Value("Full")); 18 | 19 | auto trace_level_str = trace_level_vals.GetValue(trace_level).ToString(); 20 | return std::make_tuple(trace_level, trace_level_str); 21 | } 22 | 23 | string PragmaSetTraceLevel(ClientContext &context, const FunctionParameters ¶meters) 24 | { 25 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_set_trace_level"); 26 | 27 | RFC_RC rc = RFC_OK; 28 | RFC_ERROR_INFO error_info; 29 | 30 | auto [trace_level, trace_level_str] = GetTraceLevelFromEnum(parameters.values[0]); 31 | 32 | rc = RfcSetTraceLevel(NULL, NULL, trace_level, &error_info); 33 | if (rc != RFC_OK) { 34 | throw std::runtime_error(StringUtil::Format("Failed to set trace level: %s", uc2std(error_info.message))); 35 | } 36 | 37 | auto tl_query = StringUtil::Format(StringUtil::Format("SELECT '%s (%d)' as trace_level", trace_level_str, trace_level)); 38 | return tl_query; 39 | } 40 | 41 | string PragmaSetTraceDir(ClientContext &context, const FunctionParameters ¶meters) 42 | { 43 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_set_trace_dir"); 44 | 45 | RFC_RC rc = RFC_OK; 46 | RFC_ERROR_INFO error_info; 47 | 48 | auto trace_dir = std2uc(parameters.values[0].ToString()); 49 | rc = RfcSetTraceDir(trace_dir.get(), &error_info); 50 | if (rc != RFC_OK) { 51 | throw std::runtime_error(StringUtil::Format("Failed to set trace directory: %s", uc2std(error_info.message))); 52 | } 53 | 54 | auto td_query = StringUtil::Format(StringUtil::Format("SELECT '%s' as trace_dir", uc2std(trace_dir.get()))); 55 | return td_query; 56 | } 57 | 58 | string PragmaSetMaximumTraceFileSize(ClientContext &context, const FunctionParameters ¶meters) 59 | { 60 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_set_maximum_trace_file_size"); 61 | 62 | RFC_RC rc = RFC_OK; 63 | RFC_ERROR_INFO error_info; 64 | 65 | auto trace_file_size = parameters.values[0].GetValue(); 66 | SAP_UC trace_size_unit = parameters.values[1].ToString()[0]; 67 | 68 | rc = RfcSetMaximumTraceFileSize(trace_file_size, trace_size_unit, &error_info); 69 | if (rc != RFC_OK) { 70 | throw std::runtime_error(StringUtil::Format("Failed to set trace file size: %s", uc2std(error_info.message))); 71 | } 72 | 73 | auto tfs_query = StringUtil::Format(StringUtil::Format("SELECT '%d' as trace_file_size", trace_file_size)); 74 | return tfs_query; 75 | } 76 | 77 | string PragmaSetMaximumStoredTraceFiles(ClientContext &context, const FunctionParameters ¶meters) 78 | { 79 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_set_maximum_stored_trace_files"); 80 | 81 | RFC_RC rc = RFC_OK; 82 | RFC_ERROR_INFO error_info; 83 | 84 | auto max_stored_trace_files = parameters.values[0].GetValue(); 85 | 86 | rc = RfcSetMaximumStoredTraceFiles(max_stored_trace_files, &error_info); 87 | if (rc != RFC_OK) { 88 | throw std::runtime_error(StringUtil::Format("Failed to set maximum stored trace files: %s", uc2std(error_info.message))); 89 | } 90 | 91 | auto mstf_query = StringUtil::Format(StringUtil::Format("SELECT '%d' as max_stored_trace_files", max_stored_trace_files)); 92 | return mstf_query; 93 | } 94 | 95 | PragmaFunction CreateRfcSetTraceLevelPragma() 96 | { 97 | auto rfc_set_trace_level = PragmaFunction::PragmaCall("sap_rfc_set_trace_level", 98 | PragmaSetTraceLevel, 99 | { LogicalType::INTEGER }); 100 | 101 | return rfc_set_trace_level; 102 | } 103 | 104 | PragmaFunction CreateRfcSetTraceDirPragma() 105 | { 106 | auto rfc_set_trace_dir = PragmaFunction::PragmaCall("sap_rfc_set_trace_dir", 107 | PragmaSetTraceDir, 108 | { LogicalType::VARCHAR }); 109 | 110 | return rfc_set_trace_dir; 111 | } 112 | 113 | PragmaFunction CreateRfcSetMaximumTraceFileSizePragma() 114 | { 115 | auto trace_size_unit_enum = Vector(LogicalType::VARCHAR, 2); 116 | trace_size_unit_enum.SetValue(0, Value("M")); 117 | trace_size_unit_enum.SetValue(1, Value("G")); 118 | 119 | auto rfc_set_maximum_trace_file_size = PragmaFunction::PragmaCall("sap_rfc_set_maximum_trace_file_size", 120 | PragmaSetMaximumTraceFileSize, 121 | { LogicalType::INTEGER, 122 | LogicalType::ENUM("SIZE_UNIT", trace_size_unit_enum, 2) }); 123 | 124 | return rfc_set_maximum_trace_file_size; 125 | } 126 | 127 | PragmaFunction CreateRfcSetMaximumStoredTraceFilesPragma() 128 | { 129 | auto rfc_set_maximum_stored_trace_files = PragmaFunction::PragmaCall("sap_rfc_set_maximum_stored_trace_files", 130 | PragmaSetMaximumStoredTraceFiles, 131 | { LogicalType::INTEGER }); 132 | 133 | return rfc_set_maximum_stored_trace_files; 134 | } 135 | 136 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/sap_secret.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "duckdb/main/extension_util.hpp" 3 | 4 | #include "sap_secret.hpp" 5 | #include "sap_connection.hpp" 6 | 7 | namespace duckdb { 8 | 9 | unique_ptr CreateSapSecretFunction(ClientContext &context, CreateSecretInput &input) { 10 | // apply any overridden settings 11 | vector prefix_paths; 12 | auto result = make_uniq(prefix_paths, "sap_rfc", "config", input.name); 13 | for (const auto &named_param : input.options) { 14 | auto lower_name = StringUtil::Lower(named_param.first); 15 | 16 | if (lower_name == "ashost") { 17 | result->secret_map["ashost"] = named_param.second.ToString(); 18 | } else if (lower_name == "sysnr") { 19 | result->secret_map["sysnr"] = named_param.second.ToString(); 20 | } else if (lower_name == "user") { 21 | result->secret_map["user"] = named_param.second.ToString(); 22 | } else if (lower_name == "passwd") { 23 | result->secret_map["passwd"] = named_param.second.ToString(); 24 | } else if (lower_name == "client") { 25 | result->secret_map["client"] = named_param.second.ToString(); 26 | } else if (lower_name == "lang") { 27 | result->secret_map["lang"] = named_param.second.ToString(); 28 | } else if (lower_name == "mshost") { 29 | result->secret_map["mshost"] = named_param.second.ToString(); 30 | } else if (lower_name == "msserv") { 31 | result->secret_map["msserv"] = named_param.second.ToString(); 32 | } else if (lower_name == "sysid") { 33 | result->secret_map["sysid"] = named_param.second.ToString(); 34 | } else if (lower_name == "group") { 35 | result->secret_map["group"] = named_param.second.ToString(); 36 | } else if (lower_name == "snc_qop") { 37 | result->secret_map["snc_qop"] = named_param.second.ToString(); 38 | } else if (lower_name == "snc_myname") { 39 | result->secret_map["snc_myname"] = named_param.second.ToString(); 40 | } else if (lower_name == "snc_partnername") { 41 | result->secret_map["snc_partnername"] = named_param.second.ToString(); 42 | } else if (lower_name == "snc_lib") { 43 | result->secret_map["snc_lib"] = named_param.second.ToString(); 44 | } else if (lower_name == "mysapsso2") { 45 | result->secret_map["mysapsso2"] = named_param.second.ToString(); 46 | } else { 47 | throw InternalException("Unknown named parameter passed to CreateSapSecretFunction: " + lower_name); 48 | } 49 | } 50 | 51 | //! Set redact keys 52 | result->redact_keys = {"password"}; 53 | return std::move(result); 54 | } 55 | 56 | void SetSapSecretParameters(CreateSecretFunction &function) { 57 | function.named_parameters["ashost"] = LogicalType::VARCHAR; 58 | function.named_parameters["sysnr"] = LogicalType::VARCHAR; 59 | function.named_parameters["user"] = LogicalType::VARCHAR; 60 | function.named_parameters["passwd"] = LogicalType::VARCHAR; 61 | function.named_parameters["client"] = LogicalType::VARCHAR; 62 | function.named_parameters["lang"] = LogicalType::VARCHAR; 63 | function.named_parameters["mshost"] = LogicalType::VARCHAR; 64 | function.named_parameters["msserv"] = LogicalType::VARCHAR; 65 | function.named_parameters["sysid"] = LogicalType::VARCHAR; 66 | function.named_parameters["group"] = LogicalType::VARCHAR; 67 | function.named_parameters["snc_qop"] = LogicalType::VARCHAR; 68 | function.named_parameters["snc_myname"] = LogicalType::VARCHAR; 69 | function.named_parameters["snc_partnername"] = LogicalType::VARCHAR; 70 | function.named_parameters["snc_lib"] = LogicalType::VARCHAR; 71 | function.named_parameters["mysapsso2"] = LogicalType::VARCHAR; 72 | } 73 | 74 | void RegisterSapSecretType(DatabaseInstance &db) 75 | { 76 | // Register the new type 77 | duckdb::SecretType sap_rfc_secret_type; 78 | sap_rfc_secret_type.name = SAP_SECRET_TYPE_NAME; 79 | sap_rfc_secret_type.deserializer = KeyValueSecret::Deserialize; 80 | sap_rfc_secret_type.default_provider = SAP_SECRET_PROVIDER; 81 | 82 | ExtensionUtil::RegisterSecretType(db, sap_rfc_secret_type); 83 | 84 | CreateSecretFunction sap_rfc_secret_function = {SAP_SECRET_TYPE_NAME, SAP_SECRET_PROVIDER, CreateSapSecretFunction}; 85 | SetSapSecretParameters(sap_rfc_secret_function); 86 | ExtensionUtil::RegisterFunction(db, sap_rfc_secret_function); 87 | } 88 | 89 | RfcAuthParams ConvertSecretToAuthParams(const KeyValueSecret &duck_secret) 90 | { 91 | RfcAuthParams auth_params; 92 | 93 | auto ashost_val = duck_secret.TryGetValue("ashost"); 94 | if (! ashost_val.IsNull()) { 95 | auth_params.ashost = ashost_val.ToString(); 96 | } 97 | 98 | auto sysnr_val = duck_secret.TryGetValue("sysnr"); 99 | if (! sysnr_val.IsNull()) { 100 | auth_params.sysnr = sysnr_val.ToString(); 101 | } 102 | 103 | auto user_val = duck_secret.TryGetValue("user"); 104 | if (! user_val.IsNull()) { 105 | auth_params.user = user_val.ToString(); 106 | } 107 | 108 | auto passwd_val = duck_secret.TryGetValue("passwd"); 109 | if (! passwd_val.IsNull()) { 110 | auth_params.password = passwd_val.ToString(); 111 | } 112 | 113 | auto client_val = duck_secret.TryGetValue("client"); 114 | if (! client_val.IsNull()) { 115 | auth_params.client = client_val.ToString(); 116 | } 117 | 118 | auto lang_val = duck_secret.TryGetValue("lang"); 119 | if (! lang_val.IsNull()) { 120 | auth_params.lang = lang_val.ToString(); 121 | } 122 | 123 | auto mshost_val = duck_secret.TryGetValue("mshost"); 124 | if (! mshost_val.IsNull()) { 125 | auth_params.mshost = mshost_val.ToString(); 126 | } 127 | 128 | auto msserv_val = duck_secret.TryGetValue("msserv"); 129 | if (! msserv_val.IsNull()) { 130 | auth_params.msserv = msserv_val.ToString(); 131 | } 132 | 133 | auto sysid_val = duck_secret.TryGetValue("sysid"); 134 | if (! sysid_val.IsNull()) { 135 | auth_params.sysid = sysid_val.ToString(); 136 | } 137 | 138 | auto group_val = duck_secret.TryGetValue("group"); 139 | if (! group_val.IsNull()) { 140 | auth_params.group = group_val.ToString(); 141 | } 142 | 143 | auto snc_qop_val = duck_secret.TryGetValue("snc_qop"); 144 | if (! snc_qop_val.IsNull()) { 145 | auth_params.snc_qop = snc_qop_val.ToString(); 146 | } 147 | 148 | auto snc_myname_val = duck_secret.TryGetValue("snc_myname"); 149 | if (! snc_myname_val.IsNull()) { 150 | auth_params.snc_myname = snc_myname_val.ToString(); 151 | } 152 | 153 | auto snc_partnername_val = duck_secret.TryGetValue("snc_partnername"); 154 | if (! snc_partnername_val.IsNull()) { 155 | auth_params.snc_partnername = snc_partnername_val.ToString(); 156 | } 157 | 158 | auto snc_lib_val = duck_secret.TryGetValue("snc_lib"); 159 | if (! snc_lib_val.IsNull()) { 160 | auth_params.snc_lib = snc_lib_val.ToString(); 161 | } 162 | 163 | auto mysapsso2_val = duck_secret.TryGetValue("mysapsso2"); 164 | if (! mysapsso2_val.IsNull()) { 165 | auth_params.mysapsso2 = mysapsso2_val.ToString(); 166 | } 167 | 168 | return auth_params; 169 | } 170 | 171 | // ------------------------------------------------------------------------------------------------ 172 | 173 | std::string GetSecretNameFromParams(const TableFunctionBindInput &input) 174 | { 175 | return GetSecretNameFromParams(input.named_parameters); 176 | } 177 | 178 | std::string GetSecretNameFromParams(const FunctionParameters ¶meters) 179 | { 180 | return GetSecretNameFromParams(parameters.named_parameters); 181 | } 182 | 183 | std::string GetSecretNameFromParams(const named_parameter_map_t &named_params) 184 | { 185 | if (named_params.find("secret") != named_params.end()) { 186 | auto val = named_params.at("secret").ToString(); 187 | return val; 188 | } else { 189 | return std::string(); 190 | } 191 | } 192 | 193 | RfcAuthParams GetAuthParamsFromContext(ClientContext &context, const TableFunctionBindInput ¶meters) 194 | { 195 | auto secret_name = GetSecretNameFromParams(parameters); 196 | return GetAuthParamsFromContext(context, secret_name); 197 | } 198 | 199 | RfcAuthParams GetAuthParamsFromContext(ClientContext &context, const FunctionParameters ¶meters) 200 | { 201 | auto secret_name = GetSecretNameFromParams(parameters); 202 | return GetAuthParamsFromContext(context, secret_name); 203 | } 204 | 205 | RfcAuthParams GetAuthParamsFromContext(ClientContext &context, const std::string &secret_name) 206 | { 207 | RfcAuthParams auth_params; 208 | if (!secret_name.empty()) { 209 | // Use secret for connection 210 | auth_params = RfcAuthParams::FromContext(context, secret_name); 211 | } else { 212 | // Use context settings for connection 213 | auth_params = RfcAuthParams::FromContext(context); 214 | } 215 | return auth_params; 216 | } 217 | 218 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_describe_fields.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "scanner_describe_fields.hpp" 3 | #include "telemetry.hpp" 4 | 5 | namespace duckdb 6 | { 7 | static std::vector CreateFunctionArguments(TableFunctionBindInput &input) { 8 | child_list_t args; 9 | 10 | 11 | args.push_back(make_pair("TABNAME", input.inputs[0])) ; 12 | 13 | auto &named_params = input.named_parameters; 14 | 15 | if (named_params.find("LANGUAGE") != named_params.end()) { 16 | args.push_back(make_pair("LANGU", named_params["LANGUAGE"])) ; 17 | } else { 18 | args.push_back(make_pair("LANGU", Value::CreateValue("E"))) ; 19 | } 20 | 21 | return std::vector( { Value::STRUCT(args) }); 22 | } 23 | 24 | static unique_ptr RfcDescribeFieldsBind(ClientContext &context, 25 | TableFunctionBindInput &input, 26 | vector &return_types, 27 | vector &names) 28 | { 29 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_describe_fields"); 30 | 31 | // Connect to the SAP system 32 | auto connection = RfcAuthParams::FromContext(context).Connect(); 33 | 34 | // Create the function 35 | auto func = std::make_shared(connection, "DDIF_FIELDINFO_GET"); 36 | auto func_args = CreateFunctionArguments(input); 37 | auto invocation = func->BeginInvocation(func_args); 38 | auto result_set = invocation->Invoke("/DFIES_TAB"); 39 | 40 | // Create the bind data 41 | std::vector> selected_fields = { 42 | {"POSITION", "pos"}, 43 | {"KEYFLAG", "is_key"}, 44 | {"FIELDNAME", "field"}, 45 | {"FIELDTEXT", "text"}, 46 | {"DATATYPE", "sap_type"}, 47 | {"LENG", "length"}, 48 | {"DECIMALS", "decimals"}, 49 | {"CHECKTABLE", "check_table"}, 50 | {"REFTABLE", "ref_table"}, 51 | {"REFFIELD", "ref_field"}, 52 | {"LANGU", "language"} 53 | }; 54 | auto bind_data = make_uniq(selected_fields); 55 | bind_data->invocation = invocation; 56 | bind_data->result_set = result_set; 57 | names = bind_data->GetResultNames(); 58 | return_types = bind_data->GetResultTypes(); 59 | 60 | return std::move(bind_data); 61 | } 62 | 63 | static void RfcDescribeFieldsScan(ClientContext &context, 64 | TableFunctionInput &data, 65 | DataChunk &output) 66 | { 67 | auto &bind_data = data.bind_data->CastNoConst(); 68 | if (! bind_data.HasMoreResults()) { 69 | return; 70 | } 71 | 72 | bind_data.FetchNextResult(output); 73 | } 74 | 75 | TableFunction CreateRfcDescribeFieldsScanFunction() 76 | { 77 | auto fun = TableFunction("sap_describe_fields", {LogicalType::VARCHAR}, RfcDescribeFieldsScan, RfcDescribeFieldsBind); 78 | fun.projection_pushdown = false; 79 | 80 | return fun; 81 | } 82 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_describe_function.cpp: -------------------------------------------------------------------------------- 1 | #include "scanner_describe_function.hpp" 2 | #include "telemetry.hpp" 3 | 4 | namespace duckdb 5 | { 6 | 7 | static std::vector CreateFunctionArguments(std::string &func_name) 8 | { 9 | return ArgBuilder() 10 | .Add("FUNCTIONNAME", func_name) 11 | .BuildArgList(); 12 | } 13 | 14 | static unique_ptr RfcDescribeFunctionBind(ClientContext &context, 15 | TableFunctionBindInput &input, 16 | vector &return_types, 17 | vector &names) 18 | { 19 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_describe_function"); 20 | 21 | auto &inputs = input.inputs; 22 | 23 | // Connect to the SAP system 24 | auto connection = RfcAuthParams::FromContext(context).Connect(); 25 | 26 | // Create the function 27 | auto func_name = inputs[0].ToString(); 28 | auto func_handle = std::make_shared(connection, func_name); 29 | 30 | auto func_args = CreateFunctionArguments(func_name); 31 | auto result_set = RfcResultSet::InvokeFunction(connection, "RPY_FUNCTIONMODULE_READ", func_args); 32 | 33 | auto bind_data = RfcDescribeFunctionBindData::FromFunctionHandleAndResultSet(func_handle, result_set); 34 | names = bind_data->GetResultNames(); 35 | return_types = bind_data->GetResultTypes(); 36 | 37 | return std::move(bind_data); 38 | } 39 | 40 | static void RfcDescribeFunctionScan(ClientContext &context, 41 | TableFunctionInput &data, 42 | DataChunk &output) 43 | { 44 | auto &bind_data = data.bind_data->CastNoConst(); 45 | if (! bind_data.HasMoreResults()) { 46 | return; 47 | } 48 | 49 | bind_data.FetchNextResult(output); 50 | } 51 | 52 | TableFunction CreateRfcDescribeFunctionScanFunction() 53 | { 54 | auto fun = TableFunction("sap_rfc_describe_function", {LogicalType::VARCHAR}, RfcDescribeFunctionScan, RfcDescribeFunctionBind); 55 | return fun; 56 | } 57 | 58 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_describe_references.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "scanner_show_tables.hpp" 3 | #include "telemetry.hpp" 4 | 5 | namespace duckdb 6 | { 7 | static unique_ptr RfcDescribeReferencesBind(ClientContext &context, 8 | TableFunctionBindInput &input, 9 | vector &return_types, 10 | vector &names) 11 | { 12 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_describe_references"); 13 | 14 | auto bind_data = make_uniq(); 15 | return std::move(bind_data); 16 | } 17 | 18 | static void RfcDescribeReferencesScan(ClientContext &context, 19 | TableFunctionInput &data, 20 | DataChunk &output) 21 | { 22 | 23 | } 24 | 25 | TableFunction CreateRfcDescribeReferencessScanFunction() 26 | { 27 | auto fun = TableFunction("sap_describe_references", {}, RfcDescribeReferencesScan, RfcDescribeReferencesBind); 28 | fun.projection_pushdown = false; 29 | 30 | return fun; 31 | } 32 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_invoke.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "scanner_invoke.hpp" 3 | #include "sap_secret.hpp" 4 | #include "telemetry.hpp" 5 | 6 | namespace duckdb 7 | { 8 | std::string GetPathNamedParam(TableFunctionBindInput &input) { 9 | auto &named_params = input.named_parameters; 10 | if (named_params.find("path") != named_params.end()) { 11 | return named_params["path"].GetValue(); 12 | } else { 13 | return std::string(); 14 | } 15 | } 16 | 17 | /** 18 | * @brief (Step 1) Binds the input arguments to the function. 19 | */ 20 | static unique_ptr RfcInvokeBind(ClientContext &context, 21 | TableFunctionBindInput &input, 22 | vector &return_types, 23 | vector &names) 24 | { 25 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_invoke"); 26 | 27 | auto &inputs = input.inputs; 28 | 29 | // Connect to the SAP system 30 | auto connection = GetAuthParamsFromContext(context, input).Connect(); 31 | 32 | // Create the function 33 | auto func_name = inputs[0].GetValue(); 34 | auto func_args = std::vector(inputs.begin()+1, inputs.end()); 35 | auto path = GetPathNamedParam(input); 36 | auto result_set = RfcResultSet::InvokeFunction(connection, func_name, func_args, path); 37 | names = result_set->GetResultNames(); 38 | return_types = result_set->GetResultTypes(); 39 | 40 | // Make this invocation available to the bind data 41 | auto bind_data = make_uniq(); 42 | bind_data->result_set = result_set; 43 | 44 | return std::move(bind_data); 45 | } 46 | 47 | /** 48 | * @brief (Step 2) Scans the function and returns the next chunk of data. 49 | */ 50 | static void RfcInvokeScan(ClientContext &context, 51 | TableFunctionInput &data, 52 | DataChunk &output) 53 | { 54 | auto &bind_data = data.bind_data->Cast(); 55 | auto result_set = bind_data.result_set; 56 | 57 | if (! result_set->HasMoreResults()) { 58 | return; 59 | } 60 | 61 | result_set->FetchNextResult(output); 62 | } 63 | 64 | TableFunction CreateRfcInvokeScanFunction() 65 | { 66 | auto fun = TableFunction("sap_rfc_invoke", { LogicalType::VARCHAR }, RfcInvokeScan, RfcInvokeBind); 67 | fun.projection_pushdown = false; 68 | fun.varargs = LogicalType::ANY; 69 | fun.named_parameters["path"] = LogicalType::VARCHAR; 70 | fun.named_parameters["secret"] = LogicalType::VARCHAR; 71 | 72 | return fun; 73 | } 74 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_read_table.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "duckdb/parallel/pipeline.hpp" 4 | #include "duckdb/parallel/event.hpp" 5 | 6 | #include "scanner_read_table.hpp" 7 | #include "duckdb_argument_helper.hpp" 8 | #include "sap_rfc.hpp" 9 | #include "telemetry.hpp" 10 | 11 | namespace duckdb 12 | { 13 | static unique_ptr RfcReadTableBind(ClientContext &context, 14 | TableFunctionBindInput &input, 15 | vector &return_types, 16 | vector &names) 17 | { 18 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_read_table"); 19 | 20 | auto table_name = input.inputs[0].ToString(); 21 | auto &named_params = input.named_parameters; 22 | auto max_read_threads = named_params.find("THREADS") != named_params.end() 23 | ? named_params["THREADS"].GetValue() 24 | : 0; 25 | auto limit = named_params.find("MAX_ROWS") != named_params.end() 26 | ? named_params["MAX_ROWS"].GetValue() 27 | : 0; 28 | auto where_clause = named_params.find("FILTER") != named_params.end() 29 | ? named_params["FILTER"].ToString() 30 | : ""; 31 | 32 | auto fields = named_params.find("COLUMNS") != named_params.end() 33 | ? ConvertListValueToVector(named_params["COLUMNS"]) 34 | : std::vector(); 35 | 36 | auto bind_data = make_uniq(table_name, max_read_threads, limit, &DefaultRfcConnectionFactory, context); 37 | bind_data->InitOptionsFromWhereClause(where_clause); 38 | bind_data->InitAndVerifyFields(fields); 39 | 40 | names = bind_data->GetRfcColumnNames(); 41 | return_types = bind_data->GetReturnTypes(); 42 | 43 | return std::move(bind_data); 44 | } 45 | 46 | static unique_ptr RfcReadTableInitGlobalState(ClientContext &context, 47 | TableFunctionInitInput &input) 48 | { 49 | auto &bind_data = input.bind_data->CastNoConst(); 50 | auto column_ids = input.column_ids; 51 | 52 | bind_data.ActivateColumns(column_ids); 53 | bind_data.AddOptionsFromFilters(input.filters); 54 | 55 | return make_uniq(); 56 | } 57 | 58 | static void RfcReadTableScan(ClientContext &context, 59 | TableFunctionInput &data, 60 | DataChunk &output) 61 | { 62 | auto &bind_data = data.bind_data->CastNoConst(); 63 | if (! bind_data.HasMoreResults()) { 64 | return; 65 | } 66 | 67 | //printf(">> RfcReadTableScan\n"); 68 | bind_data.Step(context, output); 69 | } 70 | 71 | double RfcReadTableProgress(ClientContext &, const FunctionData *func_data, const GlobalTableFunctionState *) 72 | { 73 | 74 | auto &bind_data = func_data->CastNoConst(); 75 | auto progress = bind_data.GetProgress(); 76 | 77 | //printf(">> RfcReadTableProgress %f\n", progress); 78 | return progress; 79 | } 80 | 81 | TableFunction CreateRfcReadTableScanFunction() 82 | { 83 | auto fun = TableFunction("sap_read_table", { LogicalType::VARCHAR }, 84 | RfcReadTableScan, 85 | RfcReadTableBind, 86 | RfcReadTableInitGlobalState); 87 | fun.named_parameters["THREADS"] = LogicalType::UINTEGER; 88 | fun.named_parameters["COLUMNS"] = LogicalType::LIST(LogicalType::VARCHAR); 89 | fun.named_parameters["FILTER"] = LogicalType::VARCHAR; 90 | fun.named_parameters["MAX_ROWS"] = LogicalType::UINTEGER; 91 | fun.table_scan_progress = RfcReadTableProgress; 92 | fun.projection_pushdown = true; 93 | fun.filter_pushdown = true; 94 | 95 | return fun; 96 | } 97 | 98 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_show_functions.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "scanner_show_functions.hpp" 3 | #include "telemetry.hpp" 4 | 5 | namespace duckdb 6 | { 7 | static std::vector CreateFunctionArguments(TableFunctionBindInput &input) { 8 | child_list_t args; 9 | 10 | auto &named_params = input.named_parameters; 11 | if (named_params.find("FUNCNAME") != named_params.end()) { 12 | args.push_back(make_pair("FUNCNAME", named_params["FUNCNAME"])) ; 13 | } else { 14 | args.push_back(make_pair("FUNCNAME", Value::CreateValue("*"))); 15 | } 16 | 17 | if (named_params.find("GROUPNAME") != named_params.end()) { 18 | args.push_back(make_pair("GROUPNAME", named_params["GROUPNAME"])) ; 19 | } 20 | 21 | if (named_params.find("LANGUAGE") != named_params.end()) { 22 | args.push_back(make_pair("LANGUAGE", named_params["LANGUAGE"])) ; 23 | } 24 | 25 | return std::vector( { Value::STRUCT(args) }); 26 | } 27 | 28 | /** 29 | * @brief (Step 1) Binds the input arguments to the function. 30 | */ 31 | static unique_ptr RfcShowTablesBind(ClientContext &context, 32 | TableFunctionBindInput &input, 33 | vector &return_types, 34 | vector &names) 35 | { 36 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_search_function"); 37 | 38 | // Connect to the SAP system 39 | auto connection = RfcAuthParams::FromContext(context).Connect(); 40 | 41 | // Create the function 42 | auto func_args = CreateFunctionArguments(input); 43 | auto result_set = RfcResultSet::InvokeFunction(connection, "RFC_FUNCTION_SEARCH", func_args, "/FUNCTIONS"); 44 | names = result_set->GetResultNames(); 45 | return_types = result_set->GetResultTypes(); 46 | 47 | auto bind_data = make_uniq(); 48 | bind_data->result_set = result_set; 49 | 50 | return std::move(bind_data); 51 | } 52 | 53 | /** 54 | * @brief (Step 2) Initializes the global state of the function. 55 | */ 56 | static void RfcShowTablesScan(ClientContext &context, 57 | TableFunctionInput &data, 58 | DataChunk &output) 59 | { 60 | auto &bind_data = data.bind_data->Cast(); 61 | auto result_set = bind_data.result_set; 62 | 63 | if (! result_set->HasMoreResults()) { 64 | return; 65 | } 66 | 67 | result_set->FetchNextResult(output); 68 | } 69 | 70 | TableFunction CreateRfcShowFunctionScanFunction() 71 | { 72 | auto fun = TableFunction("sap_rfc_show_function", {}, RfcShowTablesScan, RfcShowTablesBind); 73 | fun.named_parameters["FUNCNAME"] = LogicalType::VARCHAR; 74 | fun.named_parameters["GROUPNAME"] = LogicalType::VARCHAR; 75 | fun.named_parameters["LANGUAGE"] = LogicalType::VARCHAR; 76 | fun.projection_pushdown = false; 77 | 78 | return fun; 79 | } 80 | 81 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_show_groups.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "scanner_show_groups.hpp" 3 | #include "telemetry.hpp" 4 | 5 | namespace duckdb 6 | { 7 | static std::vector CreateFunctionArguments(TableFunctionBindInput &input) { 8 | child_list_t args; 9 | 10 | auto &named_params = input.named_parameters; 11 | if (named_params.find("GROUPNAME") != named_params.end()) { 12 | args.push_back(make_pair("GROUPNAME", named_params["GROUPNAME"])) ; 13 | } 14 | else { 15 | args.push_back(make_pair("GROUPNAME", Value::CreateValue("*"))); 16 | } 17 | 18 | if (named_params.find("LANGUAGE") != named_params.end()) { 19 | args.push_back(make_pair("LANGUAGE", named_params["LANGUAGE"])) ; 20 | } 21 | 22 | return std::vector( { Value::STRUCT(args) }); 23 | } 24 | 25 | /** 26 | * @brief (Step 1) Binds the input arguments to the function. 27 | */ 28 | static unique_ptr RfcShowGroupBind(ClientContext &context, 29 | TableFunctionBindInput &input, 30 | vector &return_types, 31 | vector &names) 32 | { 33 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_rfc_search_group"); 34 | 35 | // Connect to the SAP system 36 | auto connection = RfcAuthParams::FromContext(context).Connect(); 37 | 38 | // Create the function 39 | auto func_args = CreateFunctionArguments(input); 40 | auto result_set = RfcResultSet::InvokeFunction(connection, "RFC_GROUP_SEARCH", func_args, "/GROUPS"); 41 | names = std::vector( { "name", "text" }); 42 | return_types = result_set->GetResultTypes(); 43 | 44 | auto bind_data = make_uniq(); 45 | bind_data->result_set = result_set; 46 | 47 | return std::move(bind_data); 48 | } 49 | 50 | static void RfcShowGroupScan(ClientContext &context, 51 | TableFunctionInput &data, 52 | DataChunk &output) 53 | { 54 | auto &bind_data = data.bind_data->Cast(); 55 | auto result_set = bind_data.result_set; 56 | 57 | if (! result_set->HasMoreResults()) { 58 | return; 59 | } 60 | 61 | result_set->FetchNextResult(output); 62 | } 63 | 64 | TableFunction CreateRfcShowGroupScanFunction() 65 | { 66 | auto fun = TableFunction("sap_rfc_show_groups", {}, RfcShowGroupScan, RfcShowGroupBind); 67 | fun.named_parameters["GROUPNAME"] = LogicalType::VARCHAR; 68 | fun.named_parameters["LANGUAGE"] = LogicalType::VARCHAR; 69 | 70 | return fun; 71 | } 72 | 73 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/scanner_show_tables.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "duckdb/parallel/pipeline.hpp" 4 | #include "duckdb/parallel/event.hpp" 5 | 6 | #include "scanner_show_tables.hpp" 7 | #include "duckdb_argument_helper.hpp" 8 | #include "sap_rfc.hpp" 9 | #include "telemetry.hpp" 10 | 11 | namespace duckdb 12 | { 13 | 14 | static std::string GetSearchString(const std::string param_name, 15 | const TableFunctionBindInput &input) 16 | { 17 | auto &named_params = input.named_parameters; 18 | auto search_string = named_params.find(param_name) != named_params.end() 19 | ? named_params[param_name].ToString() : "%"; 20 | search_string = std::regex_replace(search_string, std::regex("\\*"), "%"); 21 | return search_string; 22 | } 23 | 24 | static unique_ptr RfcShowTablesBind(ClientContext &context, 25 | TableFunctionBindInput &input, 26 | vector &return_types, 27 | vector &names) 28 | { 29 | PostHogTelemetry::Instance().CaptureFunctionExecution("sap_show_tables"); 30 | 31 | auto &named_params = input.named_parameters; 32 | auto table_search_str = GetSearchString("TABLENAME", input); 33 | auto text_search_str = GetSearchString("TEXT", input); 34 | auto max_read_threads = named_params.find("THREADS") != named_params.end() 35 | ? named_params["THREADS"].GetValue() 36 | : 0; 37 | 38 | auto where_clause = StringUtil::Format( 39 | "DDLANGUAGE = 'E' AND " 40 | "TABNAME LIKE '%s' AND ( TABCLASS = 'VIEW' OR TABCLASS = 'TRANSP' OR " 41 | "TABCLASS = 'POOL' OR TABCLASS = 'CLUSTER' ) AND DDTEXT LIKE '%s'", 42 | table_search_str, text_search_str 43 | ); 44 | 45 | auto fields = std::vector({ "TABNAME", "DDTEXT", "TABCLASS" }); 46 | auto result = make_uniq("DD02V", max_read_threads, 0, &DefaultRfcConnectionFactory, context); 47 | result->InitOptionsFromWhereClause(where_clause); 48 | result->InitAndVerifyFields(fields); 49 | 50 | names = { "table_name", "text", "class" }; 51 | return_types = result->GetReturnTypes(); 52 | 53 | return std::move(result); 54 | } 55 | 56 | static unique_ptr RfcShowTablesInitGlobalState(ClientContext &context, 57 | TableFunctionInitInput &input) 58 | { 59 | auto &bind_data = input.bind_data->CastNoConst(); 60 | auto column_ids = input.column_ids; 61 | 62 | bind_data.ActivateColumns(column_ids); 63 | 64 | return make_uniq(); 65 | } 66 | 67 | static void RfcShowTablesScan(ClientContext &context, 68 | TableFunctionInput &data, 69 | DataChunk &output) 70 | { 71 | auto &bind_data = data.bind_data->CastNoConst(); 72 | 73 | if (! bind_data.HasMoreResults()) { 74 | return; 75 | } 76 | 77 | bind_data.Step(context, output); 78 | } 79 | 80 | TableFunction CreateRfcShowTablesScanFunction() 81 | { 82 | auto fun = TableFunction("sap_show_tables", { }, 83 | RfcShowTablesScan, 84 | RfcShowTablesBind, 85 | RfcShowTablesInitGlobalState); 86 | fun.named_parameters["TABLENAME"] = LogicalType::VARCHAR; 87 | fun.named_parameters["TEXT"] = LogicalType::VARCHAR; 88 | fun.named_parameters["THREADS"] = LogicalType::UINTEGER; 89 | fun.projection_pushdown = true; 90 | 91 | return TableFunction(fun); 92 | } 93 | 94 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/src/telemetry.cpp: -------------------------------------------------------------------------------- 1 | #define NOMINMAX 2 | 3 | #include "telemetry.hpp" 4 | 5 | #ifdef __linux__ 6 | 7 | #include 8 | 9 | #elif _WIN32 10 | 11 | #ifndef _MSC_VER 12 | #define _MSC_VER 1936 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | #elif __APPLE__ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #endif 28 | 29 | #define CPPHTTPLIB_OPENSSL_SUPPORT 30 | #include "httplib.hpp" 31 | 32 | namespace duckdb { 33 | 34 | std::string PostHogEvent::GetPropertiesJson() const 35 | { 36 | std::string json = "{"; 37 | bool first = true; 38 | for (auto &kv : properties) 39 | { 40 | if (!first) { 41 | json += ","; 42 | } 43 | json += StringUtil::Format("\"%s\": \"%s\"", kv.first, kv.second); 44 | first = false; 45 | } 46 | json += "}"; 47 | return json; 48 | } 49 | 50 | std::string PostHogEvent::GetNowISO8601() const 51 | { 52 | std::time_t t = std::time(nullptr); 53 | std::tm tm = *std::localtime(&t); 54 | char buffer[32]; 55 | std::strftime(buffer, sizeof(buffer), "%FT%TZ", &tm); 56 | return std::string(buffer); 57 | } 58 | 59 | // PostHogEvent ----------------------------------------------------------------- 60 | 61 | void PostHogProcess(const std::string api_key, const PostHogEvent &event) 62 | { 63 | auto re = duckdb_re2::Regex("*"); 64 | std::string payload = StringUtil::Format(R"( 65 | { 66 | "api_key": "%s", 67 | "batch": [{ 68 | "event": "%s", 69 | "distinct_id": "%s", 70 | "properties": %s, 71 | "timestamp": "%s" 72 | }] 73 | } 74 | )", api_key, event.event_name, event.distinct_id, 75 | event.GetPropertiesJson(), event.GetNowISO8601()); 76 | 77 | auto cli = duckdb_httplib_openssl::Client("https://eu.posthog.com"); 78 | auto url = "/batch/"; 79 | if (cli.is_valid() == false) { 80 | throw new std::runtime_error("Invalid client"); 81 | } 82 | auto res = cli.Post(url, payload, "application/json"); 83 | if (res && res->status != 200) { 84 | throw new std::runtime_error(StringUtil::Format("Sending posthog event failed: %s", res->body)); 85 | } 86 | 87 | cli.stop(); 88 | } 89 | 90 | // PostHogProcess ---------------------------------------------------------------- 91 | 92 | PostHogTelemetry::PostHogTelemetry() 93 | : _queue(TelemetryTaskQueue()), 94 | _telemetry_enabled(true), 95 | _api_key("phc_t3wwRLtpyEmLHYaZCSszG0MqVr74J6wnCrj9D41zk2t") 96 | { } 97 | 98 | PostHogTelemetry::~PostHogTelemetry() 99 | { } 100 | 101 | PostHogTelemetry& PostHogTelemetry::Instance() 102 | { 103 | static PostHogTelemetry instance; 104 | return instance; 105 | } 106 | 107 | void PostHogTelemetry::CaptureExtensionLoad() 108 | { 109 | CaptureExtensionLoad("erpl_rfc"); 110 | } 111 | 112 | void PostHogTelemetry::CaptureExtensionLoad(std::string extension_name) 113 | { 114 | if (!_telemetry_enabled) { 115 | return; 116 | } 117 | 118 | PostHogEvent event = { 119 | "extension_load", 120 | GetMacAddressSafe(), 121 | { 122 | {"extension_name", extension_name}, 123 | {"extension_version", "0.1.0"}, 124 | {"extension_platform", DuckDB::Platform() } 125 | } 126 | }; 127 | auto api_key = this->_api_key; 128 | _queue.EnqueueTask([api_key](auto event) { PostHogProcess(api_key, event); }, event); 129 | } 130 | 131 | void PostHogTelemetry::CaptureFunctionExecution(std::string function_name) 132 | { 133 | if (!_telemetry_enabled) { 134 | return; 135 | } 136 | 137 | PostHogEvent event = { 138 | "function_execution", 139 | GetMacAddressSafe(), 140 | { 141 | {"function_name", function_name}, 142 | {"function_version", "0.1.0"} 143 | } 144 | }; 145 | auto api_key = this->_api_key; 146 | _queue.EnqueueTask([api_key](auto event) { PostHogProcess(api_key, event); }, event); 147 | } 148 | 149 | 150 | bool PostHogTelemetry::IsEnabled() 151 | { 152 | return _telemetry_enabled; 153 | } 154 | 155 | void PostHogTelemetry::SetEnabled(bool enabled) 156 | { 157 | std::lock_guard t(_thread_lock); 158 | _telemetry_enabled = enabled; 159 | } 160 | 161 | std::string PostHogTelemetry::GetAPIKey() 162 | { 163 | return _api_key; 164 | } 165 | 166 | void PostHogTelemetry::SetAPIKey(std::string new_key) 167 | { 168 | std::lock_guard t(_thread_lock); 169 | _api_key = new_key; 170 | } 171 | 172 | std::string PostHogTelemetry::GetMacAddressSafe() 173 | { 174 | try { 175 | return GetMacAddress(); 176 | } catch (std::exception &e) { 177 | return "00:00:00:00:00:00"; 178 | } 179 | } 180 | 181 | #ifdef __linux__ 182 | 183 | std::string PostHogTelemetry::GetMacAddress() 184 | { 185 | auto device = FindFirstPhysicalDevice(); 186 | if (device.empty()) { 187 | return "00:00:00:00:00:00"; 188 | } 189 | 190 | std::ifstream file(StringUtil::Format("/sys/class/net/%s/address", device)); 191 | 192 | std::string mac_address; 193 | if (file >> mac_address) { 194 | return mac_address; 195 | } 196 | 197 | throw new std::runtime_error(StringUtil::Format("Could not read mac address of device %s", device)); 198 | } 199 | 200 | bool PostHogTelemetry::IsPhysicalDevice(const std::string& device) { 201 | std::string path = StringUtil::Format("/sys/class/net/%s/device/driver", device); 202 | return access(path.c_str(), F_OK) != -1; 203 | } 204 | 205 | std::string PostHogTelemetry::FindFirstPhysicalDevice() 206 | { 207 | DIR* dir = opendir("/sys/class/net"); 208 | if (!dir) { 209 | throw new std::runtime_error("Could not open /sys/class/net"); 210 | } 211 | 212 | std::vector devices; 213 | struct dirent* entry; 214 | while ((entry = readdir(dir)) != NULL) { 215 | if (entry->d_type == DT_LNK || entry->d_type == DT_DIR) { 216 | std::string device = entry->d_name; 217 | if (device != "." && device != "..") { 218 | devices.push_back(device); 219 | } 220 | } 221 | } 222 | closedir(dir); 223 | 224 | std::sort(devices.begin(), devices.end()); 225 | 226 | for (const std::string& device : devices) { 227 | if (IsPhysicalDevice(device)) { 228 | return device; 229 | } 230 | } 231 | 232 | return ""; 233 | } 234 | 235 | #elif _WIN32 236 | 237 | std::string PostHogTelemetry::GetMacAddress() 238 | { 239 | ULONG out_buf_len = sizeof(IP_ADAPTER_INFO); 240 | std::vector buffer(out_buf_len); 241 | 242 | auto adapter_info = reinterpret_cast(buffer.data()); 243 | if (GetAdaptersInfo(adapter_info, &out_buf_len) == ERROR_BUFFER_OVERFLOW) { 244 | buffer.resize(out_buf_len); 245 | adapter_info = reinterpret_cast(buffer.data()); 246 | } 247 | 248 | DWORD ret = GetAdaptersInfo(adapter_info, &out_buf_len); 249 | if (ret != NO_ERROR) { 250 | return ""; 251 | } 252 | 253 | std::vector mac_addresses; 254 | PIP_ADAPTER_INFO adapter = adapter_info; 255 | while (adapter) { 256 | std::ostringstream str_buf; 257 | for (UINT i = 0; i < adapter->AddressLength; i++) { 258 | str_buf << std::setw(2) << std::setfill('0') << std::hex << static_cast(adapter->Address[i]); 259 | if (i != (adapter->AddressLength - 1)) str_buf << '-'; 260 | } 261 | mac_addresses.push_back(str_buf.str()); 262 | adapter = adapter->Next; 263 | } 264 | 265 | return mac_addresses.empty() ? "" : mac_addresses.front(); 266 | } 267 | 268 | #elif __APPLE__ 269 | 270 | std::string PostHogTelemetry::GetMacAddress() 271 | { 272 | struct ifaddrs *ifap, *ifa; 273 | struct sockaddr_dl *sdl = nullptr; 274 | char mac_address[18] = {0}; 275 | 276 | if (getifaddrs(&ifap) != 0) { 277 | throw std::runtime_error("getifaddrs() failed!"); 278 | } 279 | 280 | for (ifa = ifap; ifa; ifa = ifa->ifa_next) { 281 | if (ifa->ifa_addr->sa_family == AF_LINK && strcmp(ifa->ifa_name, "en0") == 0) { 282 | sdl = (struct sockaddr_dl *)ifa->ifa_addr; 283 | break; 284 | } 285 | } 286 | 287 | if (sdl && sdl->sdl_alen == 6) { 288 | unsigned char *ptr = (unsigned char *)LLADDR(sdl); 289 | snprintf(mac_address, sizeof(mac_address), "%02x:%02x:%02x:%02x:%02x:%02x", 290 | ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); 291 | } else { 292 | strncpy(mac_address, "00:00:00:00:00:00", sizeof(mac_address)); 293 | } 294 | 295 | freeifaddrs(ifap); 296 | 297 | return std::string(mac_address); 298 | } 299 | 300 | #else 301 | 302 | std::string PostHogTelemetry::GetMacAddress() 303 | { 304 | return ""; 305 | } 306 | 307 | #endif 308 | 309 | // PostHogTelemetry -------------------------------------------------------- 310 | 311 | } // namespace duckdb -------------------------------------------------------------------------------- /rfc/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(cpp) -------------------------------------------------------------------------------- /rfc/test/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(OpenSSL REQUIRED) 2 | add_definitions(-DSAPonUNIX -DSAPwithUNICODE -DSAPwithTHREADS -DSAPonLIN) 3 | 4 | include_directories(./include 5 | ../../src/include 6 | ../../src/bics/include 7 | ../../src/odp/include 8 | ../../../duckdb/third_party/catch/ 9 | ../../../duckdb/third_party/httplib/ 10 | ../../../duckdb/test/include 11 | ../../../duckdb/extension/json/include 12 | ../../../duckdb/extension/json/yyjson/include 13 | ${SAPNWRFC_HOME}/include 14 | ${OPENSSL_INCLUDE_DIR}) 15 | 16 | set(TEST_SOURCES 17 | test_type_conversion.cpp 18 | test_serialization_helper.cpp 19 | test_value_helper.cpp 20 | test_table_wrapper.cpp 21 | test_telemetry.cpp 22 | test_main.cpp 23 | ) 24 | 25 | add_executable(erpl_rfc_tests ${TEST_SOURCES}) 26 | target_link_libraries(erpl_rfc_tests ${EXTENSION_NAME} duckdb_static test_helpers ${SAPNWRFC_LIB_FILES} ${OPENSSL_LIBRARIES}) -------------------------------------------------------------------------------- /rfc/test/cpp/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | #include 3 | #include "catch.hpp" 4 | 5 | int main(int argc, char *argv[]) { 6 | std::cout << std::endl << "**** ERPL CPP Unit Tests ****" << std::endl << std::endl; 7 | 8 | int result = Catch::Session().run( argc, argv ); 9 | return result; 10 | } 11 | -------------------------------------------------------------------------------- /rfc/test/cpp/test_table_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test_helpers.hpp" 3 | #include "duckdb.hpp" 4 | 5 | #include "duckdb_argument_helper.hpp" 6 | 7 | using namespace duckdb; 8 | using namespace std; 9 | 10 | Value CreateTestTable() 11 | { 12 | auto builder = ArgBuilder(); 13 | builder.Add("NODE_ID", Value::LIST({Value::CreateValue("1"), Value::CreateValue("2")})); 14 | builder.Add("PARENT_ID", Value::LIST({Value::CreateValue(""), Value::CreateValue("")})); 15 | builder.Add("ROLE_KEY", Value::LIST({Value::CreateValue(""), Value::CreateValue("")})); 16 | builder.Add("OBJ_KEY", Value::LIST({Value::CreateValue("0BCT_CB"), Value::CreateValue("0BW")})); 17 | builder.Add("OBJ_TYPE", Value::LIST({Value::CreateValue("AREA"), Value::CreateValue("AREA")})); 18 | builder.Add("OBJ_TEXT", Value::LIST({Value::CreateValue("Content Browser"), Value::CreateValue("Business Information Warehouse")})); 19 | builder.Add("OBJ_DESC", Value::LIST({Value::CreateValue("Content Browser"), Value::CreateValue("Business Information Warehouse")})); 20 | builder.Add("OBJ_TECHNAME", Value::LIST({Value::CreateValue("0BCT_CB"), Value::CreateValue("0BW")})); 21 | builder.Add("SUBOBJECT_TYPE", Value::LIST({Value::CreateValue("AREA"), Value::CreateValue("AREA")})); 22 | builder.Add("ATTRIB_KEY", Value::LIST({Value::CreateValue(""), Value::CreateValue("")})); 23 | builder.Add("ATTRIB_TEXT", Value::LIST({Value::CreateValue(""), Value::CreateValue("")})); 24 | builder.Add("ISFOLDER", Value::LIST({Value::CreateValue("X"), Value::CreateValue("")})); 25 | builder.Add("EXPANDER", Value::LIST({Value::CreateValue("0"), Value::CreateValue("0")})); 26 | builder.Add("LAST_CHANGED", Value::LIST({Value::CreateValue("20170713093410"), Value::CreateValue("20170713093410")})); 27 | builder.Add("LAST_CHANGED_BY", Value::LIST({Value::CreateValue("DDIC"), Value::CreateValue("DDIC")})); 28 | builder.Add("ICON_TYPE", Value::LIST({Value::CreateValue(""), Value::CreateValue("")})); 29 | 30 | return builder.Build(); 31 | } 32 | 33 | TEST_CASE("Test type conversion", "[table_wrapper]") { 34 | auto struct_oriented_table = std::make_shared(CreateTestTable()); 35 | auto table_wrapper = TableWrapper(struct_oriented_table); 36 | 37 | auto list_oriented_row_type = table_wrapper.ListOrientedRowType(); 38 | REQUIRE(list_oriented_row_type.id() == LogicalTypeId::STRUCT); 39 | auto struct_types = StructType::GetChildTypes(list_oriented_row_type); 40 | REQUIRE(struct_types.size() == 16); 41 | for (auto &struct_type: struct_types) { 42 | REQUIRE(struct_type.second.id() == LogicalTypeId::VARCHAR); 43 | } 44 | } 45 | 46 | TEST_CASE("Test size", "[table_wrapper]") { 47 | auto struct_oriented_table = std::make_shared(CreateTestTable()); 48 | auto table_wrapper = TableWrapper(struct_oriented_table); 49 | REQUIRE(table_wrapper.Size() == 2); 50 | } 51 | 52 | TEST_CASE("Test operator[]", "[table_wrapper]") { 53 | auto struct_oriented_table = std::make_shared(CreateTestTable()); 54 | auto table_wrapper = TableWrapper(struct_oriented_table); 55 | auto value = table_wrapper[0]; 56 | REQUIRE(value.type().id() == LogicalTypeId::STRUCT); 57 | 58 | auto struct_children = StructValue::GetChildren(value); 59 | REQUIRE(struct_children.size() == 16); 60 | 61 | ValueHelper helper(value); 62 | REQUIRE(helper["NODE_ID"].ToString() == "1"); 63 | REQUIRE(helper["PARENT_ID"].ToString() == ""); 64 | REQUIRE(helper["ROLE_KEY"].ToString() == ""); 65 | REQUIRE(helper["OBJ_KEY"].ToString() == "0BCT_CB"); 66 | REQUIRE(helper["OBJ_TYPE"].ToString() == "AREA"); 67 | REQUIRE(helper["OBJ_TEXT"].ToString() == "Content Browser"); 68 | } 69 | -------------------------------------------------------------------------------- /rfc/test/cpp/test_telemetry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "catch.hpp" 5 | #include "test_helpers.hpp" 6 | #include "duckdb.hpp" 7 | 8 | #include "telemetry.hpp" 9 | 10 | using namespace duckdb; 11 | using namespace std; 12 | 13 | 14 | TEST_CASE("Test recording of tracking events", "[telemetry]") 15 | { 16 | PostHogEvent event = { 17 | "test_event", 18 | "1234", 19 | { 20 | {"test_property1", "test_value"}, 21 | {"test_property2", "test_value"} 22 | } 23 | }; 24 | 25 | std::string api_key = "phc_t3wwRLtpyEmLHYaZCSszG0MqVr74J6wnCrj9D41zk2t"; 26 | 27 | TelemetryTaskQueue queue; 28 | queue.EnqueueTask([api_key](auto event) { PostHogProcess(api_key, event); }, event); 29 | 30 | std::this_thread::sleep_for(std::chrono::seconds(1)); 31 | } 32 | 33 | TEST_CASE("Test finding mac address", "[telemetry]") 34 | { 35 | std::string mac_address = PostHogTelemetry::GetMacAddress(); 36 | REQUIRE(mac_address != ""); 37 | REQUIRE(mac_address.length() == 17); 38 | } -------------------------------------------------------------------------------- /rfc/test/cpp/test_value_helper.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test_helpers.hpp" 3 | #include "duckdb.hpp" 4 | 5 | #include "duckdb_argument_helper.hpp" 6 | 7 | using namespace duckdb; 8 | using namespace std; 9 | 10 | Value CreateTestStruct() 11 | { 12 | auto struct_val = Value::STRUCT({ 13 | {"a", Value::CreateValue(123)}, 14 | {"b", Value::CreateValue("TEST")}, 15 | {"c", Value::CreateValue(true)}, 16 | {"d", Value::STRUCT({ 17 | {"x", Value::CreateValue(456)}, 18 | {"y", Value::CreateValue("BAR")} 19 | })} 20 | }); 21 | return struct_val; 22 | } 23 | 24 | Value CreateTestList() 25 | { 26 | auto list_val = Value::LIST({ 27 | Value::STRUCT({ 28 | {"x", Value::CreateValue(1)}, 29 | {"y", Value::CreateValue("A")} 30 | }), 31 | Value::STRUCT({ 32 | {"x", Value::CreateValue(2)}, 33 | {"y", Value::CreateValue("B")} 34 | }), 35 | Value::STRUCT({ 36 | {"x", Value::CreateValue(3)}, 37 | {"y", Value::CreateValue("C")} 38 | }) 39 | }); 40 | return list_val; 41 | } 42 | 43 | TEST_CASE("Can get underlying value from ValueHelper", "[value_helper]") 44 | { 45 | auto struct_val = CreateTestStruct(); 46 | auto helper = ValueHelper(struct_val); 47 | 48 | REQUIRE(helper.Get() == struct_val); 49 | } 50 | 51 | TEST_CASE("Test can select by path from a struct", "[value_helper]") 52 | { 53 | auto struct_val = CreateTestStruct(); 54 | auto helper = ValueHelper(struct_val); 55 | 56 | REQUIRE(helper["/a"].GetValue() == 123); 57 | REQUIRE(helper["/b"].GetValue() == "TEST"); 58 | REQUIRE(helper["/c"].GetValue() == true); 59 | REQUIRE(helper["/d/x"].GetValue() == 456); 60 | REQUIRE(helper["/d/y"].GetValue() == "BAR"); 61 | } 62 | 63 | TEST_CASE("Can select path with root path set", "[value_helper]") 64 | { 65 | auto struct_val = CreateTestStruct(); 66 | auto helper = ValueHelper(struct_val, "/d"); 67 | 68 | REQUIRE(helper["/x"].GetValue() == 456); 69 | REQUIRE(helper["/y"].GetValue() == "BAR"); 70 | } 71 | 72 | TEST_CASE("Test can update a struct by path", "[value_helper]") 73 | { 74 | auto struct_val = CreateTestStruct(); 75 | REQUIRE(ValueHelper(struct_val)["/a"].GetValue() == 123); 76 | 77 | auto new_value = Value::CreateValue(789); 78 | auto new_struct = ValueHelper::CreateMutatedValue(struct_val, new_value, "/a"); 79 | REQUIRE(ValueHelper(new_struct)["/a"].GetValue() == 789); 80 | 81 | new_value = Value::CreateValue("FOO"); 82 | new_struct = ValueHelper::CreateMutatedValue(struct_val, new_value, "/d/y"); 83 | REQUIRE(ValueHelper(new_struct)["/d/y"].GetValue() == "FOO"); 84 | } 85 | 86 | TEST_CASE("Test can update a list") 87 | { 88 | auto list_val = CreateTestList(); 89 | REQUIRE(ValueHelper(list_val)["/0/x"].GetValue() == 1); 90 | 91 | auto new_value = Value::CreateValue(8); 92 | auto new_list = ValueHelper::CreateMutatedValue(list_val, new_value, "/0/x"); 93 | REQUIRE(ValueHelper(new_list)["/0/x"].GetValue() == 8); 94 | 95 | 96 | REQUIRE(ValueHelper(list_val)["/1/y"].GetValue() == "B"); 97 | new_value = Value::CreateValue("FOO"); 98 | new_list = ValueHelper::CreateMutatedValue(list_val, new_value, "/1/y"); 99 | REQUIRE(ValueHelper(new_list)["/1/y"].GetValue() == "FOO"); 100 | } 101 | 102 | TEST_CASE("Test can add to a list") 103 | { 104 | auto curr_list = CreateTestList(); 105 | REQUIRE(ValueHelper(curr_list)["/0/x"].GetValue() == 1); 106 | 107 | auto new_value = Value::STRUCT({ 108 | {"x", Value::CreateValue(4)}, 109 | {"y", Value::CreateValue("D")} 110 | }); 111 | auto new_list = ValueHelper::AddToList(curr_list, new_value); 112 | REQUIRE(ValueHelper(new_list)["/3/x"].GetValue() == 4); 113 | } 114 | 115 | TEST_CASE("Test can remove from a list by value") 116 | { 117 | auto curr_list = CreateTestList(); 118 | 119 | auto remove_value = Value::STRUCT({ 120 | {"x", Value::CreateValue(3)}, 121 | {"y", Value::CreateValue("C")} 122 | }); 123 | 124 | auto new_list = ValueHelper::RemoveFromList(curr_list, remove_value); 125 | REQUIRE(ListValue::GetChildren(new_list).size() == ListValue::GetChildren(curr_list).size() - 1); 126 | } 127 | 128 | TEST_CASE("Test can remove from a list by index") 129 | { 130 | auto curr_list = CreateTestList(); 131 | 132 | auto new_list = ValueHelper::RemoveFromList(curr_list, 0); 133 | REQUIRE(ListValue::GetChildren(new_list).size() == ListValue::GetChildren(curr_list).size() - 1); 134 | REQUIRE(ValueHelper(new_list)["/0/x"].GetValue() == 2); 135 | } -------------------------------------------------------------------------------- /rfc/test/nodejs/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/rfc/test/nodejs/.empty -------------------------------------------------------------------------------- /rfc/test/python/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/rfc/test/python/.empty -------------------------------------------------------------------------------- /rfc/test/sql/sap_describe_fields.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_describe_fields.test 2 | # description: test the functionality to describe fields of a SAP table 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Confirm I retrieve the correct field names 35 | query II 36 | SELECT field, is_key FROM sap_describe_fields('/DMO/FLIGHT'); 37 | ---- 38 | CLIENT 39 | X 40 | CARRIER_ID 41 | X 42 | CONNECTION_ID 43 | X 44 | FLIGHT_DATE 45 | X 46 | PRICE 47 | (empty) 48 | CURRENCY_CODE 49 | (empty) 50 | PLANE_TYPE_ID 51 | (empty) 52 | SEATS_MAX 53 | (empty) 54 | SEATS_OCCUPIED 55 | (empty) 56 | 57 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_read_table.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_read_table.test 2 | # description: test the functionality read a full SAP table 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | # Confirm I retrieve the correct row count 34 | query I 35 | SELECT COUNT(*) FROM sap_read_table('/DMO/FLIGHT'); 36 | ---- 37 | 40 38 | 39 | # --------------------------------------------------------------------- 40 | # Confirm I retrieve the correct table data 41 | query II 42 | SELECT CARRIER_ID, CONNECTION_ID FROM sap_read_table('/DMO/FLIGHT') ORDER BY 1, 2 LIMIT 10; 43 | ---- 44 | AA 45 | 0015 46 | AA 47 | 0015 48 | AA 49 | 0017 50 | AA 51 | 0017 52 | AA 53 | 0018 54 | AA 55 | 0018 56 | AA 57 | 0322 58 | AA 59 | 0322 60 | AA 61 | 2678 62 | AA 63 | 2678 64 | 65 | # --------------------------------------------------------------------- 66 | # If we limit the number we should get this value exactly as count 67 | query I 68 | SELECT CARRIER_ID FROM sap_read_table('/DMO/FLIGHT', columns=['CARRIER_ID'], threads=1, max_rows=8); 69 | ---- 70 | AA 71 | AA 72 | AA 73 | AA 74 | AA 75 | AA 76 | AA 77 | AA 78 | 79 | # --------------------------------------------------------------------- 80 | # If we select a column that we don't have in the columns list, we should get a bind error 81 | statement error 82 | SELECT PRICE FROM sap_read_table('/DMO/FLIGHT', columns=['CARRIER_ID', 'CONNECTION_ID']); 83 | ---- 84 | Binder Error: Referenced column "PRICE" not found in FROM clause! 85 | 86 | # --------------------------------------------------------------------- 87 | # Confirm where as named param works 88 | query I 89 | SELECT COUNT(*) FROM sap_read_table('/DMO/FLIGHT', FILTER='CARRIER_ID = ''SQ'' AND CONNECTION_ID = ''0001'''); 90 | ---- 91 | 2 92 | 93 | # --------------------------------------------------------------------- 94 | # Confirm that filter pushdown works (for the simply type of expressions DuckDB supports) 95 | query I 96 | SELECT COUNT(*) FROM sap_read_table('/DMO/FLIGHT') WHERE CARRIER_ID = 'SQ' AND CONNECTION_ID = '0001'; 97 | ---- 98 | 2 99 | 100 | # --------------------------------------------------------------------- 101 | # Confirm that a combination of filter pushdown and named param works 102 | query I 103 | SELECT COUNT(*) FROM sap_read_table('/DMO/FLIGHT', FILTER='CARRIER_ID = ''SQ''') WHERE CONNECTION_ID = '0001'; 104 | ---- 105 | 2 106 | 107 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_describe_function.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/rfc/sap_rfc_describe_function.test 2 | # description: test rfc describe function capabilities 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Confirm describe function works 35 | query III 36 | select name, import[1].name, export[1].name from sap_rfc_describe_function('STFC_CONNECTION'); 37 | ---- 38 | STFC_CONNECTION 39 | REQUTEXT 40 | ECHOTEXT 41 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_invoke.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/rfc/sap_rfc_invoke.test 2 | # description: test rfc invoke capabilities 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Verify that multiple calls also works 35 | loop i 0 10 36 | 37 | # Confirm echo rfc call works 38 | query I 39 | select trim(ECHOTEXT) from sap_rfc_invoke('STFC_CONNECTION', {'REQUTEXT': 'Hello'}); 40 | ---- 41 | Hello 42 | 43 | endloop 44 | 45 | # --------------------------------------------------------------------- 46 | # Confirm structure input / output works 47 | query IIIIII 48 | select ECHOSTRUCT.RFCFLOAT, ECHOSTRUCT.RFCCHAR1, ECHOSTRUCT.RFCDATE, ECHOSTRUCT.RFCTIME, ECHOSTRUCT.RFCINT4, ECHOSTRUCT.RFCDATA1 from sap_rfc_invoke('STFC_STRUCTURE', { 49 | 'IMPORTSTRUCT': { 50 | 'RFCCHAR1': 'A', 51 | 'RFCCHAR2': 'BB', 52 | 'RFCDATE': '2022-01-01'::DATE, 53 | 'RFCTIME': '12:34:56'::TIME, 54 | 'RFCFLOAT': 1.23456789, 55 | 'RFCINT1': 1, 56 | 'RFCINT2': 130, 57 | 'RFCINT4': 32767, 58 | 'RFCHEX3': 'XXX', 59 | 'RFCDATA1': repeat('X', 50), 60 | } 61 | }); 62 | ---- 63 | 1.23456789 64 | A 65 | 2022-01-01 66 | 12:34:56 67 | 32767 68 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 69 | 70 | # --------------------------------------------------------------------- 71 | # Verify that multiple calls also works 72 | loop i 0 10 73 | 74 | # Confirm table input / output works 75 | query III 76 | select RFCTABLE[1].RFCCHAR1, RFCTABLE[2].RFCCHAR2, RFCTABLE[3].RFCDATE from sap_rfc_invoke('STFC_STRUCTURE', { 77 | 'RFCTABLE': [ 78 | {'RFCCHAR1': 'A', 'RFCCHAR2': 'AA', 'RFCDATE': '2022-01-01'::DATE, 'RFCINT4': ${i}}, 79 | {'RFCCHAR1': 'B', 'RFCCHAR2': 'BB', 'RFCDATE': '2022-01-02'::DATE, 'RFCINT4': ${i}}, 80 | {'RFCCHAR1': 'C', 'RFCCHAR2': 'CC', 'RFCDATE': '2022-01-03'::DATE, 'RFCINT4': ${i}}, 81 | ] 82 | }); 83 | ---- 84 | A 85 | BB 86 | 2022-01-03 87 | 88 | endloop 89 | 90 | # --------------------------------------------------------------------- 91 | # Verify that multiple calls also works 92 | loop i 0 10 93 | 94 | # Confirm path selection on table works 95 | query I 96 | select RFCCHAR1 from sap_rfc_invoke('STFC_STRUCTURE', { 97 | 'RFCTABLE': [ 98 | {'RFCCHAR1': 'A', 'RFCCHAR2': 'AA', 'RFCDATE': '2022-01-01'::DATE}, 99 | {'RFCCHAR1': 'B', 'RFCCHAR2': 'BB', 'RFCDATE': '2022-01-02'::DATE}, 100 | {'RFCCHAR1': 'C', 'RFCCHAR2': 'CC', 'RFCDATE': '2022-01-03'::DATE}, 101 | ] 102 | }, path='/RFCTABLE'); 103 | ---- 104 | A 105 | B 106 | C 107 | X 108 | 109 | endloop 110 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_invoke_decimal.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/rfc/sap_rfc_invoke_decimal.test 2 | # description: test rfc invoke decimal capabilities 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Confirm get decimal for get flight call works 35 | query I 36 | select count(*) from sap_rfc_invoke('BAPI_FLIGHT_GETLIST', path='/FLIGHT_LIST'); 37 | ---- 38 | 94 39 | 40 | # --------------------------------------------------------------------- 41 | # Confirm echo rfc call works 42 | query I 43 | SELECT ADDITIONAL_INFO.DISTANCE FROM sap_rfc_invoke('BAPI_FLIGHT_GETDETAIL', {'AIRLINEID': 'LH', 'CONNECTIONID': '0400', 'FLIGHTDATE': '2016-11-18'::DATE }); 44 | ---- 45 | 6162.0000 46 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_invoke_flight.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_rfc_invoke_flight.test 2 | # description: test invocation of sap rfc functions 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | # --------------------------------------------------------------------- 32 | 33 | # Confirm echo rfc call works 34 | query I 35 | select COUNT(*) from sap_rfc_invoke('BAPI_FLIGHT_GETLIST', { 36 | 'AIRLINE' : 'LH', 37 | 'DESTINATION_FROM' : { 'AIRPORTID': 'FRA'}, 38 | 'DESTINATION_TO' : { 'AIRPORTID': 'JFK' } 39 | }, path='/FLIGHT_LIST'); 40 | ---- 41 | 16 42 | 43 | 44 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_invoke_stfc.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_rfc_invoke_stfc.test 2 | # description: test invocation of sap rfc functions 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Confirm echo rfc call works 35 | query I 36 | select trim(ECHOTEXT) from sap_rfc_invoke('STFC_CONNECTION', {'REQUTEXT': 'Hello'}); 37 | ---- 38 | Hello 39 | 40 | # --------------------------------------------------------------------- 41 | # Confirm structure input / output works 42 | query IIIIII 43 | select ECHOSTRUCT.RFCFLOAT, ECHOSTRUCT.RFCCHAR1, ECHOSTRUCT.RFCDATE, ECHOSTRUCT.RFCTIME, ECHOSTRUCT.RFCINT4, ECHOSTRUCT.RFCDATA1 from sap_rfc_invoke('STFC_STRUCTURE', { 44 | 'IMPORTSTRUCT': { 45 | 'RFCCHAR1': 'A', 46 | 'RFCCHAR2': 'BB', 47 | 'RFCDATE': '2022-01-01'::DATE, 48 | 'RFCTIME': '12:34:56'::TIME, 49 | 'RFCFLOAT': 1.23456789, 50 | 'RFCINT1': 1, 51 | 'RFCINT2': 130, 52 | 'RFCINT4': 32767, 53 | 'RFCHEX3': 'XXX', 54 | 'RFCDATA1': repeat('X', 50), 55 | } 56 | }); 57 | ---- 58 | 1.23456789 59 | A 60 | 2022-01-01 61 | 12:34:56 62 | 32767 63 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 64 | 65 | # --------------------------------------------------------------------- 66 | # Confirm table input / output works 67 | query III 68 | select RFCTABLE[1].RFCCHAR1, RFCTABLE[2].RFCCHAR2, RFCTABLE[3].RFCDATE from sap_rfc_invoke('STFC_STRUCTURE', { 69 | 'RFCTABLE': [ 70 | {'RFCCHAR1': 'A', 'RFCCHAR2': 'AA', 'RFCDATE': '2022-01-01'::DATE, 'RFCINT4': 1}, 71 | {'RFCCHAR1': 'B', 'RFCCHAR2': 'BB', 'RFCDATE': '2022-01-02'::DATE, 'RFCINT4': 2}, 72 | {'RFCCHAR1': 'C', 'RFCCHAR2': 'CC', 'RFCDATE': '2022-01-03'::DATE, 'RFCINT4': 3}, 73 | ] 74 | }); 75 | ---- 76 | A 77 | BB 78 | 2022-01-03 79 | 80 | # --------------------------------------------------------------------- 81 | # Confirm path selection on table works 82 | query I 83 | select RFCCHAR1 from sap_rfc_invoke('STFC_STRUCTURE', { 84 | 'RFCTABLE': [ 85 | {'RFCCHAR1': 'A', 'RFCCHAR2': 'AA', 'RFCDATE': '2022-01-01'::DATE}, 86 | {'RFCCHAR1': 'B', 'RFCCHAR2': 'BB', 'RFCDATE': '2022-01-02'::DATE}, 87 | {'RFCCHAR1': 'C', 'RFCCHAR2': 'CC', 'RFCDATE': '2022-01-03'::DATE}, 88 | ] 89 | }, path='/RFCTABLE'); 90 | ---- 91 | A 92 | B 93 | C 94 | X 95 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_ping.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/rfc/sap_rfc_ping.test 2 | # description: test rfc ping capabilities 3 | # group: [rfc] 4 | 5 | # Before we load the extension, this will fail 6 | statement error 7 | PRAGMA sap_rfc_ping; 8 | ---- 9 | Catalog Error: Pragma Function with name sap_rfc_ping does not exist! 10 | 11 | # Require RFC the extension 12 | require erpl_rfc 13 | 14 | # Configure connection ------------------------------------------------ 15 | require-env ERPL_SAP_ASHOST 16 | 17 | require-env ERPL_SAP_SYSNR 18 | 19 | require-env ERPL_SAP_USER 20 | 21 | require-env ERPL_SAP_PASSWORD 22 | 23 | require-env ERPL_SAP_CLIENT 24 | 25 | require-env ERPL_SAP_LANG 26 | 27 | statement ok 28 | CREATE SECRET abap_trial ( 29 | TYPE sap_rfc, 30 | ASHOST '${ERPL_SAP_ASHOST}', 31 | SYSNR '${ERPL_SAP_SYSNR}', 32 | CLIENT '${ERPL_SAP_CLIENT}', 33 | USER '${ERPL_SAP_USER}', 34 | PASSWD '${ERPL_SAP_PASSWORD}', 35 | LANG '${ERPL_SAP_LANG}' 36 | ); 37 | 38 | # --------------------------------------------------------------------- 39 | 40 | # Confirm I can ping the server 41 | query I 42 | PRAGMA sap_rfc_ping; 43 | ---- 44 | PONG 45 | -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_secret.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/rfc/sap_rfc_secret.test 2 | # description: test rfc secret capabilities 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET wrong_secret ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER 'not_existent', 28 | PASSWD 'wrong', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | statement error 33 | PRAGMA sap_rfc_ping(); 34 | ---- 35 | 36 | # --------------------------------------------------------------------- 37 | 38 | # Confirm I can create and use a secret 39 | statement ok 40 | CREATE SECRET correct_secret ( 41 | TYPE sap_rfc, 42 | ASHOST '${ERPL_SAP_ASHOST}', 43 | SYSNR '${ERPL_SAP_SYSNR}', 44 | CLIENT '${ERPL_SAP_CLIENT}', 45 | USER '${ERPL_SAP_USER}', 46 | PASSWD '${ERPL_SAP_PASSWORD}', 47 | LANG '${ERPL_SAP_LANG}' 48 | ); 49 | 50 | query I 51 | PRAGMA sap_rfc_ping(secret='correct_secret'); 52 | ---- 53 | PONG 54 | 55 | statement ok 56 | DROP SECRET correct_secret; -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_show_function.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_rfc_search_function.test 2 | # description: test sap rfc search function 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Confirm echo rfc call works 35 | query III 36 | select FUNCNAME, GROUPNAME, STEXT from sap_rfc_show_function(FUNCNAME='*/ASU/RS_VARIANT_CHANGE*'); 37 | ---- 38 | /ASU/RS_VARIANT_CHANGE 39 | /ASU/SVAR 40 | Ändern Variante 41 | 42 | # --------------------------------------------------------------------- 43 | # Confirm echo rfc call works 44 | query I 45 | select COUNT(*) from sap_rfc_show_function(); 46 | ---- 47 | 22578 -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_show_groups.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_rfc_search_group.test 2 | # description: test sap rfc search group function 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Do a query on one individual group 35 | query II 36 | select name, text from sap_rfc_show_groups(GROUPNAME='RSBOLAP_BICS'); 37 | ---- 38 | RSBOLAP_BICS 39 | BI Consumer Services 40 | 41 | # --------------------------------------------------------------------- 42 | # Check whether we can query all groups 43 | query I 44 | select COUNT(*) from sap_rfc_show_groups(); 45 | ---- 46 | 16122 -------------------------------------------------------------------------------- /rfc/test/sql/sap_rfc_show_tables.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/sap_show_tables.test 2 | # description: test sap sap_show_tables function 3 | # group: [rfc] 4 | 5 | # Require RFC the extension 6 | require erpl_rfc 7 | 8 | # Configure connection ------------------------------------------------ 9 | require-env ERPL_SAP_ASHOST 10 | 11 | require-env ERPL_SAP_SYSNR 12 | 13 | require-env ERPL_SAP_USER 14 | 15 | require-env ERPL_SAP_PASSWORD 16 | 17 | require-env ERPL_SAP_CLIENT 18 | 19 | require-env ERPL_SAP_LANG 20 | 21 | statement ok 22 | CREATE SECRET abap_trial ( 23 | TYPE sap_rfc, 24 | ASHOST '${ERPL_SAP_ASHOST}', 25 | SYSNR '${ERPL_SAP_SYSNR}', 26 | CLIENT '${ERPL_SAP_CLIENT}', 27 | USER '${ERPL_SAP_USER}', 28 | PASSWD '${ERPL_SAP_PASSWORD}', 29 | LANG '${ERPL_SAP_LANG}' 30 | ); 31 | 32 | # --------------------------------------------------------------------- 33 | 34 | # Confirm I retrieve the correct number of tables 35 | query I 36 | SELECT count(*) FROM sap_show_tables(); 37 | ---- 38 | 54348 39 | 40 | # --------------------------------------------------------------------- 41 | # Confirm I retrieve correct data for a table 42 | query III 43 | SELECT table_name, text, class FROM sap_show_tables() ORDER BY 1 LIMIT 1; 44 | ---- 45 | /1BS/SADLQE_TEST 46 | Generated Table for View 47 | VIEW 48 | 49 | # --------------------------------------------------------------------- 50 | # Confirm selection via table name works 51 | query I 52 | SELECT COUNT(*) FROM sap_show_tables(TABLENAME="*FLIGHT*"); 53 | ---- 54 | 34 55 | 56 | # --------------------------------------------------------------------- 57 | # Confirm a selection with no result also works 58 | query I 59 | SELECT COUNT(*) FROM sap_show_tables(TABLENAME="__NON_EXISTING_TABLE__"); 60 | ---- 61 | 0 62 | 63 | -------------------------------------------------------------------------------- /rfc/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "openssl" 4 | ] 5 | } -------------------------------------------------------------------------------- /scripts/download_and_extract_nwrfc.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [string]$SRC = 's3://erpl-resources/sapnwrfc/nwrfc750P_12-70002755_win.zip', 4 | [string]$TARGET = './nwrfcsdk/win/' 5 | ) 6 | 7 | function Download-Extract-Move { 8 | [CmdletBinding()] 9 | param( 10 | [string]$s3_url, 11 | [string]$target_dir 12 | ) 13 | 14 | $tmp_download_dir = "C:\Temp\downloaded" 15 | $tmp_extract_dir = "C:\Temp\extracted" 16 | 17 | Write-Verbose "Creating directories: $tmp_download_dir, $tmp_extract_dir, $target_dir" 18 | New-Item -ItemType Directory -Path $tmp_download_dir, $tmp_extract_dir, $target_dir -Force | Out-Null 19 | 20 | Write-Verbose "Downloading file from $s3_url to $tmp_download_dir" 21 | python -m awscli s3 cp $s3_url $tmp_download_dir 22 | 23 | $zip_file_name = [System.IO.Path]::GetFileName($s3_url) 24 | Write-Verbose "Zip file name: $zip_file_name" 25 | 26 | Write-Verbose "Extracting $tmp_download_dir\$zip_file_name to $tmp_extract_dir" 27 | Expand-Archive -Path "$tmp_download_dir\$zip_file_name" -DestinationPath $tmp_extract_dir 28 | 29 | Write-Verbose "Moving files from $tmp_extract_dir\nwrfcsdk to $target_dir" 30 | Move-Item -Path "$tmp_extract_dir\nwrfcsdk\*" -Destination $target_dir -Force 31 | 32 | Write-Verbose "Removing temporary directories: $tmp_download_dir, $tmp_extract_dir" 33 | Remove-Item -Path $tmp_download_dir, $tmp_extract_dir -Recurse -Force 34 | } 35 | 36 | # Call the function with the Verbose switch to see verbose output 37 | Download-Extract-Move -s3_url $SRC -target_dir $TARGET -Verbose 38 | 39 | -------------------------------------------------------------------------------- /scripts/download_and_extract_nwrfc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on the first error 4 | set -e 5 | 6 | # Download and extract the NW RFC SDK 7 | SRC=${1:-'s3://erpl-resources/sapnwrfc/nwrfc750P_11-70002752_linux.zip'} 8 | TARGET=${2:-'./sapnwrfc/linux/'} 9 | 10 | function download_extract_move() { 11 | # Parameters 12 | local s3_url="$1" # S3 URL of the zip file 13 | local target_dir="$2" # Target directory where files should be moved 14 | 15 | local tmp_download_dir="/tmp/downloaded" 16 | local tmp_extract_dir="/tmp/extracted" 17 | 18 | mkdir -p "$tmp_download_dir" "$tmp_extract_dir" "$target_dir" 19 | aws s3 cp "$s3_url" "$tmp_download_dir/" 20 | 21 | local zip_file_name=$(basename "$s3_url") 22 | 23 | unzip "$tmp_download_dir/$zip_file_name" -d "$tmp_extract_dir" 24 | mv -fv "$tmp_extract_dir/nwrfcsdk/"* "$target_dir/" 25 | rm -vr "$tmp_download_dir" "$tmp_extract_dir" 26 | } 27 | 28 | download_extract_move $SRC $TARGET 29 | -------------------------------------------------------------------------------- /scripts/extension-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Extension upload script 4 | 5 | # Usage: ./extension-upload.sh 6 | # : Name of the extension 7 | # : Version (commit / version tag) of the extension 8 | # : Version (commit / version tag) of DuckDB 9 | # : Architecture target of the extension binary 10 | # : S3 bucket to upload to 11 | # : Set this as the latest version ("true" / "false", default: "false") 12 | # : Set this as a versioned version that will prevent its deletion 13 | 14 | set -xe 15 | 16 | if [[ $4 == wasm* ]]; then 17 | ext="/tmp/extension/$1.duckdb_extension.wasm" 18 | else 19 | ext="/tmp/extension/$1.duckdb_extension" 20 | fi 21 | 22 | echo $ext 23 | 24 | script_dir="$(dirname "$(readlink -f "$0")")" 25 | 26 | # compress extension binary 27 | if [[ $4 == wasm_* ]]; then 28 | brotli < $ext > "$ext.compressed" 29 | else 30 | gzip < $ext > "$ext.compressed" 31 | fi 32 | 33 | # upload versioned version 34 | if [[ $7 = 'true' ]]; then 35 | if [[ $4 == wasm* ]]; then 36 | # Old style paths with additional duckdb-wasm 37 | aws s3 cp $ext.compressed s3://$5/duckdb-wasm/$1/$2/duckdb-wasm/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 38 | # New style paths for duckdb-wasm, more uniforms 39 | aws s3 cp $ext.compressed s3://$5/$1/$2/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 40 | else 41 | aws s3 cp $ext.compressed s3://$5/$1/$2/$3/$4/$1.duckdb_extension.gz --acl public-read 42 | fi 43 | fi 44 | 45 | # upload to latest version 46 | if [[ $6 = 'true' ]]; then 47 | if [[ $4 == wasm* ]]; then 48 | # Old style paths with additional duckdb-wasm 49 | aws s3 cp $ext.compressed s3://$5/duckdb-wasm/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 50 | # New style paths for duckdb-wasm, more uniforms 51 | aws s3 cp $ext.compressed s3://$5/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 52 | else 53 | aws s3 cp $ext.compressed s3://$5/$3/$4/$1.duckdb_extension.gz --acl public-read 54 | fi 55 | fi -------------------------------------------------------------------------------- /scripts/lsan_suppress.txt: -------------------------------------------------------------------------------- 1 | leak:libsapnwrfc.so 2 | memory:libsapnwrfc.so -------------------------------------------------------------------------------- /scripts/smoke-test.linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RELEASE_URL="https://github.com/duckdb/duckdb/releases/download/v0.9.0/duckdb_cli-linux-amd64.zip" 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | DUCK="$SCRIPT_DIR/duckdb" 6 | 7 | if [ ! -f "$DUCK" ]; then 8 | echo "Downloading DuckDB CLI" 9 | wget "$RELEASE_URL" -O /tmp/duckdb.zip 10 | unzip /tmp/duckdb.zip -d "$SCRIPT_DIR" 11 | rm /tmp/duckdb.zip 12 | fi 13 | 14 | BUILD="debug" # or release 15 | ERPL_EXTENSION=`realpath "$SCRIPT_DIR/../build/$BUILD/extension/erpl/erpl.duckdb_extension"` 16 | LSAN_SUPRESS_OPTIONS="suppressions=$SCRIPT_DIR/lsan_suppress.txt" 17 | NWRFC_LIB_PATH="$SCRIPT_DIR/../nwrfcsdk/lib" 18 | TRACE_DIR="./trace" 19 | 20 | rm -rf "$HOME/.duckdb/extensions/" 21 | rm $TRACE_DIR/* 22 | 23 | #PRAGMA sap_rfc_set_trace_dir('$TRACE_DIR'); 24 | #PRAGMA sap_rfc_set_trace_level(2); 25 | #PRAGMA sap_rfc_set_trace_level(0); 26 | 27 | SQL_CMDS=$(cat </dev/null 2>&1 && pwd )" 5 | PROFILE_FILE="$SCRIPT_DIR/A4H_D00_vhcala4hci.profile" 6 | LICENSE_FILE="${1:-$SCRIPT_DIR/A4H_Multiple.txt}" 7 | 8 | docker run \ 9 | --stop-timeout 3600 \ 10 | -i \ 11 | --rm \ 12 | --name a4h \ 13 | -h vhcala4hci \ 14 | --mac-address="02:42:ac:11:00:02" \ 15 | -p 3200:3200 \ 16 | -p 3300:3300 \ 17 | -p 8443:8443 \ 18 | -p 30213:30213 \ 19 | -p 50000:50000 \ 20 | -p 50001:50001 \ 21 | --sysctl kernel.shmmax=42949672960 \ 22 | --sysctl kernel.shmmni=32768 \ 23 | --sysctl kernel.shmall=10485760 \ 24 | --sysctl kernel.msgmni=1024 \ 25 | --sysctl kernel.sem="1250 256000 100 8192" \ 26 | --ulimit nofile=1048576:1048576 \ 27 | -v $PROFILE_FILE:/usr/sap/A4H/SYS/profile/A4H_D00_vhcala4hci \ 28 | -v $LICENSE_FILE:/opt/sap/ASABAP_license \ 29 | $APAB_PLATFORM_IMAGE -agree-to-sap-license -skip-limits-check 30 | -------------------------------------------------------------------------------- /scripts/vscode-settings/cmake-tools-kits-msys2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Clang 18.1.4 x86_64-w64-windows-gnu (mingw64)", 4 | "compilers": { 5 | "C": "C:/msys64/mingw64/bin/clang.exe", 6 | "CXX": "C:/msys64/mingw64/bin/clang++.exe" 7 | }, 8 | "cmakeSettings": { 9 | "CMAKE_MAKE_PROGRAM": "C:/msys64/mingw64/bin/ninja.exe", 10 | "VCPKG_HOST_TRIPLET": "x64-mingw-dynamic", 11 | "VCPKG_TARGET_TRIPLET": "x64-mingw-dynamic", 12 | "VCPKG_DEFAULT_TRIPLET": "x64-mingw-dynamic", 13 | "VCPKG_DEFAULT_HOST_TRIPLET": "x64-mingw-dynamic" 14 | }, 15 | "isTrusted": true, 16 | "environmentVariables": { 17 | "CMT_MINGW_PATH": "C/msys64/mingw64/bin", 18 | "MSYSTEM": "MINGW64", 19 | "MSYSTEM_PREFIX": "/mingw64", 20 | "PATH": "C:/msys64/mingw64/bin;C:/msys64/usr/bin;${env:PATH}" 21 | } 22 | }, 23 | { 24 | "name": "GCC 13.2.0 x86_64-w64-mingw32 (mingw64)", 25 | "compilers": { 26 | "C": "C:/msys64/mingw64/bin/gcc.exe", 27 | "CXX": "C:/msys64/mingw64/bin/g++.exe" 28 | }, 29 | "cmakeSettings": { 30 | "CMAKE_MAKE_PROGRAM": "C:/msys64/mingw64/bin/ninja.exe", 31 | "VCPKG_HOST_TRIPLET": "x64-mingw-dynamic", 32 | "VCPKG_TARGET_TRIPLET": "x64-mingw-dynamic", 33 | "VCPKG_DEFAULT_TRIPLET": "x64-mingw-dynamic", 34 | "VCPKG_DEFAULT_HOST_TRIPLET": "x64-mingw-dynamic" 35 | }, 36 | "isTrusted": true, 37 | "environmentVariables": { 38 | "CMT_MINGW_PATH": "C:/msys64/mingw64/bin", 39 | "MSYSTEM": "MINGW64", 40 | "MSYSTEM_PREFIX": "/mingw64", 41 | "PATH": "C:/msys64/mingw64/bin;C:/msys64/usr/bin;${env:PATH}" 42 | } 43 | } 44 | ] -------------------------------------------------------------------------------- /scripts/vscode-settings/settings-msys2.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.profiles.windows": { 3 | "msys2": { 4 | "path": [ 5 | "C:\\msys64\\usr\\bin\\bash.exe" 6 | ], 7 | "args": [ 8 | "--login" 9 | ], 10 | "env": { 11 | "MSYSTEM": "MINGW64", 12 | "CHERE_INVOKING": "1" 13 | } 14 | } 15 | }, 16 | "terminal.integrated.defaultProfile.windows": "msys2", 17 | "cmake.options.statusBarVisibility": "visible", 18 | "cmake.pinnedCommands": [ 19 | "workbench.action.tasks.configureTaskRunner", 20 | "workbench.action.tasks.runTask" 21 | ], 22 | "cmake.showOptionsMovedNotification": false, 23 | "cmake.configureOnOpen": true 24 | } 25 | -------------------------------------------------------------------------------- /trace/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataZooDE/erpl/5bdf7ca4fb63a3063a4ee08d876f38752676507f/trace/.empty -------------------------------------------------------------------------------- /trampoline/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET_NAME erpl) 2 | set(EXTENSION_NAME ${TARGET_NAME}_extension) 3 | set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | project(${TARGET_NAME}) 9 | 10 | include(../scripts/functions.cmake) 11 | add_duckdb_version_definition() 12 | 13 | if(WIN32) 14 | # Attach the erpl_rfc.duckdb_extension file as a resource to the trampoline file 15 | # (we call this erpl to not confuse the user), 16 | # such that it can be extracted 17 | init_resource_file() 18 | attach_extension_and_dependencies_as_resource("erpl_rfc.duckdb_extension" "${ADDED_EXTENSION_OBJECTS}") 19 | 20 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_bics" _index) 21 | if(NOT ${_index} EQUAL -1) 22 | message(STATUS "Attaching erpl_bics.duckdb_extension") 23 | attach_extension_and_dependencies_as_resource("erpl_bics.duckdb_extension" "${ADDED_EXTENSION_OBJECTS}") 24 | endif() 25 | 26 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_odp" _index) 27 | if(NOT ${_index} EQUAL -1) 28 | message(STATUS "Attaching erpl_odp.duckdb_extension") 29 | attach_extension_and_dependencies_as_resource("erpl_odp.duckdb_extension" "${ADDED_EXTENSION_OBJECTS}") 30 | endif() 31 | 32 | # Add vcpkg libraries as resources to the trampoline file, such that it can be extracted 33 | message(STATUS "Attaching vcpkg libraries") 34 | attach_vcpkg_dlls_as_resources("${ADDED_EXTENSION_OBJECTS}") 35 | 36 | # Add the SAP libraries as resources to the trampoline file, such that it can be extracted 37 | get_filename_component(SAPNWRFC_HOME ${CMAKE_CURRENT_SOURCE_DIR}/../nwrfcsdk/win ABSOLUTE) 38 | file(GLOB SAPNWRFC_DLL_FILES "${SAPNWRFC_HOME}/lib/*.dll") 39 | foreach(DLL ${SAPNWRFC_DLL_FILES}) 40 | get_filename_component(RC_PATH ${DLL} ABSOLUTE) 41 | get_filename_component(RC_NAME ${DLL} NAME_WE) 42 | file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/resources.rc" "${RC_NAME} RCDATA \"${RC_PATH}\"\n") 43 | endforeach() 44 | 45 | set(SAPNWRFC_LIB_OBJECTS "resources.rc") 46 | 47 | file(READ "${CMAKE_CURRENT_BINARY_DIR}/resources.rc" SAPNWRFC_LIB_OBJECTS_CONTENTS) 48 | message(STATUS "${CMAKE_CURRENT_BINARY_DIR}/resources.rc:\n${SAPNWRFC_LIB_OBJECTS_CONTENTS}") 49 | endif() 50 | 51 | if(UNIX AND NOT APPLE) 52 | # Attach the erpl_XXX.duckdb_extension files as an object to the trampoline file 53 | # (we call this erpl to not confuse the user), such that it can be extracted 54 | attach_extension_as_object("erpl_rfc.duckdb_extension") 55 | 56 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_bics" _index) 57 | if(NOT ${_index} EQUAL -1) 58 | message(STATUS "Attaching erpl_bics.duckdb_extension") 59 | attach_extension_as_object("erpl_bics.duckdb_extension") 60 | endif() 61 | 62 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_odp" _index) 63 | if(NOT ${_index} EQUAL -1) 64 | message(STATUS "Attaching erpl_odp.duckdb_extension") 65 | attach_extension_as_object("erpl_odp.duckdb_extension") 66 | endif() 67 | 68 | message(STATUS "ERPL_EXTENSION_OBJECTS: ${ERPL_EXTENSION_OBJECTS}") 69 | 70 | # Attach the SAP libraries as objects to the trampoline file, such that it can be extracted 71 | get_filename_component(SAPNWRFC_HOME ${CMAKE_CURRENT_SOURCE_DIR}/../nwrfcsdk/linux ABSOLUTE) 72 | file(GLOB SAPNWRFC_SO_FILES "${SAPNWRFC_HOME}/lib/*.so*") 73 | foreach(LIB ${SAPNWRFC_SO_FILES}) 74 | get_filename_component(LIB_DIR ${LIB} DIRECTORY) 75 | get_filename_component(LIB_NAME ${LIB} NAME) 76 | get_filename_component(OBJ_NAME ${LIB} NAME_WE) 77 | file(RELATIVE_PATH REL_LIB ${CMAKE_CURRENT_SOURCE_DIR} ${LIB}) 78 | 79 | list(APPEND SAPNWRFC_LIB_OBJECTS "${OBJ_NAME}.o") 80 | 81 | add_custom_command( 82 | OUTPUT "${OBJ_NAME}.o" 83 | COMMAND objcopy --input binary --output elf64-x86-64 --binary-architecture i386:x86-64 "${LIB_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/${OBJ_NAME}.o" 84 | DEPENDS "${LIB}" 85 | COMMENT "Creating object file ${CMAKE_CURRENT_BINARY_DIR}/${OBJ_NAME}.o" 86 | WORKING_DIRECTORY "${LIB_DIR}" 87 | ) 88 | endforeach() 89 | endif() 90 | 91 | if(UNIX AND APPLE) 92 | # Attach the erpl_XXX.duckdb_extension files as an object to the trampoline file 93 | # (we call this erpl to not confuse the user), such that it can be extracted 94 | attach_extension_as_object("erpl_rfc.duckdb_extension") 95 | 96 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_bics" _index) 97 | if(NOT ${_index} EQUAL -1) 98 | message(STATUS "Attaching erpl_bics.duckdb_extension") 99 | attach_extension_as_object("erpl_bics.duckdb_extension") 100 | endif() 101 | 102 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_odp" _index) 103 | if(NOT ${_index} EQUAL -1) 104 | message(STATUS "Attaching erpl_odp.duckdb_extension") 105 | attach_extension_as_object("erpl_odp.duckdb_extension") 106 | endif() 107 | 108 | message(STATUS "ERPL_EXTENSION_OBJECTS: ${ERPL_EXTENSION_OBJECTS}") 109 | 110 | # Attach the SAP libraries as objects to the trampoline file, such that it can be extracted 111 | if("${OSX_BUILD_ARCH}" STREQUAL "arm64") 112 | get_filename_component(SAPNWRFC_HOME ${CMAKE_CURRENT_SOURCE_DIR}/../nwrfcsdk/osx_arm ABSOLUTE) 113 | else() 114 | get_filename_component(SAPNWRFC_HOME ${CMAKE_CURRENT_SOURCE_DIR}/../nwrfcsdk/osx_amd64 ABSOLUTE) 115 | endif() 116 | 117 | file(GLOB SAPNWRFC_DYLIB_FILES "${SAPNWRFC_HOME}/lib/*.dylib") 118 | foreach(LIB ${SAPNWRFC_DYLIB_FILES}) 119 | get_filename_component(LIB_DIR ${LIB} DIRECTORY) 120 | get_filename_component(LIB_NAME ${LIB} NAME) 121 | get_filename_component(OBJ_NAME ${LIB} NAME_WE) 122 | file(RELATIVE_PATH REL_LIB ${CMAKE_CURRENT_SOURCE_DIR} ${LIB}) 123 | 124 | list(APPEND SAPNWRFC_LIB_OBJECTS "${OBJ_NAME}.o") 125 | 126 | convert_dylib_to_object("${OBJ_NAME}" "${LIB}" "${OBJ_NAME}.o" "${CMAKE_CURRENT_BINARY_DIR}/${OBJ_NAME}.o" ON) 127 | endforeach() 128 | endif() 129 | 130 | include_directories(src/include) 131 | 132 | message(STATUS "SAPNWRFC_LIB_OBJECTS: ${SAPNWRFC_LIB_OBJECTS}") 133 | 134 | set(EXTENSION_SOURCES 135 | src/erpl_extension.cpp 136 | ${SAPNWRFC_LIB_OBJECTS} 137 | ${ERPL_EXTENSION_OBJECTS} 138 | ) 139 | 140 | build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES}) 141 | set(PARAMETERS "-warnings") 142 | build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES}) 143 | 144 | add_dependencies(${EXTENSION_NAME} erpl_rfc_loadable_extension) 145 | add_dependencies(${LOADABLE_EXTENSION_NAME} erpl_rfc_loadable_extension) 146 | 147 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_bics" _index) 148 | if(NOT ${_index} EQUAL -1) 149 | add_compile_definitions(WITH_ERPL_BICS) 150 | add_dependencies(${EXTENSION_NAME} erpl_bics_loadable_extension) 151 | add_dependencies(${LOADABLE_EXTENSION_NAME} erpl_bics_loadable_extension) 152 | endif() 153 | 154 | list(FIND DUCKDB_EXTENSION_NAMES "erpl_odp" _index) 155 | if(NOT ${_index} EQUAL -1) 156 | add_compile_definitions(WITH_ERPL_ODP) 157 | add_dependencies(${EXTENSION_NAME} erpl_odp_loadable_extension) 158 | add_dependencies(${LOADABLE_EXTENSION_NAME} erpl_odp_loadable_extension) 159 | endif() 160 | 161 | if(WIN32) 162 | default_win32_definitions(${EXTENSION_NAME}) 163 | default_win32_definitions(${LOADABLE_EXTENSION_NAME}) 164 | endif() 165 | 166 | if(UNIX AND NOT APPLE) 167 | default_linux_definitions(${EXTENSION_NAME}) 168 | default_linux_definitions(${LOADABLE_EXTENSION_NAME}) 169 | endif() 170 | 171 | target_link_libraries(${EXTENSION_NAME}) 172 | target_link_libraries(${LOADABLE_EXTENSION_NAME}) -------------------------------------------------------------------------------- /trampoline/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean debug release pull update 2 | 3 | all: release 4 | 5 | MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) 6 | PROJ_DIR := $(dir $(MKFILE_PATH)) 7 | 8 | #### OSX config 9 | OSX_BUILD_FLAG= 10 | ifneq (${OSX_BUILD_ARCH}, "") 11 | OSX_BUILD_FLAG=-DOSX_BUILD_ARCH=${OSX_BUILD_ARCH} 12 | endif 13 | 14 | #### VCPKG config 15 | VCPKG_TOOLCHAIN_PATH?=../vcpkg/scripts/buildsystems/vcpkg.cmake 16 | ifneq ("${VCPKG_TOOLCHAIN_PATH}", "") 17 | TOOLCHAIN_FLAGS:=${TOOLCHAIN_FLAGS} -DVCPKG_MANIFEST_DIR='${PROJ_DIR}' -DVCPKG_BUILD=1 -DCMAKE_TOOLCHAIN_FILE='${VCPKG_TOOLCHAIN_PATH}' 18 | endif 19 | ifneq ("${VCPKG_TARGET_TRIPLET}", "") 20 | TOOLCHAIN_FLAGS:=${TOOLCHAIN_FLAGS} -DVCPKG_TARGET_TRIPLET='${VCPKG_TARGET_TRIPLET}' 21 | endif 22 | 23 | #### Enable Ninja as generator 24 | ifeq ($(GEN),ninja) 25 | GENERATOR=-G "Ninja" -DFORCE_COLORED_OUTPUT=1 26 | endif 27 | 28 | #### Configuration for this extension 29 | EXTENSION_NAME=ERPL 30 | EXTENSION_FLAGS=\ 31 | -DDUCKDB_EXTENSION_NAMES="erpl" \ 32 | -DDUCKDB_EXTENSION_${EXTENSION_NAME}_PATH="$(PROJ_DIR)" \ 33 | -DDUCKDB_EXTENSION_${EXTENSION_NAME}_INCLUDE_PATH="$(PROJ_DIR)src/include" 34 | 35 | #### Add more of the DuckDB in-tree extensions here that you need (also feel free to remove them when not needed) 36 | EXTRA_EXTENSIONS_FLAG=-DBUILD_EXTENSIONS="" 37 | 38 | # EXTENSION_STATIC_BUILD=1 means that duckdb is linked into the extension for compatibility reasons, 39 | # do not change this 40 | BUILD_FLAGS=-DEXTENSION_STATIC_BUILD=1 $(EXTENSION_FLAGS) ${EXTRA_EXTENSIONS_FLAG} $(OSX_BUILD_FLAG) $(TOOLCHAIN_FLAGS) -DDUCKDB_EXPLICIT_PLATFORM='${DUCKDB_PLATFORM}' 41 | CLIENT_FLAGS:= 42 | 43 | #### Main build 44 | CLIENT_FLAGS=-DDUCKDB_EXTENSION_${EXTENSION_NAME}_SHOULD_LINK=0 -D_GLIBCXX_USE_CXX11_ABI=0 45 | 46 | ifdef DUCKDB_PLATFORM 47 | ifneq ("${DUCKDB_PLATFORM}", "") 48 | CMAKE_VARS:=${CMAKE_VARS} -DDUCKDB_EXPLICIT_PLATFORM='${DUCKDB_PLATFORM}' 49 | endif 50 | endif 51 | 52 | debug: 53 | ifeq ($(OS),Windows_NT) 54 | powershell.exe -Command "if (!(Test-Path -Path ../build/debug/$(EXTENSION_NAME))) { New-Item -ItemType Directory -Path ../build/debug/$(EXTENSION_NAME) }" 55 | else 56 | mkdir -p ../build/debug/$(EXTENSION_NAME) 57 | endif 58 | cmake $(GENERATOR) $(BUILD_FLAGS) $(CLIENT_FLAGS) -DCMAKE_BUILD_TYPE=Debug -S ../duckdb/ -B ../build/debug/$(EXTENSION_NAME) 59 | cmake --build ../build/debug/$(EXTENSION_NAME) --config Debug 60 | 61 | release: 62 | ifeq ($(OS),Windows_NT) 63 | powershell.exe -Command "if (!(Test-Path -Path ../build/release/$(EXTENSION_NAME))) { New-Item -ItemType Directory -Path ../build/release/$(EXTENSION_NAME) }" 64 | else 65 | mkdir -p ../build/release/$(EXTENSION_NAME) 66 | endif 67 | cmake $(GENERATOR) $(BUILD_FLAGS) $(CLIENT_FLAGS) -DCMAKE_BUILD_TYPE=Release -S ../duckdb/ -B ../build/release/$(EXTENSION_NAME) 68 | cmake --build ../build/release/$(EXTENSION_NAME) --config Release 69 | 70 | #### Misc 71 | update: 72 | git submodule update --remote --merge 73 | pull: 74 | git submodule init 75 | git submodule update --recursive --remote 76 | 77 | clean: 78 | ifeq ($(OS),Windows_NT) 79 | powershell.exe -Command "if (Test-Path -Path ../build) { Remove-Item -Recurse -Force ../build }" 80 | powershell.exe -Command "if (Test-Path -Path ../testext) { Remove-Item -Recurse -Force ../testext }" 81 | powershell.exe -Command "if (Test-Path -Path ../duckdb/build) { Remove-Item -Recurse -Force ../duckdb/build }" 82 | else 83 | rm -rf ../build 84 | rm -rf ../testext 85 | cd ../duckdb && make clean 86 | cd ../duckdb && make clean-python 87 | endif -------------------------------------------------------------------------------- /trampoline/src/include/erpl_extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | 5 | namespace duckdb { 6 | 7 | class ErplExtension : public Extension { 8 | public: 9 | void Load(DuckDB &db) override; 10 | std::string Name() override; 11 | }; 12 | 13 | } // namespace duckdb 14 | -------------------------------------------------------------------------------- /trampoline/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [] 3 | } -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.13" 4 | 5 | [[package]] 6 | name = "erpl" 7 | version = "0.1.0" 8 | source = { virtual = "." } 9 | dependencies = [ 10 | { name = "pip" }, 11 | ] 12 | 13 | [package.metadata] 14 | requires-dist = [{ name = "pip", specifier = ">=25.0.1" }] 15 | 16 | [[package]] 17 | name = "pip" 18 | version = "25.0.1" 19 | source = { registry = "https://pypi.org/simple" } 20 | sdist = { url = "https://files.pythonhosted.org/packages/70/53/b309b4a497b09655cb7e07088966881a57d082f48ac3cb54ea729fd2c6cf/pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea", size = 1950850 } 21 | wheels = [ 22 | { url = "https://files.pythonhosted.org/packages/c9/bc/b7db44f5f39f9d0494071bddae6880eb645970366d0a200022a1a93d57f5/pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f", size = 1841526 }, 23 | ] 24 | --------------------------------------------------------------------------------