├── e2e_tests ├── __init__.py ├── data │ └── range.parquet ├── const.py ├── tests │ ├── test_uploading_files.py │ ├── test_cors.py │ ├── test_json_compact_all_types.py │ └── test_with_auth.py ├── conftest.py ├── client.py └── responses │ ├── all_types_json.py │ └── all_types_compact.py ├── .clang-format ├── .clang-tidy ├── .editorconfig ├── vcpkg.json ├── requirements.txt ├── .gitignore ├── src ├── include │ ├── response_format.hpp │ ├── auto_cleaner.hpp │ ├── files.hpp │ ├── dash_extension.hpp │ ├── http_error_data.hpp │ ├── temp_file.hpp │ ├── utils.hpp │ ├── table_functions_bind_data.hpp │ ├── string_util.hpp │ ├── query_result_table_function.hpp │ ├── result.hpp │ ├── table_functions.hpp │ ├── uri.hpp │ ├── execution_request.hpp │ └── http_server.hpp ├── gen │ ├── file_next_static_MBo1aAX1_T01D11kt_FJe_ssgManifest_js.hpp │ ├── file_next_static_chunks_pages_app_fd7e2f06c907c6fa_js.hpp │ ├── file_next_static_chunks_pages_error_9e91ec86137a41e3_js.hpp │ ├── file_manifest_json.hpp │ ├── file_next_static_css_5b54331129c9114c_css.hpp │ ├── file_next_static_chunks_main_app_e3a92c18fc983fe1_js.hpp │ ├── file_next_static_MBo1aAX1_T01D11kt_FJe_buildManifest_js.hpp │ ├── file_next_static_chunks_app_layout_a227b7c884d754eb_js.hpp │ ├── file_next_static_chunks_app_not_found_page_d9abfad02928c6f9_js.hpp │ ├── files.cpp │ ├── file_index_txt.hpp │ ├── file_next_static_chunks_webpack_9a68f0196a060eb0_js.hpp │ ├── file_next_static_media_959fe13ebd2a94e9_s_woff2.hpp │ ├── file_404_html.hpp │ ├── file_index_html.hpp │ ├── file_next_static_media_7323b9d087306adb_s_p_woff2.hpp │ └── file_next_static_chunks_902_02a4e3e7dd6de078_js.hpp ├── temp_file.cpp └── dash_extension.cpp ├── test ├── sql │ └── dash.test └── README.md ├── Makefile ├── extension_config.cmake ├── .gitmodules ├── scripts ├── install_python_windows.bat ├── install_npm_mac.sh ├── install_python_mac.sh ├── setup-custom-toolchain.sh ├── install_node_windows.bat ├── install_python_linux.sh ├── extension-upload.sh ├── install_npm_linux.sh └── build_ui.py ├── LICENSE ├── .github └── workflows │ └── MainDistributionPipeline.yml ├── CMakeLists.txt ├── docs └── UPDATING.md ├── openapi.yaml └── README.md /e2e_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | duckdb/.clang-format -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | duckdb/.clang-tidy -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | duckdb/.editorconfig -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": ["openssl"] 3 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | black==24.10.0 2 | httpx==0.28.1 3 | pytest==8.3.4 -------------------------------------------------------------------------------- /e2e_tests/data/range.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gropaul/dash/HEAD/e2e_tests/data/range.parquet -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .idea 3 | cmake-build-debug 4 | duckdb_unittest_tempdir/ 5 | .DS_Store 6 | testext 7 | __pycache__/ 8 | .Rhistory -------------------------------------------------------------------------------- /src/include/response_format.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class ResponseFormat { 4 | INVALID = 0, 5 | COMPACT_JSON, 6 | JSON, 7 | }; 8 | -------------------------------------------------------------------------------- /e2e_tests/const.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 | 5 | DEBUG_SHELL = f"{PROJECT_DIR}/build/debug/duckdb" 6 | HOST = "localhost" 7 | PORT = 9999 8 | API_KEY = "abc123" 9 | -------------------------------------------------------------------------------- /test/sql/dash.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/dash.test 2 | # description: test dash extension 3 | # group: [dash] 4 | 5 | # require dash 6 | # 7 | # query I 8 | # CALL start_dash('127.0.0.1', 4200); 9 | # ---- 10 | # true 11 | # 12 | # query I 13 | # CALL stop_dash() 14 | # ---- 15 | # true -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | # Configuration of extension 4 | EXT_NAME=dash 5 | EXT_CONFIG=${PROJ_DIR}extension_config.cmake 6 | 7 | # Include the Makefile from extension-ci-tools 8 | include extension-ci-tools/makefiles/duckdb_extension.Makefile 9 | -------------------------------------------------------------------------------- /src/include/auto_cleaner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class AutoCleaner { 6 | public: 7 | AutoCleaner( 8 | const std::function &_cleaner) : cleaner(_cleaner) { 9 | } 10 | ~AutoCleaner() { 11 | cleaner(); 12 | } 13 | 14 | private: 15 | std::function cleaner; 16 | }; 17 | -------------------------------------------------------------------------------- /extension_config.cmake: -------------------------------------------------------------------------------- 1 | # This file is included by DuckDB's build system. It specifies which extension to load 2 | 3 | # Extension from this repo 4 | duckdb_extension_load(dash 5 | SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR} 6 | LOAD_TESTS 7 | ) 8 | 9 | # Any extra extensions that should be built 10 | # e.g.: duckdb_extension_load(json) -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "duckdb"] 2 | path = duckdb 3 | url = https://github.com/duckdb/duckdb 4 | branch = main 5 | [submodule "dash-ui"] 6 | path = dash-ui 7 | url = https://github.com/gropaul/dash-ui 8 | branch = main 9 | [submodule "extension-ci-tools"] 10 | path = extension-ci-tools 11 | url = https://github.com/duckdb/extension-ci-tools.git 12 | branch = v1.2.1 13 | -------------------------------------------------------------------------------- /e2e_tests/tests/test_uploading_files.py: -------------------------------------------------------------------------------- 1 | from e2e_tests.client import Client, ResponseFormat 2 | 3 | from e2e_tests.const import PROJECT_DIR 4 | 5 | 6 | def test_uploading_files(http_duck: Client): 7 | parquet = f"{PROJECT_DIR}/e2e_tests/data/range.parquet" 8 | 9 | res = http_duck.execute_query("FROM 'range.parquet'", files=[parquet], response_format=ResponseFormat.COMPACT_JSON) 10 | 11 | assert res["statistics"]["rows"] == 42 12 | -------------------------------------------------------------------------------- /scripts/install_python_windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set "PYTHON_URL=https://www.python.org/ftp/python/3.11.2/python-3.11.2-embed-amd64.zip" 3 | set "PYTHON_DIR=%CD%\python_embed" 4 | 5 | echo Downloading Python... 6 | powershell -Command "Invoke-WebRequest -Uri %PYTHON_URL% -OutFile python.zip" 7 | 8 | echo Extracting Python... 9 | powershell -Command "Expand-Archive -Path python.zip -DestinationPath %PYTHON_DIR%" 10 | 11 | del python.zip 12 | echo Python installed in %PYTHON_DIR% -------------------------------------------------------------------------------- /scripts/install_npm_mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if ! command -v npm &> /dev/null; then 5 | echo "npm not found. Installing via Homebrew..." 6 | 7 | if ! command -v brew &> /dev/null; then 8 | echo "Homebrew not found. Installing Homebrew first..." 9 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 10 | fi 11 | 12 | brew install node 13 | else 14 | echo "npm is already installed." 15 | fi 16 | -------------------------------------------------------------------------------- /scripts/install_python_mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if ! command -v python3 &> /dev/null; then 5 | echo "Python not found. Installing via Homebrew..." 6 | if ! command -v brew &> /dev/null; then 7 | echo "Homebrew not found. Installing Homebrew first..." 8 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 9 | fi 10 | brew install python 11 | else 12 | echo "Python is already installed." 13 | fi 14 | -------------------------------------------------------------------------------- /src/gen/file_next_static_MBo1aAX1_T01D11kt_FJe_ssgManifest_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_MBO1AAX1_T01D11KT_FJE_SSGMANIFEST_JS = { 6 | // Content 7 | "c2VsZi5fX1NTR19NQU5JRkVTVD1uZXcgU2V0KFtdKTtzZWxmLl9fU1NHX01BTklGRVNUX0NCJiZzZWxmLl9fU1NHX01BTklGRVNUX0NCKCk=", // 8 | 108, // 9 | "application/javascript", // 10 | "/_next/static/MBo1aAX1_T01D11kt_FJe/_ssgManifest.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/include/files.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb/common/optional_ptr.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace duckdb { 9 | 10 | typedef std::string Path; 11 | typedef unsigned char Byte; 12 | 13 | struct File { 14 | const char* content; // base64 encoded 15 | size_t content_length; 16 | std::string content_type; 17 | std::string path; 18 | }; 19 | 20 | optional_ptr GetFile(Path path, bool try_resolve_404 = true); 21 | 22 | } // namespace duckdb 23 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Testing this extension 2 | This directory contains all the tests for this extension. The `sql` directory holds tests that are written as [SQLLogicTests](https://duckdb.org/dev/sqllogictest/intro.html). DuckDB aims to have most its tests in this format as SQL statements, so for the quack extension, this should probably be the goal too. 3 | 4 | The root makefile contains targets to build and run all of these tests. To run the SQLLogicTests: 5 | ```bash 6 | make test 7 | ``` 8 | or 9 | ```bash 10 | make test_debug 11 | ``` -------------------------------------------------------------------------------- /e2e_tests/tests/test_cors.py: -------------------------------------------------------------------------------- 1 | def test_absence_of_cors_headers(http_duck): 2 | res = http_duck.execute_query_raw("SELECT 1") 3 | assert "Access-Control-Allow-Origin" not in res.headers 4 | 5 | 6 | def test_presence_of_cors_headers(cors_duck): 7 | res = cors_duck.execute_query_raw("SELECT 1") 8 | assert res.headers["Access-Control-Allow-Origin"] == "*" 9 | assert res.headers["Access-Control-Allow-Methods"] == "POST, GET, OPTIONS" 10 | assert res.headers["Access-Control-Allow-Headers"] == "X-Api-Key, Content-Type" 11 | assert res.headers["Access-Control-Allow-Credentials"] == "true" 12 | assert res.headers["Access-Control-Max-Age"] == "86400" 13 | -------------------------------------------------------------------------------- /scripts/setup-custom-toolchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is an example script that can be used to install additional toolchain dependencies. Feel free to remove this script 4 | # if no additional toolchains are required 5 | 6 | # To enable this script, set the `custom_toolchain_script` option to true when calling the reusable workflow 7 | # `.github/workflows/_extension_distribution.yml` from `https://github.com/duckdb/extension-ci-tools` 8 | 9 | # note that the $DUCKDB_PLATFORM environment variable can be used to discern between the platforms 10 | echo "This is the sample custom toolchain script running for architecture '$DUCKDB_PLATFORM' for the dash extension." 11 | 12 | -------------------------------------------------------------------------------- /src/include/dash_extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb.hpp" 4 | 5 | namespace duckdb { 6 | #define DUCKDB_VERSION_ENCODE(major, minor, patch) ((major) * 10000 + (minor) * 100 + (patch)) 7 | #define DUCKDB_CURRENT_VERSION DUCKDB_VERSION_ENCODE(DUCKDB_MAJOR_VERSION, DUCKDB_MINOR_VERSION, DUCKDB_PATCH_VERSION) 8 | 9 | class DashExtension : public Extension { 10 | public: 11 | #if DUCKDB_CURRENT_VERSION >= DUCKDB_VERSION_ENCODE(1, 3, 3) 12 | void Load(ExtensionLoader &db) override; 13 | #else 14 | void Load(DuckDB &db) override; 15 | #endif 16 | std::string Name() override; 17 | std::string Version() const override; 18 | }; 19 | 20 | 21 | } // namespace duckdb 22 | -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_pages_app_fd7e2f06c907c6fa_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_PAGES_APP_FD7E2F06C907C6FA_JS = { 6 | // Content 7 | "KHNlbGYud2VicGFja0NodW5rX05fRT1zZWxmLndlYnBhY2tDaHVua19OX0V8fFtdKS5wdXNoKFtbNjM2XSx7MjY0MjY6KF8sbixwKT0+eyh3aW5kb3cuX19ORVhUX1A9d2luZG93Ll9fTkVYVF9QfHxbXSkucHVzaChbIi9fYXBwIixmdW5jdGlvbigpe3JldHVybiBwKDY2NjEyKX1dKX19LF89Pnt2YXIgbj1uPT5fKF8ucz1uKTtfLk8oMCxbNTkzLDc5Ml0sKCk9PihuKDI2NDI2KSxuKDI3MzQ2KSkpLF9OX0U9Xy5PKCl9XSk7", // 8 | 320, // 9 | "application/javascript", // 10 | "/_next/static/chunks/pages/_app-fd7e2f06c907c6fa.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_pages_error_9e91ec86137a41e3_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_PAGES_ERROR_9E91EC86137A41E3_JS = { 6 | // Content 7 | "KHNlbGYud2VicGFja0NodW5rX05fRT1zZWxmLndlYnBhY2tDaHVua19OX0V8fFtdKS5wdXNoKFtbNzMxXSx7NjgyNDooXyxuLGUpPT57KHdpbmRvdy5fX05FWFRfUD13aW5kb3cuX19ORVhUX1B8fFtdKS5wdXNoKFsiL19lcnJvciIsZnVuY3Rpb24oKXtyZXR1cm4gZSgzNjAyNSl9XSl9fSxfPT57dmFyIG49bj0+XyhfLnM9bik7Xy5PKDAsWzYzNiw1OTMsNzkyXSwoKT0+big2ODI0KSksX05fRT1fLk8oKX1dKTs=", // 8 | 312, // 9 | "application/javascript", // 10 | "/_next/static/chunks/pages/_error-9e91ec86137a41e3.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/include/http_error_data.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "duckdb/common/error_data.hpp" 3 | 4 | #define CPPHTTPLIB_OPENSSL_SUPPORT 5 | #include "httplib.hpp" 6 | 7 | namespace duckdb { 8 | 9 | using namespace duckdb_httplib_openssl; // NOLINT(*-build-using-namespace) 10 | 11 | struct HttpErrorData : ErrorData { 12 | HttpErrorData() = default; 13 | HttpErrorData(const StatusCode _status_code, const string &_message) 14 | : ErrorData(ExceptionType::HTTP, _message), status_code(_status_code) { 15 | } 16 | HttpErrorData(const StatusCode _code, const ErrorData &_error) : ErrorData(_error), status_code(_code) { 17 | } 18 | 19 | StatusCode status_code = BadRequest_400; 20 | }; 21 | 22 | } // namespace duckdb 23 | -------------------------------------------------------------------------------- /e2e_tests/tests/test_json_compact_all_types.py: -------------------------------------------------------------------------------- 1 | from e2e_tests.client import Client, ResponseFormat 2 | from e2e_tests.responses.all_types_compact import ALL_TYPES_COMPACT 3 | from e2e_tests.responses.all_types_json import ALL_TYPES_JSON 4 | 5 | 6 | def test_json_simple(http_duck: Client): 7 | res = http_duck.execute_query("SELECT 1 AS number, 'text' AS text", response_format=ResponseFormat.JSON) 8 | assert res == {"data": [{"number": 1, "text": "text"}]} 9 | 10 | 11 | def test_json_compact_all_types(http_duck: Client): 12 | for [response_format, value] in zip( 13 | [ResponseFormat.JSON, ResponseFormat.COMPACT_JSON], [ALL_TYPES_JSON, ALL_TYPES_COMPACT] 14 | ): 15 | res = http_duck.execute_query("FROM test_all_types()", response_format=response_format) 16 | assert res == value 17 | -------------------------------------------------------------------------------- /src/temp_file.cpp: -------------------------------------------------------------------------------- 1 | #include "include/temp_file.hpp" 2 | 3 | #ifdef _WIN32 4 | #define WIN32_LEAN_AND_MEAN 5 | #include 6 | #include 7 | #endif 8 | 9 | std::string duckdb::TempFile::GetTempDir() { 10 | #ifdef _WIN32 11 | char temp_path[MAX_PATH]; 12 | GetTempPathA(MAX_PATH, temp_path); 13 | return temp_path; 14 | #else 15 | const char *val = nullptr; 16 | // ReSharper disable CppUsingResultOfAssignmentAsCondition 17 | (val = std::getenv("TMPDIR")) || // 18 | (val = std::getenv("TMP")) || // 19 | (val = std::getenv("TEMP")) || // 20 | (val = std::getenv("TEMPDIR")); 21 | // ReSharper restore CppUsingResultOfAssignmentAsCondition 22 | #ifdef __ANDROID__ 23 | auto *default_tmp = "/data/local/tmp"; 24 | #else 25 | auto default_tmp = "/tmp"; 26 | #endif 27 | return val ? val : default_tmp; 28 | #endif 29 | } 30 | -------------------------------------------------------------------------------- /src/gen/file_manifest_json.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_MANIFEST_JSON = { 6 | // Content 7 | "ewogICJuYW1lIjogIkRhc2giLAogICJzaG9ydF9uYW1lIjogIkRhc2giLAogICJpY29ucyI6IFsKICAgIHsKICAgICAgInNyYyI6ICIvZmF2aWNvbi93ZWItYXBwLW1hbmlmZXN0LTE5MngxOTIucG5nIiwKICAgICAgInNpemVzIjogIjE5MngxOTIiLAogICAgICAidHlwZSI6ICJpbWFnZS9wbmciLAogICAgICAicHVycG9zZSI6ICJtYXNrYWJsZSIKICAgIH0sCiAgICB7CiAgICAgICJzcmMiOiAiL2Zhdmljb24vd2ViLWFwcC1tYW5pZmVzdC01MTJ4NTEyLnBuZyIsCiAgICAgICJzaXplcyI6ICI1MTJ4NTEyIiwKICAgICAgInR5cGUiOiAiaW1hZ2UvcG5nIiwKICAgICAgInB1cnBvc2UiOiAibWFza2FibGUiCiAgICB9CiAgXSwKICAidGhlbWVfY29sb3IiOiAiI2ZmZmZmZiIsCiAgImJhY2tncm91bmRfY29sb3IiOiAiI2ZmZmZmZiIsCiAgImRpc3BsYXkiOiAic3RhbmRhbG9uZSIKfQ==", // 8 | 596, // 9 | "application/json", // 10 | "/manifest.json/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /e2e_tests/tests/test_with_auth.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from httpx import HTTPStatusError 3 | 4 | from e2e_tests.client import Client, ResponseFormat 5 | 6 | 7 | def test_with_auth(http_duck_auth: Client): 8 | res = http_duck_auth.execute_query("SELECT 1", ResponseFormat.COMPACT_JSON) 9 | assert res["statistics"]["rows"] == 1 10 | 11 | 12 | def test_without_auth(http_duck_auth: Client): 13 | with pytest.raises(HTTPStatusError) as e: 14 | http_duck_auth.with_key(api_key=None).execute_query("SELECT 1", ResponseFormat.COMPACT_JSON) 15 | 16 | assert "Missing" in str(e.value.response.text) 17 | assert e.value.response.status_code == 401 18 | 19 | 20 | def test_wrong_auth(http_duck_auth: Client): 21 | with pytest.raises(HTTPStatusError) as e: 22 | http_duck_auth.with_key(api_key="__wrong__").execute_query("SELECT 1", ResponseFormat.COMPACT_JSON) 23 | 24 | assert "Invalid" in str(e.value.response.text) 25 | assert e.value.response.status_code == 401 26 | -------------------------------------------------------------------------------- /scripts/install_node_windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | :: Check if Node.js is already installed 5 | where node >nul 2>nul 6 | if %ERRORLEVEL%==0 ( 7 | echo Node.js is already installed. 8 | exit /b 0 9 | ) 10 | 11 | :: Specify the Node.js version and download URL 12 | set "NODE_VERSION=18.14.2" 13 | set "NODE_URL=https://nodejs.org/dist/v%NODE_VERSION%/node-v%NODE_VERSION%-win-x64.zip" 14 | set "NODE_ZIP=node-v%NODE_VERSION%-win-x64.zip" 15 | set "NODE_DIR=%CD%\node-v%NODE_VERSION%-win-x64" 16 | 17 | echo Downloading Node.js (which includes npm)... 18 | powershell -Command "Invoke-WebRequest -Uri %NODE_URL% -OutFile %NODE_ZIP%" 19 | 20 | echo Extracting Node.js... 21 | powershell -Command "Expand-Archive -Path %NODE_ZIP% -DestinationPath %CD%" 22 | 23 | del "%NODE_ZIP%" 24 | echo Node.js (and npm) installed in: %NODE_DIR% 25 | 26 | echo. 27 | echo To use node/npm from anywhere, add this folder to your system PATH: 28 | echo %NODE_DIR% 29 | echo. 30 | echo Done! 31 | endlocal 32 | -------------------------------------------------------------------------------- /src/gen/file_next_static_css_5b54331129c9114c_css.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CSS_5B54331129C9114C_CSS = { 6 | // Content 7 | "LmNvZGV4LWVkaXRvcnt6LWluZGV4OnVuc2V0fS5jZS10b29sYmFyOmhhcyguY2UtcG9wb3Zlci0tb3BlbmVkKXt6LWluZGV4OjR9LmNlLWJsb2Nre2JhY2tncm91bmQtY29sb3I6aHNsKHZhcigtLWJhY2tncm91bmQpKX0uY2UtdG9vbGJhcnt6LWluZGV4OjMwfS5jZS1ibG9ja19fY29udGVudCwuY2UtdG9vbGJhcl9fY29udGVudHttYXgtd2lkdGg6NzYycHh9LmNlLWJsb2NrX19jb250ZW50e2JhY2tncm91bmQtY29sb3I6aW5oZXJpdH1oMXtmb250LXNpemU6Mi4yNXJlbTtib3JkZXItYm90dG9tOjFweCBzb2xpZCBoc2wodmFyKC0tYm9yZGVyKSl9aDEsaDJ7Zm9udC13ZWlnaHQ6NzAwO2xpbmUtaGVpZ2h0OjEuMn1oMntmb250LXNpemU6MS44NzVyZW19aDN7Zm9udC1zaXplOjEuNXJlbX1oMyxoNHtmb250LXdlaWdodDo3MDA7bGluZS1oZWlnaHQ6MS4yfWg0e2ZvbnQtc2l6ZToxLjI1cmVtfWg1e2ZvbnQtc2l6ZToxcmVtfWg1LGg2e2ZvbnQtd2VpZ2h0OjcwMDtsaW5lLWhlaWdodDoxLjJ9aDZ7Zm9udC1zaXplOi44NzVyZW19", // 8 | 704, // 9 | "text/css", // 10 | "/_next/static/css/5b54331129c9114c.css/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_main_app_e3a92c18fc983fe1_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_MAIN_APP_E3A92C18FC983FE1_JS = { 6 | // Content 7 | "KHNlbGYud2VicGFja0NodW5rX05fRT1zZWxmLndlYnBhY2tDaHVua19OX0V8fFtdKS5wdXNoKFtbMzU4XSx7OTM5MjooZSxzLG4pPT57UHJvbWlzZS5yZXNvbHZlKCkudGhlbihuLnQuYmluZChuLDQyNzkwLDIzKSksUHJvbWlzZS5yZXNvbHZlKCkudGhlbihuLnQuYmluZChuLDM3NzMwLDIzKSksUHJvbWlzZS5yZXNvbHZlKCkudGhlbihuLnQuYmluZChuLDk0OTkwLDIzKSksUHJvbWlzZS5yZXNvbHZlKCkudGhlbihuLnQuYmluZChuLDM1MTEsMjMpKSxQcm9taXNlLnJlc29sdmUoKS50aGVuKG4udC5iaW5kKG4sNDYxNTUsMjMpKSxQcm9taXNlLnJlc29sdmUoKS50aGVuKG4udC5iaW5kKG4sNzQ1NTEsMjMpKSxQcm9taXNlLnJlc29sdmUoKS50aGVuKG4udC5iaW5kKG4sMzE3NywyMykpLFByb21pc2UucmVzb2x2ZSgpLnRoZW4obi50LmJpbmQobiw3MjQ3MSwyMykpfX0sZT0+e3ZhciBzPXM9PmUoZS5zPXMpO2UuTygwLFsyNzQsNDI0XSwoKT0+KHMoNzY5NTkpLHMoOTM5MikpKSxfTl9FPWUuTygpfV0pOw==", // 8 | 688, // 9 | "application/javascript", // 10 | "/_next/static/chunks/main-app-e3a92c18fc983fe1.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018-2024 Stichting DuckDB Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/MainDistributionPipeline.yml: -------------------------------------------------------------------------------- 1 | # 2 | # This workflow calls the main distribution pipeline from DuckDB to build, test and (optionally) release the extension 3 | # 4 | name: Main Extension Distribution Pipeline 5 | on: 6 | push: 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }}-${{ github.ref != 'refs/heads/main' || github.sha }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | duckdb-next-build: 16 | name: Build extension binaries (main) 17 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main 18 | with: 19 | duckdb_version: main 20 | ci_tools_version: main 21 | extension_name: dash 22 | 23 | duckdb-stable-build: 24 | name: Build extension binaries (stable) 25 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v1.4.0 26 | with: 27 | duckdb_version: v1.4.0 28 | ci_tools_version: v1.4.0 29 | extension_name: dash -------------------------------------------------------------------------------- /src/include/temp_file.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb/common/error_data.hpp" 4 | 5 | #include 6 | 7 | namespace duckdb { 8 | 9 | class TempFile { 10 | public: 11 | TempFile(const std::string &_name, const std::string &_data) : name(_name) { 12 | assigned_path = GetTempDir() + "/" + std::to_string(::rand()) + "_" + name; 13 | 14 | std::ofstream file(assigned_path, std::ios::binary); 15 | if (!file.good()) { 16 | error = ErrorData(ExceptionType::IO, "Could not open file " + assigned_path + " for writing"); 17 | return; 18 | } 19 | 20 | file << _data; 21 | } 22 | 23 | TempFile(const TempFile &) = delete; 24 | TempFile &operator=(const TempFile &) = delete; 25 | 26 | ~TempFile() { 27 | remove(GetPath().c_str()); 28 | } 29 | 30 | std::string GetPath() const { 31 | return assigned_path; 32 | } 33 | 34 | std::string GetName() const { 35 | return name; 36 | } 37 | 38 | ErrorData GetError() const { 39 | return error; 40 | } 41 | 42 | static std::string GetTempDir(); 43 | 44 | private: 45 | std::string name; 46 | std::string assigned_path; 47 | ErrorData error; 48 | }; 49 | 50 | } // namespace duckdb 51 | -------------------------------------------------------------------------------- /src/include/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "fmt/format.h" 4 | #include "http_error_data.hpp" 5 | #include "result.hpp" 6 | 7 | #define CPPHTTPLIB_OPENSSL_SUPPORT 8 | #include "httplib.hpp" 9 | 10 | #include 11 | namespace duckdb { 12 | using namespace duckdb_httplib_openssl; // NOLINT(*-build-using-namespace) 13 | 14 | inline std::string EscapeQutes(const std::string &input) { 15 | std::string result = input; 16 | size_t pos = 0; 17 | while ((pos = result.find('\'', pos)) != std::string::npos) { 18 | result.replace(pos, 1, "''"); // replace ' with '' 19 | pos += 2; // advance past the replacement 20 | } 21 | return result; 22 | } 23 | 24 | inline Result HasCorrectApiKey(const std::string &api_key, const Request &req) { 25 | if (api_key.empty()) { 26 | return nullptr; 27 | } 28 | 29 | const auto &api_key_header = req.get_header_value("X-Api-Key"); 30 | if (api_key_header.empty()) { 31 | return HttpErrorData {Unauthorized_401, "Missing 'X-Api-Key' header"}; 32 | } 33 | 34 | if (api_key_header != api_key) { 35 | return HttpErrorData {Unauthorized_401, "Invalid API key"}; 36 | } 37 | 38 | return nullptr; 39 | } 40 | 41 | } // namespace duckdb -------------------------------------------------------------------------------- /src/gen/file_next_static_MBo1aAX1_T01D11kt_FJe_buildManifest_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_MBO1AAX1_T01D11KT_FJE_BUILDMANIFEST_JS = { 6 | // Content 7 | "c2VsZi5fX0JVSUxEX01BTklGRVNUPWZ1bmN0aW9uKGUscix0KXtyZXR1cm57X19yZXdyaXRlczp7YWZ0ZXJGaWxlczpbXSxiZWZvcmVGaWxlczpbXSxmYWxsYmFjazpbXX0sX19yb3V0ZXJGaWx0ZXJTdGF0aWM6e251bUl0ZW1zOjcsZXJyb3JSYXRlOjFlLTQsbnVtQml0czoxMzUsbnVtSGFzaGVzOjE0LGJpdEFycmF5OlsxLDEsZSwwLGUsZSwwLGUsZSxyLHIscixlLHIscixlLGUscixyLGUsZSxyLHIsZSxlLGUscixlLHIscixlLHIsZSxlLHIscixyLHIsZSxlLGUsZSxlLGUscixlLGUscixlLHIsZSxlLHIscixyLGUsZSxlLGUsZSxyLHIscixyLGUsZSxyLHIscixyLGUsZSxyLHIsZSxlLHIsZSxyLGUscixlLGUscixlLGUsZSxlLGUsZSxyLHIscixlLGUscixlLHIsZSxlLHIscixlLHIsZSxlLHIsZSxlLGUscixyLHIscixlLGUsZSxlLGUsZSxlLHIscixyLHIscixlLHIsZSxlLHIsZSxyLGUscl19LF9fcm91dGVyRmlsdGVyRHluYW1pYzp7bnVtSXRlbXM6cixlcnJvclJhdGU6MWUtNCxudW1CaXRzOnIsbnVtSGFzaGVzOk5hTixiaXRBcnJheTpbXX0sIi9fZXJyb3IiOlsic3RhdGljL2NodW5rcy9wYWdlcy9fZXJyb3ItOWU5MWVjODYxMzdhNDFlMy5qcyJdLHNvcnRlZFBhZ2VzOlsiL19hcHAiLCIvX2Vycm9yIl19fSgxLDAsMWUtNCksc2VsZi5fX0JVSUxEX01BTklGRVNUX0NCJiZzZWxmLl9fQlVJTERfTUFOSUZFU1RfQ0IoKTs=", // 8 | 932, // 9 | "application/javascript", // 10 | "/_next/static/MBo1aAX1_T01D11kt_FJe/_buildManifest.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /e2e_tests/conftest.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from typing import Iterator 3 | 4 | import pytest 5 | 6 | from .client import Client 7 | from .const import DEBUG_SHELL, HOST, PORT, API_KEY 8 | 9 | 10 | def _start_server(start_cmd: str, client: Client) -> Iterator[Client]: 11 | process = subprocess.Popen( 12 | [ 13 | DEBUG_SHELL, 14 | ], 15 | stdin=subprocess.PIPE, 16 | stdout=subprocess.PIPE, 17 | stderr=subprocess.PIPE, 18 | text=True, 19 | bufsize=2 ^ 16, 20 | ) 21 | 22 | # Load the extension 23 | process.stdin.write("LOAD dash;\n") 24 | process.stdin.write(start_cmd) 25 | 26 | client.on_ready() 27 | yield client 28 | 29 | process.kill() 30 | 31 | 32 | @pytest.fixture 33 | def http_duck() -> Iterator[Client]: 34 | for client in _start_server(f"CALL start_dash('{HOST}', {PORT});\n", Client(f"http://{HOST}:{PORT}")): 35 | yield client 36 | 37 | 38 | @pytest.fixture 39 | def cors_duck() -> Iterator[Client]: 40 | for client in _start_server( 41 | f"CALL start_dash('{HOST}', {PORT}, enable_cors=true);\n", Client(f"http://{HOST}:{PORT}") 42 | ): 43 | yield client 44 | 45 | 46 | @pytest.fixture 47 | def http_duck_auth() -> Iterator[Client]: 48 | for client in _start_server( 49 | f"CALL start_dash('{HOST}', {PORT}, api_key='{API_KEY}');\n", Client(f"http://{HOST}:{PORT}", API_KEY) 50 | ): 51 | yield client 52 | -------------------------------------------------------------------------------- /src/include/table_functions_bind_data.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "uri.hpp" 3 | 4 | namespace duckdb { 5 | struct StartServerFunctionData final : FunctionData { 6 | StartServerFunctionData(const string &_host, const int32_t _port, const string &_api_key, const bool _enable_cors, const bool _open_browser, 7 | const Uri &_ui_proxy) 8 | : host(_host), port(_port), api_key(_api_key), enable_cors(_enable_cors), open_browser(_open_browser), ui_proxy(_ui_proxy) { 9 | } 10 | 11 | unique_ptr Copy() const override { 12 | return make_uniq_base(host, port, api_key, enable_cors, open_browser, ui_proxy); 13 | } 14 | 15 | bool Equals(const FunctionData &other_p) const override { 16 | auto &other = other_p.Cast(); 17 | return host == other.host && port == other.port && api_key == other.api_key && 18 | enable_cors == other.enable_cors && ui_proxy == other.ui_proxy; 19 | } 20 | 21 | const std::string host; 22 | const int32_t port; 23 | const std::string api_key; 24 | const bool enable_cors; 25 | const bool open_browser; 26 | const Uri ui_proxy; 27 | }; 28 | 29 | 30 | struct EmptyFunctionData final : FunctionData { 31 | unique_ptr Copy() const override { 32 | return make_uniq_base(); 33 | } 34 | 35 | bool Equals(const FunctionData &other_p) const override { 36 | return true; 37 | } 38 | }; 39 | 40 | } // namespace duckdb 41 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(dash) 3 | 4 | # CMake example 5 | string(REGEX MATCH "v([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ ${DUCKDB_VERSION}) 6 | set(DUCKDB_MAJOR_VERSION "${CMAKE_MATCH_1}") 7 | set(DUCKDB_MINOR_VERSION "${CMAKE_MATCH_2}") 8 | set(DUCKDB_PATCH_VERSION "${CMAKE_MATCH_3}") 9 | add_compile_definitions( 10 | DUCKDB_MAJOR_VERSION=${DUCKDB_MAJOR_VERSION} 11 | DUCKDB_MINOR_VERSION=${DUCKDB_MINOR_VERSION} 12 | DUCKDB_PATCH_VERSION=${DUCKDB_PATCH_VERSION} 13 | ) 14 | 15 | # Set extension name here 16 | set(TARGET_NAME dash) 17 | 18 | if(MINGW) 19 | set(OPENSSL_USE_STATIC_LIBS TRUE) 20 | endif() 21 | 22 | find_package(OpenSSL REQUIRED) 23 | 24 | include_directories(src/include duckdb/third_party/httplib) 25 | 26 | set(EXTENSION_NAME ${TARGET_NAME}_extension) 27 | set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension) 28 | 29 | set(EXTENSION_SOURCES 30 | src/dash_extension.cpp 31 | src/gen/files.cpp 32 | src/temp_file.cpp 33 | ) 34 | 35 | 36 | # 3) Build the extensions 37 | build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES}) 38 | build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES}) 39 | 40 | # 4) Link OpenSSL 41 | target_link_libraries(${EXTENSION_NAME} OpenSSL::SSL OpenSSL::Crypto) 42 | target_link_libraries(${LOADABLE_EXTENSION_NAME} OpenSSL::SSL OpenSSL::Crypto) 43 | 44 | # 5) Make sure the extension depends on the script target 45 | #add_dependencies(${EXTENSION_NAME} run_my_python_script) 46 | #add_dependencies(${LOADABLE_EXTENSION_NAME} run_my_python_script) 47 | 48 | install( 49 | TARGETS ${EXTENSION_NAME} 50 | EXPORT "${DUCKDB_EXPORT_SET}" 51 | LIBRARY DESTINATION "${INSTALL_LIB_DIR}" 52 | ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" 53 | ) 54 | -------------------------------------------------------------------------------- /docs/UPDATING.md: -------------------------------------------------------------------------------- 1 | # Extension updating 2 | When cloning this template, the target version of DuckDB should be the latest stable release of DuckDB. However, there 3 | will inevitably come a time when a new DuckDB is released and the extension repository needs updating. This process goes 4 | as follows: 5 | 6 | - Bump submodules 7 | - `./duckdb` should be set to latest tagged release 8 | - `./extension-ci-tools` should be set to updated branch corresponding to latest DuckDB release. So if you're building for DuckDB `v1.1.0` there will be a branch in `extension-ci-tools` named `v1.1.0` to which you should check out. 9 | - Bump versions in `./github/workflows` 10 | - `duckdb_version` input in `duckdb-stable-build` job in `MainDistributionPipeline.yml` should be set to latest tagged release 11 | - `duckdb_version` input in `duckdb-stable-deploy` job in `MainDistributionPipeline.yml` should be set to latest tagged release 12 | - the reusable workflow `duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml` for the `duckdb-stable-build` job should be set to latest tagged release 13 | 14 | # API changes 15 | DuckDB extensions built with this extension template are built against the internal C++ API of DuckDB. This API is not guaranteed to be stable. 16 | What this means for extension development is that when updating your extensions DuckDB target version using the above steps, you may run into the fact that your extension no longer builds properly. 17 | 18 | Currently, DuckDB does not (yet) provide a specific change log for these API changes, but it is generally not too hard to figure out what has changed. 19 | 20 | For figuring out how and why the C++ API changed, we recommend using the following resources: 21 | - DuckDB's [Release Notes](https://github.com/duckdb/duckdb/releases) 22 | - DuckDB's history of [Core extension patches](https://github.com/duckdb/duckdb/commits/main/.github/patches/extensions) 23 | - The git history of the relevant C++ Header file of the API that has changed -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_app_layout_a227b7c884d754eb_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_APP_LAYOUT_A227B7C884D754EB_JS = { 6 | // Content 7 | "KHNlbGYud2VicGFja0NodW5rX05fRT1zZWxmLndlYnBhY2tDaHVua19OX0V8fFtdKS5wdXNoKFtbMTc3XSx7MzAwNDM6KGUsbixzKT0+eyJ1c2Ugc3RyaWN0IjtzLnIobikscy5kKG4se2RlZmF1bHQ6KCk9PnV9KTt2YXIgaT1zKDc2NDk1KTtzKDcwNjg2KSxzKDYwOTY3KTt2YXIgdD1zKDU3NTQ1KSxhPXMoNTkwNjUpLGw9cygzNTI0MCk7ZnVuY3Rpb24gcihlKXtsZXR7Y2hpbGRyZW46bn09ZSxbcyxyLGgsY109KDAsYS5vKShlPT5bZS5zZXR0aW5ncy5pc09wZW4sZS5zZXR0aW5ncy5jdXJyZW50VGFiLGUuc2V0U2V0dGluZ3NPcGVuLGUuc2V0dGluZ3MuZm9yY2VPcGVuUmVhc29uc10pLGQ9KDAsbC5KKShlPT5lLm9uQ29ubmVjdGlvblNwZWNTZWxlY3RlZCk7cmV0dXJuKDAsaS5qc3hzKShpLkZyYWdtZW50LHtjaGlsZHJlbjpbbiwoMCxpLmpzeCkodC5nWix7b3BlbjpzLGZvcmNlT3BlblJlYXNvbnM6Yyxvbk9wZW5DaGFuZ2U6aCxvblNwZWNTYXZlOmQsaW5pdGlhbFRhYjpyfSldfSl9dmFyIGg9cyg0ODgzNSk7ZnVuY3Rpb24gYyhlKXtsZXR7Y2hpbGRyZW46biwuLi5zfT1lO3JldHVybigwLGkuanN4KShoLk4sey4uLnMsY2hpbGRyZW46bn0pfXZhciBkPXMoMjQ5MDIpLG89cyg4NDI0MSkscD1zKDMxMjY5KTtmdW5jdGlvbiB1KGUpe2xldHtjaGlsZHJlbjpufT1lO3JldHVybigwLGkuanN4cykoImh0bWwiLHtsYW5nOiJlbiIsc3VwcHJlc3NIeWRyYXRpb25XYXJuaW5nOiEwLGNoaWxkcmVuOlsoMCxpLmpzeHMpKCJoZWFkIix7Y2hpbGRyZW46WygwLGkuanN4KSgidGl0bGUiLHtjaGlsZHJlbjoiRGFzaCJ9KSwoMCxpLmpzeCkoIm1ldGEiLHtuYW1lOiJhcHBsZS1tb2JpbGUtd2ViLWFwcC10aXRsZSIsY29udGVudDoiRGFzaCJ9KV19KSwoMCxpLmpzeCkoImJvZHkiLHtjbGFzc05hbWU6KDAsZC5jbikoIm1pbi0xMDBkdmggYmctYmFja2dyb3VuZCBhbnRpYWxpYXNlZCIpLGNoaWxkcmVuOigwLGkuanN4cykoIm1haW4iLHtjaGlsZHJlbjpbKDAsaS5qc3gpKCJkaXYiLHtjbGFzc05hbWU6InctZnVsbCBtaW4taC1bMTAwZHZoXSBzdXBwb3J0cy1baGVpZ2h0OjEwMGR2aF06bWluLWgtZHZoIixjaGlsZHJlbjooMCxpLmpzeCkoYyx7YXR0cmlidXRlOiJjbGFzcyIsZGVmYXVsdFRoZW1lOiJzeXN0ZW0iLGVuYWJsZVN5c3RlbTohMCxkaXNhYmxlVHJhbnNpdGlvbk9uQ2hhbmdlOiEwLGNoaWxkcmVuOigwLGkuanN4KShwLk5rLHtjaGlsZHJlbjooMCxpLmpzeCkocix7Y2hpbGRyZW46KDAsaS5qc3gpKCJkaXYiLHtjbGFzc05hbWU6ImZsZXggZmxleC1yb3cgbWluLWgtWzEwMGR2aF0gc3VwcG9ydHMtW2hlaWdodDoxMDBkdmhdOm1pbi1oLWR2aCB3LWZ1bGwiLGNoaWxkcmVuOm59KX0pfSl9KX0pLCgwLGkuanN4KShvLmwkLHt9KV19KX0pXX0pfX0sNjExMjE6KGUsbixzKT0+e1Byb21pc2UucmVzb2x2ZSgpLnRoZW4ocy5iaW5kKHMsMzAwNDMpKX0sNzA2ODY6KCk9Pnt9fSxlPT57dmFyIG49bj0+ZShlLnM9bik7ZS5PKDAsWzE5MSwzNDYsNjk2LDI5LDI3NCw0MjQsMzU4XSwoKT0+big2MTEyMSkpLF9OX0U9ZS5PKCl9XSk7", // 8 | 2060, // 9 | "application/javascript", // 10 | "/_next/static/chunks/app/layout-a227b7c884d754eb.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /e2e_tests/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import time 5 | from enum import Enum 6 | from pathlib import Path 7 | 8 | import httpx 9 | from httpx._types import FileTypes 10 | 11 | 12 | class ResponseFormat(Enum): 13 | COMPACT_JSON = "compact_json" 14 | JSON = "json" 15 | 16 | 17 | class Client: 18 | def __init__(self, url: str, api_key: str | None = None): 19 | self._url = url 20 | self._api_key = api_key 21 | 22 | def with_key(self, api_key: str | None) -> Client: 23 | self._api_key = api_key 24 | return self 25 | 26 | def execute_query( 27 | self, 28 | sql: str, 29 | response_format: ResponseFormat, 30 | files: list[str | Path] | None = None, 31 | ) -> dict: 32 | return self.execute_query_raw(sql=sql, response_format=response_format, files=files).json() 33 | 34 | def execute_query_raw( 35 | self, 36 | sql: str, 37 | response_format: ResponseFormat, 38 | files: list[str | Path] | None = None, 39 | ) -> httpx.Response: 40 | files = files or [] 41 | 42 | headers = {} 43 | if self._api_key: 44 | headers["X-API-Key"] = self._api_key 45 | 46 | body = {"query": sql, "format": response_format.value} 47 | 48 | transformed_files: dict[str, FileTypes] = {} 49 | for file in files: 50 | file_name = Path(file).name 51 | transformed_files[file_name] = open(file, "rb") 52 | 53 | transformed_files["query.json"] = (None, json.dumps(body).encode("utf-8"), "application/json") 54 | 55 | with httpx.Client(timeout=120) as client: 56 | response = client.post(self._url + "/query", headers=headers, json=body, files=transformed_files) 57 | response.raise_for_status() 58 | return response 59 | 60 | def ping(self, timeout: int | None = None) -> None: 61 | with httpx.Client(timeout=timeout) as client: 62 | response = client.get(f"{self._url}/ping") 63 | response.raise_for_status() 64 | 65 | def on_ready(self, timeout=15) -> None: 66 | end_time = time.time() + timeout 67 | while time.time() < end_time: 68 | try: 69 | self.ping(timeout=timeout) 70 | return 71 | except Exception: 72 | pass 73 | 74 | raise TimeoutError("Server is not ready") 75 | -------------------------------------------------------------------------------- /src/include/string_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "response_format.hpp" 4 | 5 | #include 6 | 7 | namespace duckdb { 8 | namespace string_util { 9 | 10 | template 11 | inline std::string ToString(const T &) { 12 | throw NotImplementedException(StringUtil::Format("ToString not implemented for type %s", typeid(T).name())); 13 | } 14 | 15 | template <> 16 | inline std::string ToString(const ResponseFormat &value) { 17 | switch (value) { 18 | case ResponseFormat::COMPACT_JSON: 19 | return "COMPACT_JSON"; 20 | case ResponseFormat::JSON: 21 | return "JSON"; 22 | case ResponseFormat::INVALID: 23 | return "INVALID"; 24 | } 25 | throw NotImplementedException( 26 | StringUtil::Format("Enum value not implemented in ToString: %d", value)); 27 | } 28 | 29 | template 30 | inline vector Values() { 31 | throw NotImplementedException(StringUtil::Format("ToString not implemented for type %s", typeid(T).name())); 32 | } 33 | 34 | template <> 35 | inline vector Values() { 36 | return {ResponseFormat::COMPACT_JSON, ResponseFormat::JSON}; 37 | } 38 | 39 | template 40 | inline string StringValues() { 41 | vector values = Values(); 42 | // Map the values to their string representation 43 | vector string_values {values.size()}; 44 | std::transform(values.begin(), values.end(), string_values.begin(), [](const T &x) { return ToString(x); }); 45 | return StringUtil::Join(string_values, ", "); 46 | } 47 | 48 | template 49 | inline T FromString(const std::string &) { 50 | throw NotImplementedException(StringUtil::Format("FromString not implemented for type %s", typeid(T).name())); 51 | } 52 | 53 | template <> 54 | inline ResponseFormat FromString(const std::string &value) { 55 | const auto upper = StringUtil::Upper(value.c_str()); 56 | if (StringUtil::Equals(upper.c_str(), "COMPACT_JSON")) { 57 | return ResponseFormat::COMPACT_JSON; 58 | } 59 | if (StringUtil::Equals(upper.c_str(), "INVALID")) { 60 | return ResponseFormat::INVALID; 61 | } 62 | if (StringUtil::Equals(upper.c_str(), "JSON")) { 63 | return ResponseFormat::JSON; 64 | } 65 | throw SerializationException( 66 | StringUtil::Format("Enum value: '%s' not implemented in FromString. Possible values {%s}'", value, 67 | StringValues())); 68 | } 69 | 70 | } // namespace string_util 71 | } // namespace duckdb 72 | -------------------------------------------------------------------------------- /src/include/query_result_table_function.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dash_extension.hpp" 4 | #include "duckdb.hpp" 5 | #include "duckdb/common/exception.hpp" 6 | #include "duckdb/main/prepared_statement_data.hpp" 7 | 8 | #include 9 | 10 | #include // for std::fixed and std::setprecision 11 | 12 | namespace duckdb { 13 | struct QueryResultFunctionData final : FunctionData { 14 | 15 | unique_ptr result; 16 | string query; 17 | explicit QueryResultFunctionData(const string &query_p, unique_ptr result_p) : result(std::move(result_p)), query(query_p) { 18 | 19 | } 20 | 21 | unique_ptr Copy() const override { 22 | throw Exception(ExceptionType::INTERNAL, "Cant copy this"); 23 | } 24 | 25 | bool Equals(const FunctionData &other) const override { 26 | const auto &otherQR = other.Cast(); 27 | return this->query == otherQR.query; 28 | } 29 | }; 30 | 31 | struct QueryResultState final : GlobalTableFunctionState { 32 | 33 | bool has_result; 34 | unique_ptr result; 35 | QueryResultState() : has_result(false) { 36 | } 37 | 38 | static unique_ptr Init(ClientContext &context, TableFunctionInitInput &input) { 39 | return make_uniq(); 40 | } 41 | }; 42 | 43 | static void QueryResultFun(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { 44 | auto &function_data = data_p.bind_data->Cast(); 45 | const auto chunk = function_data.result->Fetch(); 46 | if (chunk) { 47 | output.Reference(*chunk); 48 | output.SetCardinality(chunk->size()); 49 | } else { 50 | output.SetCardinality(0); 51 | } 52 | } 53 | 54 | static unique_ptr QueryResultBind(ClientContext &context, TableFunctionBindInput &input, 55 | vector &return_types, vector &names) { 56 | 57 | if (input.inputs.size() != 1) { 58 | throw Exception(ExceptionType::BINDER, "QueryResult needs exactly one argument"); 59 | } 60 | 61 | const string query = input.inputs[0].GetValue(); 62 | Connection conn(*context.db); 63 | auto result = conn.Query(query); 64 | 65 | if (result->HasError()) { 66 | throw Exception(result->GetErrorObject().Type(), result->GetErrorObject().RawMessage()); 67 | } 68 | 69 | for (int col_idx = 0; col_idx < result->ColumnCount(); col_idx ++) { 70 | return_types.push_back(result->types[col_idx]); 71 | names.push_back(result->names[col_idx]); 72 | } 73 | 74 | auto data = make_uniq(query, std::move(result)); 75 | return std::move(data); 76 | 77 | } 78 | } // namespace duckdb -------------------------------------------------------------------------------- /scripts/install_python_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Detect if running as root (for Docker) 5 | if [ "$(id -u)" -eq 0 ]; then 6 | SUDO="" 7 | else 8 | if command -v sudo &> /dev/null; then 9 | SUDO="sudo" 10 | else 11 | echo "Error: sudo not found and not running as root." 12 | exit 1 13 | fi 14 | fi 15 | 16 | # Detect and print distribution name 17 | if [ -f /etc/os-release ]; then 18 | . /etc/os-release 19 | echo "Detected distribution: $NAME ($ID)" 20 | DISTRO_FAMILY="$ID_LIKE" 21 | DISTRO_ID="$ID" 22 | elif [ -f /etc/debian_version ]; then 23 | echo "Detected distribution: Debian (no /etc/os-release)" 24 | DISTRO_ID="debian" 25 | elif [ -f /etc/redhat-release ]; then 26 | echo "Detected distribution: Red Hat-based (no /etc/os-release)" 27 | DISTRO_ID="rhel" 28 | else 29 | echo "Could not detect Linux distribution." 30 | exit 1 31 | fi 32 | 33 | # Check if musl or glibc 34 | if ldd --version 2>&1 | grep -q "musl"; then 35 | echo "Detected musl-based system (Alpine/musl)." 36 | MUSL_SYSTEM=true 37 | else 38 | echo "Detected glibc-based system." 39 | MUSL_SYSTEM=false 40 | fi 41 | 42 | # Function to install Python 43 | install_python() { 44 | if ! command -v python3 &> /dev/null; then 45 | echo "Python not found. Installing via package manager..." 46 | 47 | if $MUSL_SYSTEM; then 48 | echo "Installing Python for musl-based system (Alpine)..." 49 | $SUDO apk add --no-cache python3 50 | else 51 | case "$DISTRO_ID" in 52 | ubuntu|debian) 53 | $SUDO apt-get update 54 | $SUDO apt-get install -y python3 55 | ;; 56 | fedora) 57 | $SUDO dnf install -y python3 58 | ;; 59 | centos|rhel|almalinux|rocky) 60 | $SUDO yum install -y python3 61 | ;; 62 | opensuse*|sles) 63 | $SUDO zypper install -y python3 64 | ;; 65 | arch) 66 | $SUDO pacman -Sy --noconfirm python 67 | ;; 68 | *) 69 | if [[ "$DISTRO_FAMILY" =~ debian ]]; then 70 | $SUDO apt-get update 71 | $SUDO apt-get install -y python3 72 | elif [[ "$DISTRO_FAMILY" =~ rhel|fedora ]]; then 73 | $SUDO yum install -y python3 74 | else 75 | echo "Unsupported distribution: $NAME ($ID)" 76 | exit 1 77 | fi 78 | ;; 79 | esac 80 | fi 81 | else 82 | echo "Python is already installed." 83 | fi 84 | } 85 | 86 | install_python 87 | -------------------------------------------------------------------------------- /src/include/result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "http_error_data.hpp" 4 | 5 | namespace duckdb { 6 | 7 | #define RETURN_IF_ERROR(result) \ 8 | { \ 9 | if (result.HasError()) { \ 10 | return result.GetError(); \ 11 | } \ 12 | } 13 | #define RETURN_IF_ERROR_CB(result, cb) \ 14 | { \ 15 | if (result.HasError()) { \ 16 | cb(result.GetError()); \ 17 | return; \ 18 | } \ 19 | } 20 | 21 | template 22 | struct disjunction : std::false_type {}; 23 | 24 | template 25 | struct disjunction : B1 {}; 26 | 27 | template 28 | struct disjunction : std::conditional>::type {}; 29 | 30 | template 31 | class Result { 32 | public: 33 | template ::type..., HttpErrorData>::value, int>::type = 0> 35 | Result(Args &&...args) : value(make_uniq(std::forward(args)...)) { 36 | } 37 | 38 | Result(HttpErrorData &&_error) : error(std::forward(_error)) { 39 | D_ASSERT(error.HasError()); 40 | } 41 | 42 | Result(const HttpErrorData &_error) : error(_error) { 43 | D_ASSERT(error.HasError()); 44 | } 45 | 46 | Result(const StatusCode _code, ErrorData &&_error) : error(_code, _error) { 47 | D_ASSERT(error.HasError()); 48 | } 49 | 50 | Result(const StatusCode _code, const ErrorData &_error) : error(_code, _error) { 51 | D_ASSERT(error.HasError()); 52 | } 53 | 54 | bool HasError() const { 55 | return error.HasError(); 56 | } 57 | 58 | HttpErrorData GetError() { 59 | D_ASSERT(HasError()); 60 | return error; 61 | } 62 | 63 | T &GetValue() { 64 | D_ASSERT(!HasError()); 65 | return value; 66 | } 67 | 68 | T *operator->() { 69 | D_ASSERT(!HasError()); 70 | return value.get(); 71 | } 72 | 73 | T &operator*() { 74 | D_ASSERT(!HasError()); 75 | return value; 76 | } 77 | 78 | private: 79 | unique_ptr value; 80 | HttpErrorData error; 81 | }; 82 | 83 | } // namespace duckdb 84 | -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_app_not_found_page_d9abfad02928c6f9_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_APP_NOT_FOUND_PAGE_D9ABFAD02928C6F9_JS = { 6 | // Content 7 | "KHNlbGYud2VicGFja0NodW5rX05fRT1zZWxmLndlYnBhY2tDaHVua19OX0V8fFtdKS5wdXNoKFtbNDkyXSx7MWUzOihlLHQscik9PnsidXNlIHN0cmljdCI7T2JqZWN0LmRlZmluZVByb3BlcnR5KHQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJkZWZhdWx0Iix7ZW51bWVyYWJsZTohMCxnZXQ6ZnVuY3Rpb24oKXtyZXR1cm4gb319KTtsZXQgbD1yKDc2NDk1KSxuPXIoMTI1OTUpO2Z1bmN0aW9uIG8oKXtyZXR1cm4oMCxsLmpzeCkobi5IVFRQQWNjZXNzRXJyb3JGYWxsYmFjayx7c3RhdHVzOjQwNCxtZXNzYWdlOiJUaGlzIHBhZ2UgY291bGQgbm90IGJlIGZvdW5kLiJ9KX0oImZ1bmN0aW9uIj09dHlwZW9mIHQuZGVmYXVsdHx8Im9iamVjdCI9PXR5cGVvZiB0LmRlZmF1bHQmJm51bGwhPT10LmRlZmF1bHQpJiZ2b2lkIDA9PT10LmRlZmF1bHQuX19lc01vZHVsZSYmKE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LmRlZmF1bHQsIl9fZXNNb2R1bGUiLHt2YWx1ZTohMH0pLE9iamVjdC5hc3NpZ24odC5kZWZhdWx0LHQpLGUuZXhwb3J0cz10LmRlZmF1bHQpfSwxMjU5NTooZSx0LHIpPT57InVzZSBzdHJpY3QiO09iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KSxPYmplY3QuZGVmaW5lUHJvcGVydHkodCwiSFRUUEFjY2Vzc0Vycm9yRmFsbGJhY2siLHtlbnVtZXJhYmxlOiEwLGdldDpmdW5jdGlvbigpe3JldHVybiBvfX0pLHIoOTIwMTEpO2xldCBsPXIoNzY0OTUpO3IoNjA5NjcpO2xldCBuPXtlcnJvcjp7Zm9udEZhbWlseTonc3lzdGVtLXVpLCJTZWdvZSBVSSIsUm9ib3RvLEhlbHZldGljYSxBcmlhbCxzYW5zLXNlcmlmLCJBcHBsZSBDb2xvciBFbW9qaSIsIlNlZ29lIFVJIEVtb2ppIicsaGVpZ2h0OiIxMDB2aCIsdGV4dEFsaWduOiJjZW50ZXIiLGRpc3BsYXk6ImZsZXgiLGZsZXhEaXJlY3Rpb246ImNvbHVtbiIsYWxpZ25JdGVtczoiY2VudGVyIixqdXN0aWZ5Q29udGVudDoiY2VudGVyIn0sZGVzYzp7ZGlzcGxheToiaW5saW5lLWJsb2NrIn0saDE6e2Rpc3BsYXk6ImlubGluZS1ibG9jayIsbWFyZ2luOiIwIDIwcHggMCAwIixwYWRkaW5nOiIwIDIzcHggMCAwIixmb250U2l6ZToyNCxmb250V2VpZ2h0OjUwMCx2ZXJ0aWNhbEFsaWduOiJ0b3AiLGxpbmVIZWlnaHQ6IjQ5cHgifSxoMjp7Zm9udFNpemU6MTQsZm9udFdlaWdodDo0MDAsbGluZUhlaWdodDoiNDlweCIsbWFyZ2luOjB9fTtmdW5jdGlvbiBvKGUpe2xldHtzdGF0dXM6dCxtZXNzYWdlOnJ9PWU7cmV0dXJuKDAsbC5qc3hzKShsLkZyYWdtZW50LHtjaGlsZHJlbjpbKDAsbC5qc3gpKCJ0aXRsZSIse2NoaWxkcmVuOnQrIjogIityfSksKDAsbC5qc3gpKCJkaXYiLHtzdHlsZTpuLmVycm9yLGNoaWxkcmVuOigwLGwuanN4cykoImRpdiIse2NoaWxkcmVuOlsoMCxsLmpzeCkoInN0eWxlIix7ZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUw6e19faHRtbDoiYm9keXtjb2xvcjojMDAwO2JhY2tncm91bmQ6I2ZmZjttYXJnaW46MH0ubmV4dC1lcnJvci1oMXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMCwwLDAsLjMpfUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6ZGFyayl7Ym9keXtjb2xvcjojZmZmO2JhY2tncm91bmQ6IzAwMH0ubmV4dC1lcnJvci1oMXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMjU1LDI1NSwyNTUsLjMpfX0ifX0pLCgwLGwuanN4KSgiaDEiLHtjbGFzc05hbWU6Im5leHQtZXJyb3ItaDEiLHN0eWxlOm4uaDEsY2hpbGRyZW46dH0pLCgwLGwuanN4KSgiZGl2Iix7c3R5bGU6bi5kZXNjLGNoaWxkcmVuOigwLGwuanN4KSgiaDIiLHtzdHlsZTpuLmgyLGNoaWxkcmVuOnJ9KX0pXX0pfSldfSl9KCJmdW5jdGlvbiI9PXR5cGVvZiB0LmRlZmF1bHR8fCJvYmplY3QiPT10eXBlb2YgdC5kZWZhdWx0JiZudWxsIT09dC5kZWZhdWx0KSYmdm9pZCAwPT09dC5kZWZhdWx0Ll9fZXNNb2R1bGUmJihPYmplY3QuZGVmaW5lUHJvcGVydHkodC5kZWZhdWx0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KSxPYmplY3QuYXNzaWduKHQuZGVmYXVsdCx0KSxlLmV4cG9ydHM9dC5kZWZhdWx0KX0sNjA4NjQ6KGUsdCxyKT0+eyh3aW5kb3cuX19ORVhUX1A9d2luZG93Ll9fTkVYVF9QfHxbXSkucHVzaChbIi9fbm90LWZvdW5kL3BhZ2UiLGZ1bmN0aW9uKCl7cmV0dXJuIHIoMWUzKX1dKX19LGU9Pnt2YXIgdD10PT5lKGUucz10KTtlLk8oMCxbMjc0LDQyNCwzNThdLCgpPT50KDYwODY0KSksX05fRT1lLk8oKX1dKTs=", // 8 | 2992, // 9 | "application/javascript", // 10 | "/_next/static/chunks/app/_not-found/page-d9abfad02928c6f9.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /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 -e 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 | # calculate SHA256 hash of extension binary 27 | cat $ext > $ext.append 28 | 29 | if [[ $4 == wasm* ]]; then 30 | # 0 for custom section 31 | # 113 in hex = 275 in decimal, total lenght of what follows (1 + 16 + 2 + 256) 32 | # [1(continuation) + 0010011(payload) = \x93, 0(continuation) + 10(payload) = \x02] 33 | echo -n -e '\x00' >> $ext.append 34 | echo -n -e '\x93\x02' >> $ext.append 35 | # 10 in hex = 16 in decimal, lenght of name, 1 byte 36 | echo -n -e '\x10' >> $ext.append 37 | echo -n -e 'duckdb_signature' >> $ext.append 38 | # the name of the WebAssembly custom section, 16 bytes 39 | # 100 in hex, 256 in decimal 40 | # [1(continuation) + 0000000(payload) = ff, 0(continuation) + 10(payload)], 41 | # for a grand total of 2 bytes 42 | echo -n -e '\x80\x02' >> $ext.append 43 | fi 44 | 45 | # (Optionally) Sign binary 46 | if [ "$DUCKDB_EXTENSION_SIGNING_PK" != "" ]; then 47 | echo "$DUCKDB_EXTENSION_SIGNING_PK" > private.pem 48 | $script_dir/../duckdb/scripts/compute-extension-hash.sh $ext.append > $ext.hash 49 | openssl pkeyutl -sign -in $ext.hash -inkey private.pem -pkeyopt digest:sha256 -out $ext.sign 50 | rm -f private.pem 51 | fi 52 | 53 | # Signature is always there, potentially defaulting to 256 zeros 54 | truncate -s 256 $ext.sign 55 | 56 | # append signature to extension binary 57 | cat $ext.sign >> $ext.append 58 | 59 | # compress extension binary 60 | if [[ $4 == wasm_* ]]; then 61 | brotli < $ext.append > "$ext.compressed" 62 | else 63 | gzip < $ext.append > "$ext.compressed" 64 | fi 65 | 66 | set -e 67 | 68 | # Abort if AWS key is not set 69 | if [ -z "$AWS_ACCESS_KEY_ID" ]; then 70 | echo "No AWS key found, skipping.." 71 | exit 0 72 | fi 73 | 74 | # upload versioned version 75 | if [[ $7 = 'true' ]]; then 76 | if [[ $4 == wasm* ]]; then 77 | 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" 78 | else 79 | aws s3 cp $ext.compressed s3://$5/$1/$2/$3/$4/$1.duckdb_extension.gz --acl public-read 80 | fi 81 | fi 82 | 83 | # upload to latest version 84 | if [[ $6 = 'true' ]]; then 85 | if [[ $4 == wasm* ]]; then 86 | aws s3 cp $ext.compressed s3://$5/$3/$4/$1.duckdb_extension.wasm --acl public-read --content-encoding br --content-type="application/wasm" 87 | else 88 | aws s3 cp $ext.compressed s3://$5/$3/$4/$1.duckdb_extension.gz --acl public-read 89 | fi 90 | fi 91 | -------------------------------------------------------------------------------- /src/include/table_functions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "table_functions_bind_data.hpp" 3 | 4 | namespace duckdb { 5 | struct RunOnceGlobalTableFunctionState final : GlobalTableFunctionState { 6 | std::atomic_bool has_run; 7 | 8 | static unique_ptr Init(ClientContext &, TableFunctionInitInput &) { 9 | return make_uniq_base(); 10 | } 11 | }; 12 | 13 | inline void StartHttpServer(ClientContext &context, TableFunctionInput &data, DataChunk &output) { 14 | auto &g_state = data.global_state->Cast(); 15 | if (g_state.has_run.exchange(true)) { 16 | return; 17 | } 18 | 19 | auto &input = data.bind_data->Cast(); 20 | 21 | GetServer(context).Start(context, input); 22 | 23 | output.SetCardinality(1); 24 | output.SetValue(0, 0, true); 25 | } 26 | 27 | inline unique_ptr BindStopHttpServer(ClientContext &, TableFunctionBindInput &, 28 | vector &return_types, vector &names) { 29 | return_types.push_back(LogicalType::BOOLEAN); 30 | names.push_back("success"); 31 | 32 | return make_uniq_base(); 33 | } 34 | 35 | inline void StopHttpServer(ClientContext &context, TableFunctionInput &data, DataChunk &output) { 36 | auto &g_state = data.global_state->Cast(); 37 | if (g_state.has_run.exchange(true)) { 38 | return; 39 | } 40 | 41 | GetServer(context).Stop(); 42 | 43 | output.SetCardinality(1); 44 | output.SetValue(0, 0, true); 45 | } 46 | 47 | template 48 | T GetOrDefault(const TableFunctionBindInput &data, const string &key, T default_value, 49 | const std::function &validator = {}) { 50 | if (data.named_parameters.find(key) != data.named_parameters.end()) { 51 | auto param = data.named_parameters.at(key).GetValue(); 52 | if (validator) { 53 | validator(param); 54 | } 55 | return param; 56 | } 57 | return default_value; 58 | } 59 | 60 | inline unique_ptr BindStartHttpServer(ClientContext &, TableFunctionBindInput &input, 61 | vector &return_types, vector &names) { 62 | auto host = input.inputs[0].GetValue(); 63 | auto port = input.inputs[1].GetValue(); 64 | 65 | return_types.push_back(LogicalType::BOOLEAN); 66 | names.push_back("success"); 67 | 68 | const auto api_key = GetOrDefault(input, "api_key", "", [](const string &value) { 69 | if (value.empty()) { 70 | throw BinderException("api_key cannot be an empty string"); 71 | } 72 | }); 73 | 74 | const auto enable_cors = GetOrDefault(input, "enable_cors", false); 75 | const auto open_browser = GetOrDefault(input, "open_browser", false); 76 | const auto ui_proxy = GetOrDefault(input, "ui_proxy", "", [](const string &value) { 77 | const auto uri = Uri::Parse(value); 78 | uri.AssertValid(); 79 | if (uri.Protocol != "http" && uri.Protocol != "https") { 80 | throw BinderException("ui_proxy must be an HTTP or HTTPS URL"); 81 | } 82 | if (!uri.QueryString.empty()) { 83 | throw BinderException("ui_proxy cannot contain a query string"); 84 | } 85 | }); 86 | 87 | return make_uniq_base(host, port, api_key, enable_cors, open_browser, 88 | Uri::Parse(ui_proxy)); 89 | } 90 | 91 | } // namespace duckdb 92 | -------------------------------------------------------------------------------- /src/include/uri.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace duckdb { 6 | struct Uri { 7 | // FROM https://stackoverflow.com/a/11044337 8 | std::string QueryString {}; 9 | std::string Path {}; 10 | std::string Protocol {}; 11 | std::string Host {}; 12 | std::string Port {}; 13 | 14 | static Uri Parse(const std::string &uri) { 15 | Uri result {}; 16 | 17 | typedef std::string::const_iterator iterator_t; 18 | 19 | if (uri.length() == 0) { 20 | return result; 21 | } 22 | 23 | iterator_t uriEnd = uri.end(); 24 | 25 | // get query start 26 | iterator_t queryStart = std::find(uri.begin(), uriEnd, L'?'); 27 | 28 | // protocol 29 | iterator_t protocolStart = uri.begin(); 30 | iterator_t protocolEnd = std::find(protocolStart, uriEnd, L':'); //"://"); 31 | 32 | if (protocolEnd != uriEnd) { 33 | std::string prot = &*(protocolEnd); 34 | if ((prot.length() > 3) && (prot.substr(0, 3) == "://")) { 35 | result.Protocol = std::string(protocolStart, protocolEnd); 36 | protocolEnd += 3; // :// 37 | } else 38 | protocolEnd = uri.begin(); // no protocol 39 | } else { 40 | // no protocol 41 | protocolEnd = uri.begin(); 42 | } 43 | 44 | // host 45 | iterator_t hostStart = protocolEnd; 46 | iterator_t pathStart = std::find(hostStart, uriEnd, L'/'); // get pathStart 47 | 48 | iterator_t hostEnd = std::find(protocolEnd, (pathStart != uriEnd) ? pathStart : queryStart, 49 | L':'); // check for port 50 | 51 | result.Host = std::string(hostStart, hostEnd); 52 | 53 | // port 54 | if ((hostEnd != uriEnd) && ((&*(hostEnd))[0] == L':')) { 55 | hostEnd++; 56 | iterator_t portEnd = (pathStart != uriEnd) ? pathStart : queryStart; 57 | result.Port = std::string(hostEnd, portEnd); 58 | } 59 | 60 | // path 61 | if (pathStart != uriEnd) { 62 | result.Path = std::string(pathStart, queryStart); 63 | } 64 | 65 | // query 66 | if (queryStart != uriEnd) { 67 | result.QueryString = std::string(queryStart, uri.end()); 68 | } 69 | 70 | if (result.Port.empty()) { 71 | if (result.Protocol == "http") { 72 | result.Port = "80"; 73 | } else if (result.Protocol == "https") { 74 | result.Port = "443"; 75 | } 76 | } 77 | 78 | return result; 79 | } 80 | 81 | void AssertValid() const { 82 | if (Protocol.empty()) { 83 | throw BinderException("Protocol cannot be empty"); 84 | } 85 | 86 | if (Host.empty()) { 87 | throw BinderException("Host cannot be empty"); 88 | } 89 | 90 | if (Port.empty()) { 91 | throw BinderException("Port cannot be empty"); 92 | } 93 | 94 | if (ParsedPort() <= 0) { 95 | throw BinderException("Port must be a positive integer"); 96 | } 97 | } 98 | 99 | bool Valid() const { 100 | try { 101 | AssertValid(); 102 | return true; 103 | } catch (...) { 104 | return false; 105 | } 106 | } 107 | 108 | 109 | 110 | bool operator==(const Uri &uri) const { 111 | return QueryString == uri.QueryString && Path == uri.Path && Protocol == uri.Protocol && Host == uri.Host && 112 | Port == uri.Port; 113 | } 114 | 115 | int ParsedPort() const { 116 | try { 117 | return std::stoi(Port); 118 | } catch (...) { 119 | return -1; 120 | } 121 | } 122 | 123 | string ToString() const { 124 | string result = Protocol + "://" + Host; 125 | if (Port != "80" && Port != "443") { 126 | result += ":" + Port; 127 | } 128 | result += Path; 129 | if (!QueryString.empty()) { 130 | result += "?" + QueryString; 131 | } 132 | return result; 133 | } 134 | 135 | string HostWithProtocol() const { 136 | string result = Protocol + "://" + Host; 137 | if (Port != "80" && Port != "443") { 138 | result += ":" + Port; 139 | } 140 | 141 | return result; 142 | } 143 | }; 144 | } // namespace duckdb 145 | -------------------------------------------------------------------------------- /src/gen/files.cpp: -------------------------------------------------------------------------------- 1 | #include "files.hpp" 2 | 3 | #include "duckdb/common/optional_ptr.hpp" 4 | #include "duckdb/common/string_util.hpp" 5 | 6 | #include "file_icon_png.hpp" 7 | #include "file_favicon_ico.hpp" 8 | #include "file_index_html.hpp" 9 | #include "file_apple_icon_png.hpp" 10 | #include "file_index_txt.hpp" 11 | #include "file_404_html.hpp" 12 | #include "file_icon_svg.hpp" 13 | #include "file_manifest_json.hpp" 14 | #include "file_next_static_css_60b2e10d0c51dc30_css.hpp" 15 | #include "file_next_static_css_21bb8425bd7146cc_css.hpp" 16 | #include "file_next_static_css_5b54331129c9114c_css.hpp" 17 | #include "file_next_static_MBo1aAX1_T01D11kt_FJe_ssgManifest_js.hpp" 18 | #include "file_next_static_MBo1aAX1_T01D11kt_FJe_buildManifest_js.hpp" 19 | #include "file_next_static_chunks_75146d7d_09aa1a27ef8ffd85_js.hpp" 20 | #include "file_next_static_chunks_dc10eadf_140341d4bbe49ac0_js.hpp" 21 | #include "file_next_static_chunks_273acdc0_074651c6dcce57aa_js.hpp" 22 | #include "file_next_static_chunks_122_3ec9e1262bd14e23_js.hpp" 23 | #include "file_next_static_chunks_93bfa88f_cc391f3208003935_js.hpp" 24 | #include "file_next_static_chunks_872_6e627e6a293ed396_js.hpp" 25 | #include "file_next_static_chunks_framework_f238c7b8f578dd52_js.hpp" 26 | #include "file_next_static_chunks_0b42ce73_8a46a4463856ecb8_js.hpp" 27 | #include "file_next_static_chunks_32fa3855_229c70a9f91729d1_js.hpp" 28 | #include "file_next_static_chunks_main_2e8571477b54eb52_js.hpp" 29 | #include "file_next_static_chunks_6bb80806_a5d608ad39020eee_js.hpp" 30 | #include "file_next_static_chunks_3c1c9f4a_13941a99703194ad_js.hpp" 31 | #include "file_next_static_chunks_webpack_9a68f0196a060eb0_js.hpp" 32 | #include "file_next_static_chunks_main_app_e3a92c18fc983fe1_js.hpp" 33 | #include "file_next_static_chunks_902_02a4e3e7dd6de078_js.hpp" 34 | #include "file_next_static_chunks_199_bc7f641ac29ae1f4_js.hpp" 35 | #include "file_next_static_chunks_8d466849_3e1c16aabe33caff_js.hpp" 36 | #include "file_next_static_chunks_696_b636fe34b79a4e00_js.hpp" 37 | #include "file_next_static_chunks_424_e724fd0d45236c50_js.hpp" 38 | #include "file_next_static_chunks_polyfills_42372ed130431b0a_js.hpp" 39 | #include "file_next_static_chunks_29_f6aedeb04133da7f_js.hpp" 40 | #include "file_next_static_chunks_app_page_2a1fba065de39cb8_js.hpp" 41 | #include "file_next_static_chunks_app_layout_a227b7c884d754eb_js.hpp" 42 | #include "file_next_static_chunks_app_not_found_page_d9abfad02928c6f9_js.hpp" 43 | #include "file_next_static_chunks_pages_error_9e91ec86137a41e3_js.hpp" 44 | #include "file_next_static_chunks_pages_app_fd7e2f06c907c6fa_js.hpp" 45 | #include "file_next_static_media_7323b9d087306adb_s_p_woff2.hpp" 46 | #include "file_next_static_media_959fe13ebd2a94e9_s_woff2.hpp" 47 | #include "file_favicon_web_app_manifest_192x192_png.hpp" 48 | #include "file_favicon_web_app_manifest_512x512_png.hpp" 49 | #include "file_fonts_Urbanist_VariableFont_wght_ttf.hpp" 50 | 51 | #include 52 | 53 | namespace duckdb { 54 | std::vector files = { 55 | FILE_ICON_PNG,FILE_FAVICON_ICO,FILE_INDEX_HTML,FILE_APPLE_ICON_PNG,FILE_INDEX_TXT,FILE_404_HTML,FILE_ICON_SVG,FILE_MANIFEST_JSON,FILE_NEXT_STATIC_CSS_60B2E10D0C51DC30_CSS,FILE_NEXT_STATIC_CSS_21BB8425BD7146CC_CSS,FILE_NEXT_STATIC_CSS_5B54331129C9114C_CSS,FILE_NEXT_STATIC_MBO1AAX1_T01D11KT_FJE_SSGMANIFEST_JS,FILE_NEXT_STATIC_MBO1AAX1_T01D11KT_FJE_BUILDMANIFEST_JS,FILE_NEXT_STATIC_CHUNKS_75146D7D_09AA1A27EF8FFD85_JS,FILE_NEXT_STATIC_CHUNKS_DC10EADF_140341D4BBE49AC0_JS,FILE_NEXT_STATIC_CHUNKS_273ACDC0_074651C6DCCE57AA_JS,FILE_NEXT_STATIC_CHUNKS_122_3EC9E1262BD14E23_JS,FILE_NEXT_STATIC_CHUNKS_93BFA88F_CC391F3208003935_JS,FILE_NEXT_STATIC_CHUNKS_872_6E627E6A293ED396_JS,FILE_NEXT_STATIC_CHUNKS_FRAMEWORK_F238C7B8F578DD52_JS,FILE_NEXT_STATIC_CHUNKS_0B42CE73_8A46A4463856ECB8_JS,FILE_NEXT_STATIC_CHUNKS_32FA3855_229C70A9F91729D1_JS,FILE_NEXT_STATIC_CHUNKS_MAIN_2E8571477B54EB52_JS,FILE_NEXT_STATIC_CHUNKS_6BB80806_A5D608AD39020EEE_JS,FILE_NEXT_STATIC_CHUNKS_3C1C9F4A_13941A99703194AD_JS,FILE_NEXT_STATIC_CHUNKS_WEBPACK_9A68F0196A060EB0_JS,FILE_NEXT_STATIC_CHUNKS_MAIN_APP_E3A92C18FC983FE1_JS,FILE_NEXT_STATIC_CHUNKS_902_02A4E3E7DD6DE078_JS,FILE_NEXT_STATIC_CHUNKS_199_BC7F641AC29AE1F4_JS,FILE_NEXT_STATIC_CHUNKS_8D466849_3E1C16AABE33CAFF_JS,FILE_NEXT_STATIC_CHUNKS_696_B636FE34B79A4E00_JS,FILE_NEXT_STATIC_CHUNKS_424_E724FD0D45236C50_JS,FILE_NEXT_STATIC_CHUNKS_POLYFILLS_42372ED130431B0A_JS,FILE_NEXT_STATIC_CHUNKS_29_F6AEDEB04133DA7F_JS,FILE_NEXT_STATIC_CHUNKS_APP_PAGE_2A1FBA065DE39CB8_JS,FILE_NEXT_STATIC_CHUNKS_APP_LAYOUT_A227B7C884D754EB_JS,FILE_NEXT_STATIC_CHUNKS_APP_NOT_FOUND_PAGE_D9ABFAD02928C6F9_JS,FILE_NEXT_STATIC_CHUNKS_PAGES_ERROR_9E91EC86137A41E3_JS,FILE_NEXT_STATIC_CHUNKS_PAGES_APP_FD7E2F06C907C6FA_JS,FILE_NEXT_STATIC_MEDIA_7323B9D087306ADB_S_P_WOFF2,FILE_NEXT_STATIC_MEDIA_959FE13EBD2A94E9_S_WOFF2,FILE_FAVICON_WEB_APP_MANIFEST_192X192_PNG,FILE_FAVICON_WEB_APP_MANIFEST_512X512_PNG,FILE_FONTS_URBANIST_VARIABLEFONT_WGHT_TTF 56 | }; 57 | 58 | optional_ptr GetFile(Path path, const bool try_resolve_404) { 59 | if (path.empty() || path == "/") { 60 | path = "index.html"; 61 | } 62 | 63 | // Append / before and after 64 | path = "/" + path + "/"; 65 | path = StringUtil::Replace(path, "//", "/"); 66 | 67 | for (auto &file : files) { 68 | if (file.path == path) { 69 | return file; 70 | } 71 | } 72 | 73 | if (try_resolve_404) { 74 | GetFile("/404.html/", false); 75 | } 76 | 77 | return nullptr; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/dash_extension.cpp: -------------------------------------------------------------------------------- 1 | 2 | #define DUCKDB_EXTENSION_MAIN 3 | #define DUCKDB_VERSION_ENCODE(major, minor, patch) ((major) * 10000 + (minor) * 100 + (patch)) 4 | #define DUCKDB_CURRENT_VERSION DUCKDB_VERSION_ENCODE(DUCKDB_MAJOR_VERSION, DUCKDB_MINOR_VERSION, DUCKDB_PATCH_VERSION) 5 | #define STRINGIFY2(x) #x 6 | #define STRINGIFY(x) STRINGIFY2(x) 7 | 8 | #define DUCKDB_VERSION_CODE DUCKDB_VERSION_ENCODE(DUCKDB_MAJOR_VERSION, DUCKDB_MINOR_VERSION, DUCKDB_PATCH_VERSION) 9 | 10 | // Manual calculation since pragma can't evaluate arithmetic expressions 11 | #pragma message("DUCKDB version code: " STRINGIFY(DUCKDB_VERSION_CODE)) 12 | 13 | #include "include/dash_extension.hpp" 14 | 15 | #ifndef EMSCRIPTEN 16 | #include "include/http_server.hpp" 17 | #include "include/table_functions.hpp" 18 | #endif 19 | #include "query_result_table_function.hpp" 20 | 21 | #if DUCKDB_CURRENT_VERSION < DUCKDB_VERSION_ENCODE(1, 3, 3) 22 | #include "duckdb/main/extension_util.hpp" 23 | #endif 24 | 25 | 26 | 27 | 28 | namespace duckdb { 29 | 30 | #if DUCKDB_CURRENT_VERSION >= DUCKDB_VERSION_ENCODE(1, 3, 3) 31 | 32 | #pragma message("Loading with ExtensionLoader (DuckDB >= 1.4.0)") 33 | 34 | static void LoadInternal(ExtensionLoader &loader) { 35 | Connection conn(loader.GetDatabaseInstance()); 36 | conn.BeginTransaction(); 37 | #ifndef EMSCRIPTEN 38 | { 39 | TableFunction tf(std::string("start_dash"), 40 | { 41 | LogicalType::VARCHAR, // Host 42 | LogicalType::INTEGER // Port 43 | }, 44 | StartHttpServer, BindStartHttpServer, RunOnceGlobalTableFunctionState::Init); 45 | tf.named_parameters["api_key"] = LogicalType::VARCHAR; 46 | tf.named_parameters["enable_cors"] = LogicalType::BOOLEAN; 47 | tf.named_parameters["ui_proxy"] = LogicalType::VARCHAR; 48 | tf.named_parameters["open_browser"] = LogicalType::BOOLEAN; 49 | loader.RegisterFunction(tf); 50 | }{ 51 | 52 | TableFunction tf(std::string("stop_dash"), {}, StopHttpServer, BindStopHttpServer, 53 | RunOnceGlobalTableFunctionState::Init); 54 | loader.RegisterFunction(tf); 55 | } 56 | #endif 57 | { 58 | 59 | const pragma_query_t PragmaDash = [](ClientContext &context, const FunctionParameters &type) -> string { 60 | return "CALL start_dash('localhost', 4200, api_key=CAST(CAST(round(random() * 1000000) AS INT) AS String), enable_cors=False, open_browser=True)"; 61 | }; 62 | 63 | PragmaFunction dash = PragmaFunction::PragmaCall("dash", PragmaDash, {}); 64 | loader.RegisterFunction(dash); 65 | } 66 | 67 | TableFunction query_result("query_result", {LogicalType::VARCHAR}, QueryResultFun, QueryResultBind, QueryResultState::Init); 68 | loader.RegisterFunction(query_result); 69 | conn.Commit(); 70 | } 71 | 72 | void DashExtension::Load(ExtensionLoader &loader) { 73 | LoadInternal(loader); 74 | } 75 | 76 | extern "C" { 77 | 78 | DUCKDB_CPP_EXTENSION_ENTRY(dash, loader) { 79 | duckdb::LoadInternal(loader); 80 | } 81 | } 82 | 83 | 84 | // *** DUCKDB < v1.3.3 *** 85 | 86 | #else 87 | 88 | #include "duckdb/main/extension_util.hpp" 89 | #pragma message("Loading with Extension Utils (DuckDB < 1.4.0)") 90 | 91 | 92 | static void LoadInternal(DatabaseInstance &instance) { 93 | Connection conn(instance); 94 | conn.BeginTransaction(); 95 | #ifndef EMSCRIPTEN 96 | { 97 | TableFunction tf(std::string("start_dash"), 98 | { 99 | LogicalType::VARCHAR, // Host 100 | LogicalType::INTEGER // Port 101 | }, 102 | StartHttpServer, BindStartHttpServer, RunOnceGlobalTableFunctionState::Init); 103 | tf.named_parameters["api_key"] = LogicalType::VARCHAR; 104 | tf.named_parameters["enable_cors"] = LogicalType::BOOLEAN; 105 | tf.named_parameters["ui_proxy"] = LogicalType::VARCHAR; 106 | tf.named_parameters["open_browser"] = LogicalType::BOOLEAN; 107 | ExtensionUtil::RegisterFunction(instance, tf); 108 | }{ 109 | 110 | TableFunction tf(std::string("stop_dash"), {}, StopHttpServer, BindStopHttpServer, 111 | RunOnceGlobalTableFunctionState::Init); 112 | ExtensionUtil::RegisterFunction(instance, tf); 113 | } 114 | #endif 115 | { 116 | 117 | const pragma_query_t PragmaDash = [](ClientContext &context, const FunctionParameters &type) -> string { 118 | return "CALL start_dash('localhost', 4200, api_key=CAST(CAST(round(random() * 1000000) AS INT) AS String), enable_cors=False, open_browser=True)"; 119 | }; 120 | 121 | PragmaFunction dash = PragmaFunction::PragmaCall("dash", PragmaDash, {}); 122 | ExtensionUtil::RegisterFunction(instance, dash); 123 | 124 | } 125 | 126 | TableFunction query_result("query_result", {LogicalType::VARCHAR}, QueryResultFun, QueryResultBind, QueryResultState::Init); 127 | ExtensionUtil::RegisterFunction(instance, query_result); 128 | conn.Commit(); 129 | } 130 | 131 | void DashExtension::Load(DuckDB &db) { 132 | LoadInternal(*db.instance); 133 | } 134 | 135 | extern "C" { 136 | 137 | DUCKDB_EXTENSION_API void dash_init(duckdb::DatabaseInstance &db) { 138 | duckdb::DuckDB db_wrapper(db); 139 | db_wrapper.LoadExtension(); 140 | } 141 | 142 | DUCKDB_EXTENSION_API const char *dash_version() { 143 | return duckdb::DuckDB::LibraryVersion(); 144 | } 145 | } 146 | 147 | #endif 148 | 149 | 150 | #ifndef DUCKDB_EXTENSION_MAIN 151 | #error DUCKDB_EXTENSION_MAIN not defined 152 | #endif 153 | 154 | std::string DashExtension::Name() { 155 | return "dash"; 156 | } 157 | 158 | std::string DashExtension::Version() const { 159 | #ifdef EXT_VERSION_DASH 160 | return EXT_VERSION_DASH; 161 | #else 162 | return ""; 163 | #endif 164 | } 165 | 166 | } // namespace duckdb -------------------------------------------------------------------------------- /openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Duck Explorer API 4 | version: 0.0.1 5 | servers: 6 | - url: http://localhost:4200 7 | paths: 8 | /: 9 | get: 10 | summary: View Web UI 11 | responses: 12 | '200': 13 | description: View the integrated web UI. 14 | content: 15 | text/html: 16 | schema: 17 | type: string 18 | /query: 19 | post: 20 | summary: Execute SQL query 21 | requestBody: 22 | content: 23 | application/json: 24 | schema: 25 | $ref: '#/components/schemas/QueryRequest' 26 | multipart/form-data: 27 | schema: 28 | type: object 29 | properties: 30 | query.json: 31 | allOf: 32 | - $ref: '#/components/schemas/QueryRequest' 33 | type: string 34 | title: JSON File 35 | format: application/json 36 | additionalProperties: 37 | type: string 38 | format: binary 39 | title: File 40 | description: "Files that can then be referenced in THIS query" 41 | required: [query.json] 42 | responses: 43 | '200': 44 | description: Query executed successfully. 45 | content: 46 | application/json: 47 | schema: 48 | $ref: '#/components/schemas/QueryResult' 49 | '400': 50 | description: Bad request. 51 | content: 52 | application/json: 53 | schema: 54 | $ref: '#/components/schemas/ErrorData' 55 | '401': 56 | description: Unauthorized. 57 | content: 58 | application/json: 59 | schema: 60 | $ref: '#/components/schemas/ErrorData' 61 | '500': 62 | description: Internal server error. 63 | content: 64 | application/json: 65 | schema: 66 | $ref: '#/components/schemas/ErrorData' 67 | parameters: 68 | - in: header 69 | name: X-Api-Key 70 | schema: 71 | type: string 72 | required: false 73 | description: API key for authentication. 74 | /ping: 75 | get: 76 | summary: Is running 77 | responses: 78 | '200': 79 | description: Server is running. 80 | content: 81 | text/plain: 82 | schema: 83 | type: string 84 | example: pong 85 | components: 86 | schemas: 87 | Any: 88 | description: Can be any value - string, number, boolean, array or object. 89 | QueryRequest: 90 | type: object 91 | properties: 92 | query: 93 | type: string 94 | description: The SQL query to be executed. 95 | example: SELECT 1 96 | format: 97 | type: string 98 | description: The response format. 99 | enum: [ compact_json, json ] 100 | QueryResult: 101 | type: object 102 | properties: 103 | meta: 104 | type: array 105 | items: 106 | type: object 107 | properties: 108 | name: 109 | type: string 110 | type: 111 | type: string 112 | data: 113 | type: array 114 | items: 115 | type: array 116 | items: 117 | $ref: '#/components/schemas/Any' 118 | statistics: 119 | type: object 120 | properties: 121 | rows: 122 | type: integer 123 | format: int64 124 | required: 125 | - meta 126 | - data 127 | - statistics 128 | ErrorData: 129 | type: object 130 | properties: 131 | exception_type: 132 | $ref: '#/components/schemas/ExceptionType' 133 | exception_message: 134 | type: string 135 | description: The error message. 136 | extra_info: 137 | type: object 138 | additionalProperties: 139 | type: string 140 | description: Additional error information in key-value pairs. 141 | required: 142 | - exception_type 143 | - exception_message 144 | additionalProperties: true 145 | ExceptionType: 146 | description: The type of exception that occurred. 147 | type: string 148 | enum: 149 | - INVALID 150 | - OUT_OF_RANGE 151 | - CONVERSION 152 | - UNKNOWN_TYPE 153 | - DECIMAL 154 | - MISMATCH_TYPE 155 | - DIVIDE_BY_ZERO 156 | - OBJECT_SIZE 157 | - INVALID_TYPE 158 | - SERIALIZATION 159 | - TRANSACTION 160 | - NOT_IMPLEMENTED 161 | - EXPRESSION 162 | - CATALOG 163 | - PARSER 164 | - PLANNER 165 | - SCHEDULER 166 | - EXECUTOR 167 | - CONSTRAINT 168 | - INDEX 169 | - STAT 170 | - CONNECTION 171 | - SYNTAX 172 | - SETTINGS 173 | - BINDER 174 | - NETWORK 175 | - OPTIMIZER 176 | - NULL_POINTER 177 | - IO 178 | - INTERRUPT 179 | - FATAL 180 | - INTERNAL 181 | - INVALID_INPUT 182 | - OUT_OF_MEMORY 183 | - PERMISSION 184 | - PARAMETER_NOT_RESOLVED 185 | - PARAMETER_NOT_ALLOWED 186 | - DEPENDENCY 187 | - HTTP 188 | - MISSING_EXTENSION 189 | - AUTOLOAD 190 | - SEQUENCE 191 | - INVALID_CONFIGURATION -------------------------------------------------------------------------------- /src/gen/file_index_txt.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_INDEX_TXT = { 6 | // Content 7 | "MToiJFNyZWFjdC5mcmFnbWVudCIKMjpJWzM3NzMwLFtdLCJDbGllbnRTZWdtZW50Um9vdCJdCjM6SVszMDA0MyxbIjM0NiIsInN0YXRpYy9jaHVua3MvMjczYWNkYzAtMDc0NjUxYzZkY2NlNTdhYS5qcyIsIjY5NiIsInN0YXRpYy9jaHVua3MvNjk2LWI2MzZmZTM0Yjc5YTRlMDAuanMiLCIyOSIsInN0YXRpYy9jaHVua3MvMjktZjZhZWRlYjA0MTMzZGE3Zi5qcyIsIjE3NyIsInN0YXRpYy9jaHVua3MvYXBwL2xheW91dC1hMjI3YjdjODg0ZDc1NGViLmpzIl0sImRlZmF1bHQiXQo0OklbNDYxNTUsW10sIiJdCjU6SVs3MjQ3MSxbXSwiIl0KNzpJWzQyNzkwLFtdLCJDbGllbnRQYWdlUm9vdCJdCjg6SVszNTIwOSxbIjM0NiIsInN0YXRpYy9jaHVua3MvMjczYWNkYzAtMDc0NjUxYzZkY2NlNTdhYS5qcyIsIjY1NSIsInN0YXRpYy9jaHVua3MvNmJiODA4MDYtYTVkNjA4YWQzOTAyMGVlZS5qcyIsIjgyMiIsInN0YXRpYy9jaHVua3MvOTNiZmE4OGYtY2MzOTFmMzIwODAwMzkzNS5qcyIsIjQ0NyIsInN0YXRpYy9jaHVua3MvNzUxNDZkN2QtMDlhYTFhMjdlZjhmZmQ4NS5qcyIsIjkyMSIsInN0YXRpYy9jaHVua3MvM2MxYzlmNGEtMTM5NDFhOTk3MDMxOTRhZC5qcyIsIjIyNCIsInN0YXRpYy9jaHVua3MvMzJmYTM4NTUtMjI5YzcwYTlmOTE3MjlkMS5qcyIsIjk5MyIsInN0YXRpYy9jaHVua3MvMGI0MmNlNzMtOGE0NmE0NDYzODU2ZWNiOC5qcyIsIjY5NiIsInN0YXRpYy9jaHVua3MvNjk2LWI2MzZmZTM0Yjc5YTRlMDAuanMiLCI4NzIiLCJzdGF0aWMvY2h1bmtzLzg3Mi02ZTYyN2U2YTI5M2VkMzk2LmpzIiwiMjkiLCJzdGF0aWMvY2h1bmtzLzI5LWY2YWVkZWIwNDEzM2RhN2YuanMiLCI5NzQiLCJzdGF0aWMvY2h1bmtzL2FwcC9wYWdlLTJhMWZiYTA2NWRlMzljYjguanMiXSwiZGVmYXVsdCJdCmI6SVszMTc3LFtdLCJPdXRsZXRCb3VuZGFyeSJdCmU6SVszMTc3LFtdLCJWaWV3cG9ydEJvdW5kYXJ5Il0KMTA6SVszMTc3LFtdLCJNZXRhZGF0YUJvdW5kYXJ5Il0KMTI6SVs5NDk5MCxbXSwiIl0KOkhMWyIvX25leHQvc3RhdGljL2Nzcy8yMWJiODQyNWJkNzE0NmNjLmNzcyIsInN0eWxlIl0KOkhMWyIvX25leHQvc3RhdGljL21lZGlhLzczMjNiOWQwODczMDZhZGItcy5wLndvZmYyIiwiZm9udCIseyJjcm9zc09yaWdpbiI6IiIsInR5cGUiOiJmb250L3dvZmYyIn1dCjpITFsiL19uZXh0L3N0YXRpYy9jc3MvNjBiMmUxMGQwYzUxZGMzMC5jc3MiLCJzdHlsZSJdCjA6eyJQIjpudWxsLCJiIjoiTUJvMWFBWDFfVDAxRDExa3RfRkplIiwicCI6IiIsImMiOlsiIiwiIl0sImkiOmZhbHNlLCJmIjpbW1siIix7ImNoaWxkcmVuIjpbIl9fUEFHRV9fIix7fV19LCIkdW5kZWZpbmVkIiwiJHVuZGVmaW5lZCIsdHJ1ZV0sWyIiLFsiJCIsIiQxIiwiYyIseyJjaGlsZHJlbiI6W1tbIiQiLCJsaW5rIiwiMCIseyJyZWwiOiJzdHlsZXNoZWV0IiwiaHJlZiI6Ii9fbmV4dC9zdGF0aWMvY3NzLzIxYmI4NDI1YmQ3MTQ2Y2MuY3NzIiwicHJlY2VkZW5jZSI6Im5leHQiLCJjcm9zc09yaWdpbiI6IiR1bmRlZmluZWQiLCJub25jZSI6IiR1bmRlZmluZWQifV1dLFsiJCIsIiRMMiIsbnVsbCx7IkNvbXBvbmVudCI6IiQzIiwic2xvdHMiOnsiY2hpbGRyZW4iOlsiJCIsIiRMNCIsbnVsbCx7InBhcmFsbGVsUm91dGVyS2V5IjoiY2hpbGRyZW4iLCJlcnJvciI6IiR1bmRlZmluZWQiLCJlcnJvclN0eWxlcyI6IiR1bmRlZmluZWQiLCJlcnJvclNjcmlwdHMiOiIkdW5kZWZpbmVkIiwidGVtcGxhdGUiOlsiJCIsIiRMNSIsbnVsbCx7fV0sInRlbXBsYXRlU3R5bGVzIjoiJHVuZGVmaW5lZCIsInRlbXBsYXRlU2NyaXB0cyI6IiR1bmRlZmluZWQiLCJub3RGb3VuZCI6W1tbIiQiLCJ0aXRsZSIsbnVsbCx7ImNoaWxkcmVuIjoiNDA0OiBUaGlzIHBhZ2UgY291bGQgbm90IGJlIGZvdW5kLiJ9XSxbIiQiLCJkaXYiLG51bGwseyJzdHlsZSI6eyJmb250RmFtaWx5Ijoic3lzdGVtLXVpLFwiU2Vnb2UgVUlcIixSb2JvdG8sSGVsdmV0aWNhLEFyaWFsLHNhbnMtc2VyaWYsXCJBcHBsZSBDb2xvciBFbW9qaVwiLFwiU2Vnb2UgVUkgRW1vamlcIiIsImhlaWdodCI6IjEwMHZoIiwidGV4dEFsaWduIjoiY2VudGVyIiwiZGlzcGxheSI6ImZsZXgiLCJmbGV4RGlyZWN0aW9uIjoiY29sdW1uIiwiYWxpZ25JdGVtcyI6ImNlbnRlciIsImp1c3RpZnlDb250ZW50IjoiY2VudGVyIn0sImNoaWxkcmVuIjpbIiQiLCJkaXYiLG51bGwseyJjaGlsZHJlbiI6W1siJCIsInN0eWxlIixudWxsLHsiZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUwiOnsiX19odG1sIjoiYm9keXtjb2xvcjojMDAwO2JhY2tncm91bmQ6I2ZmZjttYXJnaW46MH0ubmV4dC1lcnJvci1oMXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMCwwLDAsLjMpfUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6ZGFyayl7Ym9keXtjb2xvcjojZmZmO2JhY2tncm91bmQ6IzAwMH0ubmV4dC1lcnJvci1oMXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMjU1LDI1NSwyNTUsLjMpfX0ifX1dLFsiJCIsImgxIixudWxsLHsiY2xhc3NOYW1lIjoibmV4dC1lcnJvci1oMSIsInN0eWxlIjp7ImRpc3BsYXkiOiJpbmxpbmUtYmxvY2siLCJtYXJnaW4iOiIwIDIwcHggMCAwIiwicGFkZGluZyI6IjAgMjNweCAwIDAiLCJmb250U2l6ZSI6MjQsImZvbnRXZWlnaHQiOjUwMCwidmVydGljYWxBbGlnbiI6InRvcCIsImxpbmVIZWlnaHQiOiI0OXB4In0sImNoaWxkcmVuIjo0MDR9XSxbIiQiLCJkaXYiLG51bGwseyJzdHlsZSI6eyJkaXNwbGF5IjoiaW5saW5lLWJsb2NrIn0sImNoaWxkcmVuIjpbIiQiLCJoMiIsbnVsbCx7InN0eWxlIjp7ImZvbnRTaXplIjoxNCwiZm9udFdlaWdodCI6NDAwLCJsaW5lSGVpZ2h0IjoiNDlweCIsIm1hcmdpbiI6MH0sImNoaWxkcmVuIjoiVGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZC4ifV19XV19XX1dXSxbXV0sImZvcmJpZGRlbiI6IiR1bmRlZmluZWQiLCJ1bmF1dGhvcml6ZWQiOiIkdW5kZWZpbmVkIn1dfSwicGFyYW1zIjp7fSwicHJvbWlzZSI6IiRANiJ9XV19XSx7ImNoaWxkcmVuIjpbIl9fUEFHRV9fIixbIiQiLCIkMSIsImMiLHsiY2hpbGRyZW4iOltbIiQiLCIkTDciLG51bGwseyJDb21wb25lbnQiOiIkOCIsInNlYXJjaFBhcmFtcyI6e30sInBhcmFtcyI6IiQwOmY6MDoxOjE6cHJvcHM6Y2hpbGRyZW46MTpwcm9wczpwYXJhbXMiLCJwcm9taXNlcyI6WyIkQDkiLCIkQGEiXX1dLCIkdW5kZWZpbmVkIixbWyIkIiwibGluayIsIjAiLHsicmVsIjoic3R5bGVzaGVldCIsImhyZWYiOiIvX25leHQvc3RhdGljL2Nzcy82MGIyZTEwZDBjNTFkYzMwLmNzcyIsInByZWNlZGVuY2UiOiJuZXh0IiwiY3Jvc3NPcmlnaW4iOiIkdW5kZWZpbmVkIiwibm9uY2UiOiIkdW5kZWZpbmVkIn1dXSxbIiQiLCIkTGIiLG51bGwseyJjaGlsZHJlbiI6WyIkTGMiLCIkTGQiLG51bGxdfV1dfV0se30sbnVsbCxmYWxzZV19LG51bGwsZmFsc2VdLFsiJCIsIiQxIiwiaCIseyJjaGlsZHJlbiI6W251bGwsWyIkIiwiJDEiLCJhUkVMM25od1A0MVBSM3hmTkNqSk8iLHsiY2hpbGRyZW4iOltbIiQiLCIkTGUiLG51bGwseyJjaGlsZHJlbiI6IiRMZiJ9XSxbIiQiLCJtZXRhIixudWxsLHsibmFtZSI6Im5leHQtc2l6ZS1hZGp1c3QiLCJjb250ZW50IjoiIn1dXX1dLFsiJCIsIiRMMTAiLG51bGwseyJjaGlsZHJlbiI6IiRMMTEifV1dfV0sZmFsc2VdXSwibSI6IiR1bmRlZmluZWQiLCJHIjpbIiQxMiIsIiR1bmRlZmluZWQiXSwicyI6ZmFsc2UsIlMiOnRydWV9CjY6e30KOTp7fQphOnt9CmY6W1siJCIsIm1ldGEiLCIwIix7ImNoYXJTZXQiOiJ1dGYtOCJ9XSxbIiQiLCJtZXRhIiwiMSIseyJuYW1lIjoidmlld3BvcnQiLCJjb250ZW50Ijoid2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEifV1dCmM6bnVsbApkOm51bGwKMTE6W1siJCIsImxpbmsiLCIwIix7InJlbCI6Im1hbmlmZXN0IiwiaHJlZiI6Ii9tYW5pZmVzdC5qc29uIiwiY3Jvc3NPcmlnaW4iOiIkdW5kZWZpbmVkIn1dLFsiJCIsImxpbmsiLCIxIix7InJlbCI6Imljb24iLCJocmVmIjoiL2Zhdmljb24uaWNvIiwidHlwZSI6ImltYWdlL3gtaWNvbiIsInNpemVzIjoiNDh4NDgifV0sWyIkIiwibGluayIsIjIiLHsicmVsIjoiaWNvbiIsImhyZWYiOiIvaWNvbi5wbmc/ZjQ1MjFhNTlkZjBkOGYwYiIsInR5cGUiOiJpbWFnZS9wbmciLCJzaXplcyI6Ijk2eDk2In1dLFsiJCIsImxpbmsiLCIzIix7InJlbCI6ImFwcGxlLXRvdWNoLWljb24iLCJocmVmIjoiL2FwcGxlLWljb24ucG5nP2U0N2M0MmQ5OWYyZDI0NGYiLCJ0eXBlIjoiaW1hZ2UvcG5nIiwic2l6ZXMiOiIxODB4MTgwIn1dXQo=", // 8 | 5720, // 9 | "text/plain", // 10 | "/index.txt/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dash 2 | https://github.com/user-attachments/assets/96f3f4fb-1f53-4113-b1d5-d55934c865e0 3 | 4 | ## Getting started 5 | 6 | 7 | 8 | 9 | ### Installation 10 | 11 | ```sql 12 | FROM community INSTALL dash; 13 | LOAD dash; 14 | PRAGMA dash; 15 | ``` 16 | 17 | ### Usage 18 | 19 | ```sql 20 | -- Starts the http server 21 | CALL start_dash('127.0.0.1', 4200) 22 | 23 | -- Enable cors (false by default) 24 | CALL start_dash('127.0.0.1', 4200, enable_cors=true); 25 | 26 | -- Require authentication (off by default) 27 | CALL start_dash('127.0.0.1', 4200, api_key='abc123'); 28 | 29 | -- Proxy the web UI from a different location 30 | CALL start_dash('127.0.0.1', 4200, ui_proxy='https://gropaul.github.io/dash-ui/'); 31 | ``` 32 | 33 | Once the _dash_ is running, access the WebUI by opening http://127.0.0.1:4200 in your browser. 34 | 35 | ## API Endpoints 36 | 37 | | Endpoint | Method | Description | Parameters | 38 | |----------|--------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 39 | | `/` | GET | View the integrated web UI. | | 40 | | `/query` | POST | Execute an SQL query. | **Header:**
- `X-Api-Key` (optional) – API key for authentication.

**Body (JSON):**
- `query` (string, required) – The SQL query to be executed.
- `format` (string, required) – Response format (`compact_json` | `json`). | 41 | | `/ping` | GET | Check if the server is running. | | 42 | 43 | For detailed schema definitions refer to the [API documentation](openapi.yaml). 44 | This also contains instructions on querying and uploading files using the HTTP API. For a reference client 45 | implementation have a look at the [Python test client](./e2e_tests/client.py). 46 | 47 | ## Serialization pragmas 48 | 49 | The extension provides a set of pragmas to control the serialization of queries. These **SHOULD** be used independently 50 | of the web UI or the HTTP API and using them in a statement sent to the HTTP API is not advised, as the HTTP API already 51 | serializes the results using the same format. 52 | 53 | ```sql 54 | -- Default format is JSON 55 | PRAGMA AS_JSON('SELECT {''key1'': range} as map, range FROM range(2)'); 56 | 57 | PRAGMA AS_JSON('SELECT {''key1'': range} as map, range FROM range(2)', format='JSON'); 58 | -- Output: 59 | -- [ 60 | -- {"map":{"key1":0},"range":0}, 61 | -- {"map":{"key1":1},"range":1} 62 | -- ] 63 | 64 | PRAGMA AS_JSON('SELECT {''key1'': range} as map, range FROM range(2)', format='COMPACT_JSON'); 65 | -- Output: 66 | -- { 67 | -- "meta": [ 68 | -- {"name": "map","type": "STRUCT(key1 BIGINT)"}, 69 | -- {"name": "range","type": "BIGINT"} 70 | -- ], 71 | -- "data": [ 72 | -- [{"key1": 0},0], 73 | -- [{"key1": 1},1] 74 | -- ], 75 | -- "statistics": { 76 | -- "rows": 2 77 | -- } 78 | -- } 79 | ``` 80 | 81 | ## Development 82 | 83 | ### Setting up the Repository 84 | 85 | Clone the repository and all its submodules 86 | 87 | ```bash 88 | git clone 89 | git submodule update --init --recursive 90 | ``` 91 | 92 | **Build the UI:** Change into the _dash-ui_ directory and build the UI 93 | 94 | ```bash 95 | cd dash-ui 96 | pnpm install --frozen-lockfile 97 | NEXT_PUBLIC_API_URL="" pnpm run build 98 | ``` 99 | 100 | **Generate source files:** Change back to the root directory and generate the source files containing the UI 101 | 102 | ```bash 103 | python3 scripts/gen_ui_files.py 104 | ``` 105 | 106 | ### Setting up CLion 107 | 108 | **Opening project:** 109 | Configuring CLion with the extension template requires a little work. Firstly, make sure that the DuckDB submodule is 110 | available. 111 | Then make sure to open `./duckdb/CMakeLists.txt` (so not the top level `CMakeLists.txt` file from this repo) as a 112 | project in CLion. 113 | Now to fix your project path go to 114 | `tools->CMake->Change Project Root`([docs](https://www.jetbrains.com/help/clion/change-project-root-directory.html)) to 115 | set the project root to the root dir of this repo. 116 | 117 | **Debugging:** 118 | To set up debugging in CLion, there are two simple steps required. Firstly, in 119 | `CLion -> Settings / Preferences -> Build, Execution, Deploy -> CMake` you will need to add the desired builds (e.g. 120 | Debug, Release, RelDebug, etc). There's different ways to configure this, but the easiest is to leave all empty, except 121 | the `build path`, which needs to be set to `../build/{build type}`. Now on a clean repository you will first need to run 122 | `make {build type}` to initialize the CMake build directory. After running make, you will be able to (re)build from 123 | CLion by using the build target we just created. If you use the CLion editor, you can create a CLion CMake profiles 124 | matching the CMake variables that are described in the makefile, and then you don't need to invoke the Makefile. 125 | 126 | The second step is to configure the unittest runner as a run/debug configuration. To do this, go to 127 | `Run -> Edit Configurations` and click `+ -> Cmake Application`. The target and executable should be `unittest`. This 128 | will run all the DuckDB tests. To specify only running the extension specific tests, add `--test-dir ../../.. [sql]` to 129 | the `Program Arguments`. Note that it is recommended to use the `unittest` executable for testing/development within 130 | CLion. The actual DuckDB CLI currently does not reliably work as a run target in CLion. 131 | 132 | ### Testing 133 | 134 | To run the E2E test install all packages necessary: 135 | 136 | ```bash 137 | pip install -r requirements.txt 138 | ``` 139 | 140 | Then run the test suite: 141 | 142 | ```bash 143 | pytest e2e_tests 144 | ``` 145 | -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_webpack_9a68f0196a060eb0_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_WEBPACK_9A68F0196A060EB0_JS = { 6 | // Content 7 | "KCgpPT57InVzZSBzdHJpY3QiO3ZhciBlPXt9LHQ9e307ZnVuY3Rpb24gcihuKXt2YXIgbz10W25dO2lmKHZvaWQgMCE9PW8pcmV0dXJuIG8uZXhwb3J0czt2YXIgYT10W25dPXtpZDpuLGxvYWRlZDohMSxleHBvcnRzOnt9fSxpPSEwO3RyeXtlW25dLmNhbGwoYS5leHBvcnRzLGEsYS5leHBvcnRzLHIpLGk9ITF9ZmluYWxseXtpJiZkZWxldGUgdFtuXX1yZXR1cm4gYS5sb2FkZWQ9ITAsYS5leHBvcnRzfXIubT1lLCgoKT0+e3ZhciBlPVtdO3IuTz0odCxuLG8sYSk9PntpZihuKXthPWF8fDA7Zm9yKHZhciBpPWUubGVuZ3RoO2k+MCYmZVtpLTFdWzJdPmE7aS0tKWVbaV09ZVtpLTFdO2VbaV09W24sbyxhXTtyZXR1cm59Zm9yKHZhciBsPTEvMCxpPTA7aTxlLmxlbmd0aDtpKyspe2Zvcih2YXJbbixvLGFdPWVbaV0sZD0hMCx1PTA7dTxuLmxlbmd0aDt1KyspKCExJmF8fGw+PWEpJiZPYmplY3Qua2V5cyhyLk8pLmV2ZXJ5KGU9PnIuT1tlXShuW3VdKSk/bi5zcGxpY2UodS0tLDEpOihkPSExLGE8bCYmKGw9YSkpO2lmKGQpe2Uuc3BsaWNlKGktLSwxKTt2YXIgcz1vKCk7dm9pZCAwIT09cyYmKHQ9cyl9fXJldHVybiB0fX0pKCksci5uPWU9Pnt2YXIgdD1lJiZlLl9fZXNNb2R1bGU/KCk9PmUuZGVmYXVsdDooKT0+ZTtyZXR1cm4gci5kKHQse2E6dH0pLHR9LCgoKT0+e3ZhciBlLHQ9T2JqZWN0LmdldFByb3RvdHlwZU9mP2U9Pk9iamVjdC5nZXRQcm90b3R5cGVPZihlKTplPT5lLl9fcHJvdG9fXztyLnQ9ZnVuY3Rpb24obixvKXtpZigxJm8mJihuPXRoaXMobikpLDgmb3x8Im9iamVjdCI9PXR5cGVvZiBuJiZuJiYoNCZvJiZuLl9fZXNNb2R1bGV8fDE2Jm8mJiJmdW5jdGlvbiI9PXR5cGVvZiBuLnRoZW4pKXJldHVybiBuO3ZhciBhPU9iamVjdC5jcmVhdGUobnVsbCk7ci5yKGEpO3ZhciBpPXt9O2U9ZXx8W251bGwsdCh7fSksdChbXSksdCh0KV07Zm9yKHZhciBsPTImbyYmbjsib2JqZWN0Ij09dHlwZW9mIGwmJiF+ZS5pbmRleE9mKGwpO2w9dChsKSlPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhsKS5mb3JFYWNoKGU9PmlbZV09KCk9Pm5bZV0pO3JldHVybiBpLmRlZmF1bHQ9KCk9Pm4sci5kKGEsaSksYX19KSgpLHIuZD0oZSx0KT0+e2Zvcih2YXIgbiBpbiB0KXIubyh0LG4pJiYhci5vKGUsbikmJk9iamVjdC5kZWZpbmVQcm9wZXJ0eShlLG4se2VudW1lcmFibGU6ITAsZ2V0OnRbbl19KX0sci5mPXt9LHIuZT1lPT5Qcm9taXNlLmFsbChPYmplY3Qua2V5cyhyLmYpLnJlZHVjZSgodCxuKT0+KHIuZltuXShlLHQpLHQpLFtdKSksci51PWU9PiJzdGF0aWMvY2h1bmtzLyIrKDU5MT09PWU/ImRjMTBlYWRmIjplKSsiLiIrKHsxMjI6IjNlYzllMTI2MmJkMTRlMjMiLDE5OToiYmM3ZjY0MWFjMjlhZTFmNCIsNTkxOiIxNDAzNDFkNGJiZTQ5YWMwIiw5MDI6IjAyYTRlM2U3ZGQ2ZGUwNzgifSlbZV0rIi5qcyIsci5taW5pQ3NzRj1lPT4ic3RhdGljL2Nzcy81YjU0MzMxMTI5YzkxMTRjLmNzcyIsci5nPWZ1bmN0aW9uKCl7aWYoIm9iamVjdCI9PXR5cGVvZiBnbG9iYWxUaGlzKXJldHVybiBnbG9iYWxUaGlzO3RyeXtyZXR1cm4gdGhpc3x8RnVuY3Rpb24oInJldHVybiB0aGlzIikoKX1jYXRjaChlKXtpZigib2JqZWN0Ij09dHlwZW9mIHdpbmRvdylyZXR1cm4gd2luZG93fX0oKSxyLm89KGUsdCk9Pk9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChlLHQpLCgoKT0+e3ZhciBlPXt9LHQ9Il9OX0U6IjtyLmw9KG4sbyxhLGkpPT57aWYoZVtuXSl7ZVtuXS5wdXNoKG8pO3JldHVybn1pZih2b2lkIDAhPT1hKWZvcih2YXIgbCxkLHU9ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoInNjcmlwdCIpLHM9MDtzPHUubGVuZ3RoO3MrKyl7dmFyIGM9dVtzXTtpZihjLmdldEF0dHJpYnV0ZSgic3JjIik9PW58fGMuZ2V0QXR0cmlidXRlKCJkYXRhLXdlYnBhY2siKT09dCthKXtsPWM7YnJlYWt9fWx8fChkPSEwLChsPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInNjcmlwdCIpKS5jaGFyc2V0PSJ1dGYtOCIsbC50aW1lb3V0PTEyMCxyLm5jJiZsLnNldEF0dHJpYnV0ZSgibm9uY2UiLHIubmMpLGwuc2V0QXR0cmlidXRlKCJkYXRhLXdlYnBhY2siLHQrYSksbC5zcmM9ci50dShuKSksZVtuXT1bb107dmFyIGY9KHQscik9PntsLm9uZXJyb3I9bC5vbmxvYWQ9bnVsbCxjbGVhclRpbWVvdXQocCk7dmFyIG89ZVtuXTtpZihkZWxldGUgZVtuXSxsLnBhcmVudE5vZGUmJmwucGFyZW50Tm9kZS5yZW1vdmVDaGlsZChsKSxvJiZvLmZvckVhY2goZT0+ZShyKSksdClyZXR1cm4gdChyKX0scD1zZXRUaW1lb3V0KGYuYmluZChudWxsLHZvaWQgMCx7dHlwZToidGltZW91dCIsdGFyZ2V0Omx9KSwxMmU0KTtsLm9uZXJyb3I9Zi5iaW5kKG51bGwsbC5vbmVycm9yKSxsLm9ubG9hZD1mLmJpbmQobnVsbCxsLm9ubG9hZCksZCYmZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChsKX19KSgpLHIucj1lPT57InVuZGVmaW5lZCIhPXR5cGVvZiBTeW1ib2wmJlN5bWJvbC50b1N0cmluZ1RhZyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KGUsU3ltYm9sLnRvU3RyaW5nVGFnLHt2YWx1ZToiTW9kdWxlIn0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShlLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KX0sci5ubWQ9ZT0+KGUucGF0aHM9W10sZS5jaGlsZHJlbnx8KGUuY2hpbGRyZW49W10pLGUpLCgoKT0+e3ZhciBlO3IudHQ9KCk9Pih2b2lkIDA9PT1lJiYoZT17Y3JlYXRlU2NyaXB0VVJMOmU9PmV9LCJ1bmRlZmluZWQiIT10eXBlb2YgdHJ1c3RlZFR5cGVzJiZ0cnVzdGVkVHlwZXMuY3JlYXRlUG9saWN5JiYoZT10cnVzdGVkVHlwZXMuY3JlYXRlUG9saWN5KCJuZXh0anMjYnVuZGxlciIsZSkpKSxlKX0pKCksci50dT1lPT5yLnR0KCkuY3JlYXRlU2NyaXB0VVJMKGUpLHIucD0iL19uZXh0LyIsKCgpPT57dmFyIGU9KGUsdCxyLG4pPT57dmFyIG89ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgibGluayIpO3JldHVybiBvLnJlbD0ic3R5bGVzaGVldCIsby50eXBlPSJ0ZXh0L2NzcyIsby5vbmVycm9yPW8ub25sb2FkPWE9PntpZihvLm9uZXJyb3I9by5vbmxvYWQ9bnVsbCwibG9hZCI9PT1hLnR5cGUpcigpO2Vsc2V7dmFyIGk9YSYmKCJsb2FkIj09PWEudHlwZT8ibWlzc2luZyI6YS50eXBlKSxsPWEmJmEudGFyZ2V0JiZhLnRhcmdldC5ocmVmfHx0LGQ9RXJyb3IoIkxvYWRpbmcgQ1NTIGNodW5rICIrZSsiIGZhaWxlZC5cbigiK2wrIikiKTtkLmNvZGU9IkNTU19DSFVOS19MT0FEX0ZBSUxFRCIsZC50eXBlPWksZC5yZXF1ZXN0PWwsby5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKG8pLG4oZCl9fSxvLmhyZWY9dCxmdW5jdGlvbihlKXtpZigiZnVuY3Rpb24iPT10eXBlb2YgX05fRV9TVFlMRV9MT0FEKXtsZXR7aHJlZjp0LG9ubG9hZDpyLG9uZXJyb3I6bn09ZTtfTl9FX1NUWUxFX0xPQUQoMD09PXQuaW5kZXhPZih3aW5kb3cubG9jYXRpb24ub3JpZ2luKT9uZXcgVVJMKHQpLnBhdGhuYW1lOnQpLnRoZW4oKCk9Pm51bGw9PXI/dm9pZCAwOnIuY2FsbChlLHt0eXBlOiJsb2FkIn0pLCgpPT5udWxsPT1uP3ZvaWQgMDpuLmNhbGwoZSx7fSkpfWVsc2UgZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChlKX0obyksb30sdD0oZSx0KT0+e2Zvcih2YXIgcj1kb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgibGluayIpLG49MDtuPHIubGVuZ3RoO24rKyl7dmFyIG89cltuXSxhPW8uZ2V0QXR0cmlidXRlKCJkYXRhLWhyZWYiKXx8by5nZXRBdHRyaWJ1dGUoImhyZWYiKTtpZigic3R5bGVzaGVldCI9PT1vLnJlbCYmKGE9PT1lfHxhPT09dCkpcmV0dXJuIG99Zm9yKHZhciBpPWRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJzdHlsZSIpLG49MDtuPGkubGVuZ3RoO24rKyl7dmFyIG89aVtuXSxhPW8uZ2V0QXR0cmlidXRlKCJkYXRhLWhyZWYiKTtpZihhPT09ZXx8YT09PXQpcmV0dXJuIG99fSxuPW49Pm5ldyBQcm9taXNlKChvLGEpPT57dmFyIGk9ci5taW5pQ3NzRihuKSxsPXIucCtpO2lmKHQoaSxsKSlyZXR1cm4gbygpO2UobixsLG8sYSl9KSxvPXs2ODowfTtyLmYubWluaUNzcz0oZSx0KT0+e29bZV0/dC5wdXNoKG9bZV0pOjAhPT1vW2VdJiYoezMwMjoxfSlbZV0mJnQucHVzaChvW2VdPW4oZSkudGhlbigoKT0+e29bZV09MH0sdD0+e3Rocm93IGRlbGV0ZSBvW2VdLHR9KSl9fSkoKSwoKCk9Pnt2YXIgZT17Njg6MCwxOTE6MCw3MTc6MH07ci5mLmo9KHQsbik9Pnt2YXIgbz1yLm8oZSx0KT9lW3RdOnZvaWQgMDtpZigwIT09byl7aWYobyluLnB1c2gob1syXSk7ZWxzZSBpZigvXigxOTF8MzAyfDY4fDcxNykkLy50ZXN0KHQpKWVbdF09MDtlbHNle3ZhciBhPW5ldyBQcm9taXNlKChyLG4pPT5vPWVbdF09W3Isbl0pO24ucHVzaChvWzJdPWEpO3ZhciBpPXIucCtyLnUodCksbD1FcnJvcigpO3IubChpLG49PntpZihyLm8oZSx0KSYmKDAhPT0obz1lW3RdKSYmKGVbdF09dm9pZCAwKSxvKSl7dmFyIGE9biYmKCJsb2FkIj09PW4udHlwZT8ibWlzc2luZyI6bi50eXBlKSxpPW4mJm4udGFyZ2V0JiZuLnRhcmdldC5zcmM7bC5tZXNzYWdlPSJMb2FkaW5nIGNodW5rICIrdCsiIGZhaWxlZC5cbigiK2ErIjogIitpKyIpIixsLm5hbWU9IkNodW5rTG9hZEVycm9yIixsLnR5cGU9YSxsLnJlcXVlc3Q9aSxvWzFdKGwpfX0sImNodW5rLSIrdCx0KX19fSxyLk8uaj10PT4wPT09ZVt0XTt2YXIgdD0odCxuKT0+e3ZhciBvLGEsW2ksbCxkXT1uLHU9MDtpZihpLnNvbWUodD0+MCE9PWVbdF0pKXtmb3IobyBpbiBsKXIubyhsLG8pJiYoci5tW29dPWxbb10pO2lmKGQpdmFyIHM9ZChyKX1mb3IodCYmdChuKTt1PGkubGVuZ3RoO3UrKylhPWlbdV0sci5vKGUsYSkmJmVbYV0mJmVbYV1bMF0oKSxlW2FdPTA7cmV0dXJuIHIuTyhzKX0sbj1zZWxmLndlYnBhY2tDaHVua19OX0U9c2VsZi53ZWJwYWNrQ2h1bmtfTl9FfHxbXTtuLmZvckVhY2godC5iaW5kKG51bGwsMCkpLG4ucHVzaD10LmJpbmQobnVsbCxuLnB1c2guYmluZChuKSl9KSgpLHIubmM9dm9pZCAwfSkoKTs=", // 8 | 6588, // 9 | "application/javascript", // 10 | "/_next/static/chunks/webpack-9a68f0196a060eb0.js/", // 11 | 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/include/execution_request.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "auto_cleaner.hpp" 4 | #include "duckdb/main/client_data.hpp" 5 | #include "duckdb/main/connection.hpp" 6 | #include "response_format.hpp" 7 | #include "result.hpp" 8 | #include "temp_file.hpp" 9 | #include "yyjson.hpp" 10 | 11 | #define CPPHTTPLIB_OPENSSL_SUPPORT 12 | #include "httplib.hpp" 13 | #include "string_util.hpp" 14 | #include "utils.hpp" 15 | #include "fmt/format.h" 16 | 17 | namespace duckdb { 18 | using namespace duckdb_httplib_openssl; // NOLINT(*-build-using-namespace) 19 | using namespace duckdb_yyjson; // NOLINT(*-build-using-namespace) 20 | 21 | struct ExecutionRequest { 22 | const std::string query {}; 23 | const ResponseFormat format = ResponseFormat::INVALID; 24 | const MultipartFormDataMap &files; 25 | 26 | ExecutionRequest(const std::string &query, const ResponseFormat format, const MultipartFormDataMap &files) 27 | : query(query), format(format), files(files) { 28 | } 29 | 30 | Result Execute(const shared_ptr &db, Response &res) const { 31 | D_ASSERT(db); 32 | D_ASSERT(format != ResponseFormat::INVALID); 33 | 34 | Connection conn(*db); 35 | std::vector> temp_files; 36 | // Create a temporary table for each file by creating the files in the temporary directory, then creating a 37 | // temporary table for each file 38 | for (const auto &file : files) { 39 | const auto &file_name = !file.second.filename.empty() ? file.second.filename : file.second.name; 40 | if (file_name == "query.json") { 41 | continue; 42 | } 43 | const auto &file_data = file.second.content; 44 | temp_files.push_back(make_uniq(file_name, file_data)); 45 | } 46 | 47 | for (const auto &file : temp_files) { 48 | auto create_file_query = "CREATE TEMP TABLE " + KeywordHelper::WriteQuoted(file->GetName()) + " AS FROM " + 49 | KeywordHelper::WriteQuoted(file->GetPath()); 50 | auto result = conn.Query(create_file_query); 51 | if (result->HasError()) { 52 | return {InternalServerError_500, result->GetErrorObject()}; 53 | } 54 | } 55 | const string escaped_query = EscapeQutes(query); 56 | 57 | const std::string query_template = R"( 58 | WITH data AS MATERIALIZED ( 59 | FROM query_result('{}') 60 | ), 61 | dash_row_number_ids AS ( 62 | SELECT range as dash_row_number_id 63 | FROM range((SELECT COUNT(*) FROM data)) 64 | ), 65 | json_data AS ( 66 | SELECT dash_row_number_ids.dash_row_number_id, to_json(COLUMNS(c -> c != 'dash_row_number_id')) 67 | FROM data 68 | POSITIONAL JOIN dash_row_number_ids 69 | ), 70 | json_list AS MATERIALIZED ( 71 | SELECT ifnull(list([*COLUMNS(c -> c != 'dash_row_number_id')] ORDER BY dash_row_number_id), []) as data 72 | FROM json_data 73 | ), 74 | types_data AS (SELECT ANY_VALUE(typeof(COLUMNS(*))) FROM data), 75 | types_list_data AS (SELECT [(*COLUMNS(*))] as types_with_null, list_filter(types_with_null, x -> x is not null) as types FROM types_data), 76 | names_data AS (SELECT ANY_VALUE(alias(COLUMNS(*))) FROM data), 77 | names_list_data AS (SELECT [(*COLUMNS(*))] as names_with_null, list_filter(names_with_null, x -> x is not null) as names FROM names_data), 78 | 79 | combined_data AS ( 80 | SELECT data as rows, list_transform(list_zip(types, names), x -> {{type: x[1], name: x[2]}}) as columns, names 81 | FROM json_list 82 | POSITIONAL JOIN types_list_data 83 | POSITIONAL JOIN names_list_data 84 | ) 85 | SELECT json_object('rows', rows, 'columns', columns, 'stats', {{ rows: len(rows) }}), names 86 | FROM combined_data 87 | 88 | )"; 89 | const string json_query = duckdb_fmt::format(query_template, escaped_query); 90 | 91 | auto result = conn.Query(json_query); 92 | if (result->HasError()) { 93 | return {BadRequest_400, result->GetErrorObject()}; 94 | } 95 | 96 | const auto json = result->GetValue(0,0); 97 | res.set_content(json.ToString(), "application/json"); 98 | 99 | return nullptr; 100 | } 101 | 102 | static Result FromRequest(const Request &req, const std::string &api_key) { 103 | RETURN_IF_ERROR(HasCorrectApiKey(api_key, req)); 104 | 105 | auto body = GetRequestBody(req); 106 | RETURN_IF_ERROR(body); 107 | 108 | return ParseQuery(body->first, body->second); 109 | } 110 | 111 | private: 112 | 113 | static Result> GetRequestBody(const Request &req) { 114 | if (req.is_multipart_form_data()) { 115 | if (!req.has_file("query.json")) { 116 | return HttpErrorData {BadRequest_400, "Missing 'query.json' file"}; 117 | } 118 | 119 | // Make sure that the files does not have multiple values 120 | for (auto it = req.files.begin(); it != req.files.end();) { 121 | auto count = req.files.count(it->first); 122 | if (count > 1) { 123 | return HttpErrorData {BadRequest_400, "Multiple files with name: " + it->first}; 124 | } 125 | std::advance(it, count); 126 | } 127 | 128 | return std::make_pair(req.get_file_value("query.json").content, std::ref(req.files)); 129 | } else { 130 | return std::make_pair(req.body, std::ref(req.files)); 131 | } 132 | } 133 | 134 | static Result ParseQuery(const std::string &request_str, const MultipartFormDataMap &files) { 135 | constexpr yyjson_read_flag flags = YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_INF_AND_NAN; 136 | yyjson_doc *doc = yyjson_read(request_str.c_str(), request_str.size(), flags); 137 | if (!doc) { 138 | return HttpErrorData {BadRequest_400, "Could not parse JSON body"}; 139 | } 140 | 141 | yyjson_val *obj = yyjson_doc_get_root(doc); 142 | AutoCleaner cleaner([&] { yyjson_doc_free(doc); }); 143 | 144 | if (!obj || yyjson_get_type(obj) != YYJSON_TYPE_OBJ) { 145 | return HttpErrorData {BadRequest_400, "Expected JSON object as root"}; 146 | } 147 | 148 | yyjson_val *query_obj = yyjson_obj_get(obj, "query"); 149 | if (!query_obj || yyjson_get_type(query_obj) != YYJSON_TYPE_STR) { 150 | return HttpErrorData {BadRequest_400, "Expected 'query' field as string"}; 151 | } 152 | 153 | std::string query = yyjson_get_str(query_obj); 154 | if (query.empty()) { 155 | return HttpErrorData {BadRequest_400, "Query is empty"}; 156 | } 157 | 158 | yyjson_val *format_obj = yyjson_obj_get(obj, "format"); 159 | if (!format_obj || yyjson_get_type(format_obj) != YYJSON_TYPE_STR) { 160 | return HttpErrorData {BadRequest_400, "Expected 'format' field as string"}; 161 | } 162 | 163 | auto format = ResponseFormat::INVALID; 164 | const std::string format_str = yyjson_get_str(format_obj); 165 | try { 166 | format = string_util::FromString(format_str); 167 | } catch (const std::exception &ex) { 168 | return HttpErrorData {BadRequest_400, ex.what()}; 169 | } 170 | 171 | return ExecutionRequest(query, format, files); 172 | } 173 | }; 174 | 175 | } // namespace duckdb 176 | -------------------------------------------------------------------------------- /e2e_tests/responses/all_types_json.py: -------------------------------------------------------------------------------- 1 | ALL_TYPES_JSON = [ 2 | { 3 | "bool": False, 4 | "tinyint": -128, 5 | "smallint": -32768, 6 | "int": -2147483648, 7 | "bigint": -9223372036854775808, 8 | "hugeint": "-170141183460469231731687303715884105728", 9 | "uhugeint": "0", 10 | "utinyint": 0, 11 | "usmallint": 0, 12 | "uint": 0, 13 | "ubigint": 0, 14 | "varint": "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", 15 | "date": "5877642-06-25 (BC)", 16 | "time": "00:00:00", 17 | "timestamp": "290309-12-22 (BC) 00:00:00", 18 | "timestamp_s": "290309-12-22 (BC) 00:00:00", 19 | "timestamp_ms": "290309-12-22 (BC) 00:00:00", 20 | "timestamp_ns": "1677-09-22 00:00:00", 21 | "time_tz": "00:00:00+15:59:59", 22 | "timestamp_tz": "290309-12-22 (BC) 00:00:00+00", 23 | "float": -3.4028234663852886e38, 24 | "double": -1.7976931348623157e308, 25 | "dec_4_1": -999.9, 26 | "dec_9_4": -99999.9999, 27 | "dec_18_6": -1000000000000.0, 28 | "dec38_10": -1e28, 29 | "uuid": "00000000-0000-0000-0000-000000000000", 30 | "interval": "00:00:00", 31 | "varchar": "🦆🦆🦆🦆🦆🦆", 32 | "blob": "thisisalongblob\\x00withnullbytes", 33 | "bit": "0010001001011100010101011010111", 34 | "small_enum": "DUCK_DUCK_ENUM", 35 | "medium_enum": "enum_0", 36 | "large_enum": "enum_0", 37 | "int_array": [], 38 | "double_array": [], 39 | "date_array": [], 40 | "timestamp_array": [], 41 | "timestamptz_array": [], 42 | "varchar_array": [], 43 | "nested_int_array": [], 44 | "struct": {"a": None, "b": None}, 45 | "struct_of_arrays": {"a": None, "b": None}, 46 | "array_of_structs": [], 47 | "map": {}, 48 | "union": "Frank", 49 | "fixed_int_array": [None, 2, 3], 50 | "fixed_varchar_array": ["a", None, "c"], 51 | "fixed_nested_int_array": [[None, 2, 3], None, [None, 2, 3]], 52 | "fixed_nested_varchar_array": [["a", None, "c"], None, ["a", None, "c"]], 53 | "fixed_struct_array": [{"a": None, "b": None}, {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, {"a": None, "b": None}], 54 | "struct_of_fixed_array": {"a": [None, 2, 3], "b": ["a", None, "c"]}, 55 | "fixed_array_of_int_list": [[], [42, 999, None, None, -42], []], 56 | "list_of_fixed_int_array": [[None, 2, 3], [4, 5, 6], [None, 2, 3]], 57 | }, 58 | { 59 | "bool": True, 60 | "tinyint": 127, 61 | "smallint": 32767, 62 | "int": 2147483647, 63 | "bigint": 9223372036854775807, 64 | "hugeint": "170141183460469231731687303715884105727", 65 | "uhugeint": "340282366920938463463374607431768211455", 66 | "utinyint": 255, 67 | "usmallint": 65535, 68 | "uint": 4294967295, 69 | "ubigint": 18446744073709551615, 70 | "varint": "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", 71 | "date": "5881580-07-10", 72 | "time": "24:00:00", 73 | "timestamp": "294247-01-10 04:00:54.775806", 74 | "timestamp_s": "294247-01-10 04:00:54", 75 | "timestamp_ms": "294247-01-10 04:00:54.775", 76 | "timestamp_ns": "2262-04-11 23:47:16.854775806", 77 | "time_tz": "24:00:00-15:59:59", 78 | "timestamp_tz": "294247-01-10 04:00:54.775806+00", 79 | "float": 3.4028234663852886e38, 80 | "double": 1.7976931348623157e308, 81 | "dec_4_1": 999.9, 82 | "dec_9_4": 99999.9999, 83 | "dec_18_6": 1000000000000.0, 84 | "dec38_10": 1e28, 85 | "uuid": "ffffffff-ffff-ffff-ffff-ffffffffffff", 86 | "interval": "83 years 3 months 999 days 00:16:39.999999", 87 | "varchar": "goo", 88 | "blob": "\\x00\\x00\\x00a", 89 | "bit": "10101", 90 | "small_enum": "GOOSE", 91 | "medium_enum": "enum_299", 92 | "large_enum": "enum_69999", 93 | "int_array": [42, 999, None, None, -42], 94 | "double_array": [42.0, "nan", "inf", "-inf", None, -42.0], 95 | "date_array": ["1970-01-01", "infinity", "-infinity", None, "2022-05-12"], 96 | "timestamp_array": ["1970-01-01 00:00:00", "infinity", "-infinity", None, "2022-05-12 16:23:45"], 97 | "timestamptz_array": ["1970-01-01 00:00:00+00", "infinity", "-infinity", None, "2022-05-12 23:23:45+00"], 98 | "varchar_array": ["🦆🦆🦆🦆🦆🦆", "goose", None, ""], 99 | "nested_int_array": [[], [42, 999, None, None, -42], None, [], [42, 999, None, None, -42]], 100 | "struct": {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, 101 | "struct_of_arrays": {"a": [42, 999, None, None, -42], "b": ["🦆🦆🦆🦆🦆🦆", "goose", None, ""]}, 102 | "array_of_structs": [{"a": None, "b": None}, {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, None], 103 | "map": {"key1": "🦆🦆🦆🦆🦆🦆", "key2": "goose"}, 104 | "union": 5, 105 | "fixed_int_array": [4, 5, 6], 106 | "fixed_varchar_array": ["d", "e", "f"], 107 | "fixed_nested_int_array": [[4, 5, 6], [None, 2, 3], [4, 5, 6]], 108 | "fixed_nested_varchar_array": [["d", "e", "f"], ["a", None, "c"], ["d", "e", "f"]], 109 | "fixed_struct_array": [{"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, {"a": None, "b": None}, {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}], 110 | "struct_of_fixed_array": {"a": [4, 5, 6], "b": ["d", "e", "f"]}, 111 | "fixed_array_of_int_list": [[42, 999, None, None, -42], [], [42, 999, None, None, -42]], 112 | "list_of_fixed_int_array": [[4, 5, 6], [None, 2, 3], [4, 5, 6]], 113 | }, 114 | { 115 | "bool": None, 116 | "tinyint": None, 117 | "smallint": None, 118 | "int": None, 119 | "bigint": None, 120 | "hugeint": None, 121 | "uhugeint": None, 122 | "utinyint": None, 123 | "usmallint": None, 124 | "uint": None, 125 | "ubigint": None, 126 | "varint": None, 127 | "date": None, 128 | "time": None, 129 | "timestamp": None, 130 | "timestamp_s": None, 131 | "timestamp_ms": None, 132 | "timestamp_ns": None, 133 | "time_tz": None, 134 | "timestamp_tz": None, 135 | "float": None, 136 | "double": None, 137 | "dec_4_1": None, 138 | "dec_9_4": None, 139 | "dec_18_6": None, 140 | "dec38_10": None, 141 | "uuid": None, 142 | "interval": None, 143 | "varchar": None, 144 | "blob": None, 145 | "bit": None, 146 | "small_enum": None, 147 | "medium_enum": None, 148 | "large_enum": None, 149 | "int_array": None, 150 | "double_array": None, 151 | "date_array": None, 152 | "timestamp_array": None, 153 | "timestamptz_array": None, 154 | "varchar_array": None, 155 | "nested_int_array": None, 156 | "struct": None, 157 | "struct_of_arrays": None, 158 | "array_of_structs": None, 159 | "map": None, 160 | "union": None, 161 | "fixed_int_array": None, 162 | "fixed_varchar_array": None, 163 | "fixed_nested_int_array": None, 164 | "fixed_nested_varchar_array": None, 165 | "fixed_struct_array": None, 166 | "struct_of_fixed_array": None, 167 | "fixed_array_of_int_list": None, 168 | "list_of_fixed_int_array": None, 169 | }, 170 | ] 171 | -------------------------------------------------------------------------------- /src/gen/file_next_static_media_959fe13ebd2a94e9_s_woff2.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_MEDIA_959FE13EBD2A94E9_S_WOFF2 = { 6 | // Content 7 | "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" 8 | "R+JUzmi8hPB6EUvQeo1H9KWVa4nLyColLqXtGoqKZRU29MtqaWto5uOj39sgwMjYz7Kb2JqZm5RQYYQTsEZwYcbjm8fumtOkG5QpFYUh4uJUiKroCxtLKuyMa2Yrt+0x6B/k3reEsQJ2cMJ0iKZliOF0RJVlRNN0zLdlxuj9fnb9z+fnBAaRi54x/pJ7b+Pnb64fnY40I/Weu/N8q/P3Go419J2yX+2PEOw0dZNuOf2lD+ak2XdV3Wl03R2TGR7T5CWe8qm5F5r08hk0wdEtU8GpoCyciCdRjFIHVRFReKop7WEGWp2soClfDtNQopVs/xXGHHi1oXWTtrrpRNMurDHaLr3rfn45xvW+33Un3wcVlX98PP6KPY+n7ppbYugbZ6O9kOa5Jw80P7e27ukGAWnf4jQtYpg24UwW6xO3XVHWy7D+7YT6IZGEZ/YNGfy/tsin8oqn20yL3tL4aDGJ6znzPKiNFyJjjPGitsbQIDpekmEFSxBFZfgQANGtp31+LqlnnSU772tY8febJ5dm3BLihpDx/jif9LGVMe0aLJaAtHjrYESZe8ho/7WGr8HGPvsu3RKAkUD3LNJVcyIFsUVPtq8mS9LQJac4uDCRpttMEoo1ZyVbFSK7lRG7Wt2EahTA4bfOmAyK3Dj3tt0zw2hrT4u/x3pXb8ORRiy7A3vj+e73+1IkGHlkEr07nMSzKpiI4jJNCqdi2vkd0jDP906/gk2mdiLbx3BnxZnxl+MkCBCAjWZ6ZwWQjMwsoOx+GDHgAAAA==", // 9 | 8968, // 10 | "font/woff2", // 11 | "/_next/static/media/959fe13ebd2a94e9-s.woff2/", // 12 | 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/include/http_server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "duckdb/main/client_context.hpp" 4 | #include "duckdb/main/connection_manager.hpp" 5 | #include "execution_request.hpp" 6 | #include "files.hpp" 7 | #include "http_error_data.hpp" 8 | #include "result.hpp" 9 | #include "table_functions_bind_data.hpp" 10 | #include "uri.hpp" 11 | 12 | #define CPPHTTPLIB_OPENSSL_SUPPORT 13 | #include "httplib.hpp" 14 | 15 | #include 16 | 17 | namespace duckdb { 18 | using namespace duckdb_httplib_openssl; // NOLINT(*-build-using-namespace) 19 | using namespace duckdb_yyjson; // NOLINT(*-build-using-namespace) 20 | #include 21 | 22 | static bool tryCommand(const std::string& command, const std::string& argument) { 23 | // Ensure argument does not contain unsafe characters 24 | if (argument.find('"') != std::string::npos || argument.find(';') != std::string::npos || 25 | argument.find('|') != std::string::npos) { 26 | return false; // Prevent command injection 27 | } 28 | 29 | // Construct the command safely 30 | const std::string safeCommand = command + " \"" + argument + "\""; 31 | // system(...) returns -1 on error; return code depends on shell command success 32 | return (std::system(safeCommand.c_str()) == 0); 33 | } 34 | 35 | static void openURL(const std::string& url) { 36 | // Try xdg-open first 37 | if (tryCommand("xdg-open", url)) { 38 | return; 39 | } 40 | // Try open (macOS) 41 | if (tryCommand("open", url)) { 42 | return; 43 | } 44 | // Finally try start (Windows) 45 | // The 2> redirection won't work on Windows cmd, but won't break either 46 | tryCommand("start", url + " 2> nul"); 47 | } 48 | 49 | class DashHttpServer { 50 | public: 51 | DashHttpServer() { 52 | server.Post("/query", [this](const Request &req, Response &res) { ExecuteQuery(req, res); }); 53 | server.Post("/cancel", [this](const Request &req, Response &res) { CancelAllQueries(req, res); }); 54 | server.Get("/ping", [](const Request &, Response &res) { res.body = "pong"; }); 55 | server.Get(".*", [this](const Request &req, Response &res) { ServeUi(req, res); }); 56 | server.Options(".*", [this](const Request &, Response &res) { AddCorsHeaders(res); }); 57 | } 58 | ~DashHttpServer() { 59 | Stop(); 60 | } 61 | 62 | void XorEncrypt(std::vector& data, const std::vector& key) { 63 | D_ASSERT(!key.empty()); 64 | for (size_t i = 0; i < data.size(); i++) { 65 | data[i] ^= key[i % key.size()]; 66 | } 67 | } 68 | 69 | std::string XorEncrypt(const std::string& data, const std::string& key) { 70 | std::vector data_vec(data.begin(), data.end()); 71 | std::vector key_vec(key.begin(), key.end()); 72 | XorEncrypt(data_vec, key_vec); 73 | return std::string(data_vec.begin(), data_vec.end()); 74 | } 75 | 76 | void Start(ClientContext &c, const StartServerFunctionData &data) { 77 | if (started.exchange(true)) { 78 | throw ExecutorException("Server already started"); 79 | } 80 | 81 | std::string base_url = "http://" + data.host + ":" + std::to_string(data.port); 82 | std::string user_click_url = base_url; 83 | // say that the client should use the duckdbhttp API 84 | // without specifying the url parameter, the client will use the same URL as the server 85 | user_click_url += "/?api=http"; 86 | // add the API key if it is set 87 | if (!data.api_key.empty()) { 88 | auto encryption_key = "DuckDB"; // this should not be secure, but just obfuscate the key 89 | auto encrypted_api_key = XorEncrypt(data.api_key, encryption_key); 90 | user_click_url += "&k=" + StringUtil::URLEncode(encrypted_api_key); 91 | } 92 | if (data.open_browser) { 93 | openURL(user_click_url); 94 | } 95 | Printer::Print("Starting server on " + user_click_url); 96 | 97 | db_instance = c.db; 98 | 99 | api_key = data.api_key; 100 | enable_cors = data.enable_cors; 101 | ui_proxy = data.ui_proxy; 102 | 103 | const auto host = data.host; 104 | const auto port = data.port; 105 | server_thread = std::thread([host, port, this] { 106 | if (!server.listen(host, port)) { 107 | Printer::Print("Failed to start HTTP server on " + host + ":" + std::to_string(port)); 108 | Stop(false); 109 | } 110 | }); 111 | } 112 | 113 | void Stop(const bool join_thread = true) { 114 | if (!started.exchange(false)) { 115 | return; 116 | } 117 | 118 | Printer::Print("Stopping server"); 119 | server.stop(); 120 | db_instance.reset(); 121 | if (join_thread) { 122 | server_thread.join(); 123 | } else { 124 | server_thread.detach(); 125 | } 126 | } 127 | 128 | private: 129 | void ExecuteQuery(const Request &req, Response &res) const { 130 | AddCorsHeaders(res); 131 | auto execution_request = ExecutionRequest::FromRequest(req, api_key); 132 | RETURN_IF_ERROR_CB(execution_request, ([&res](const HttpErrorData &error) { RespondError(error, res); })) 133 | auto execution_error = execution_request->Execute(db_instance.lock(), res); 134 | RETURN_IF_ERROR_CB(execution_error, ([&res](const HttpErrorData &error) { RespondError(error, res); })); 135 | } 136 | 137 | void CancelAllQueries(const Request &req, Response &res) const { 138 | AddCorsHeaders(res); 139 | 140 | auto result = HasCorrectApiKey(api_key, req); 141 | RETURN_IF_ERROR_CB(result, ([&res](const HttpErrorData &error) { RespondError(error, res); })); 142 | 143 | auto db = db_instance.lock(); 144 | if (!db) { 145 | res.status = 500; 146 | res.set_content("{\"error\": \"Database not available\"}", "application/json"); 147 | return; 148 | } 149 | 150 | // Get all active connections and interrupt them 151 | auto &connection_manager = ConnectionManager::Get(*db); 152 | auto connections = connection_manager.GetConnectionList(); 153 | 154 | idx_t cancelled_count = 0; 155 | for (auto &context : connections) { 156 | if (context) { 157 | context->Interrupt(); 158 | cancelled_count++; 159 | } 160 | } 161 | 162 | // Return success response with count 163 | res.status = 200; 164 | res.set_content("{\"cancelled\": " + std::to_string(cancelled_count) + "}", "application/json"); 165 | } 166 | 167 | void ServeUi(const Request &req, Response &res) const { 168 | if (ui_proxy.Valid()) { 169 | ServerFromProxy(req, res); 170 | } else { 171 | ServerFromLocal(req, res); 172 | } 173 | } 174 | 175 | vector Base64Decode(const string &key) const { 176 | auto result_size = Blob::FromBase64Size(key); 177 | auto output = duckdb::unique_ptr(new unsigned char[result_size]); 178 | Blob::FromBase64(key, output.get(), result_size); 179 | return vector(output.get(), output.get() + result_size); 180 | } 181 | 182 | 183 | void ServerFromLocal(const Request &req, Response &res) const { 184 | auto file = GetFile(req.path); 185 | if (!file) { 186 | res.status = 404; 187 | res.set_content("Not found", "text/plain"); 188 | return; 189 | } 190 | 191 | auto string_content = std::string(file->content, file->content_length); 192 | auto result_size = Blob::FromBase64Size(string_content); 193 | vector decoded = Base64Decode(file->content); 194 | unsigned char *decoded_ptr = decoded.data(); 195 | // create a string from decoded bytes 196 | std::string decoded_str(reinterpret_cast(decoded_ptr), result_size); 197 | 198 | // get the content as bytes 199 | res.set_content(decoded_str, file->content_type); 200 | } 201 | 202 | void ServerFromProxy(const Request &req, Response &res) const { 203 | Client cli(ui_proxy.HostWithProtocol()); 204 | string path; 205 | if (StringUtil::StartsWith(req.path, ui_proxy.Path)) { 206 | path = req.path; 207 | } else { 208 | path = ui_proxy.Path + req.path; 209 | } 210 | auto response = cli.Get(path); 211 | 212 | if (!response) { 213 | res.status = 500; 214 | res.set_content(to_string(response.error()), "text/plain"); 215 | return; 216 | } 217 | 218 | res.set_content(response->body.c_str(), response->body.size(), response->get_header_value("Content-Type")); 219 | res.status = response->status; 220 | } 221 | 222 | static void RespondError(HttpErrorData error, Response &res) { 223 | error.ConvertErrorToJSON(); 224 | res.status = error.status_code; 225 | res.set_content(error.Message(), "application/json"); 226 | } 227 | 228 | void AddCorsHeaders(Response &res) const { 229 | if (!enable_cors) { 230 | return; 231 | } 232 | res.set_header("Access-Control-Allow-Origin", "*"); 233 | res.set_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); 234 | res.set_header("Access-Control-Allow-Headers", "X-Api-Key, Content-Type"); 235 | res.set_header("Access-Control-Allow-Credentials", "true"); 236 | res.set_header("Access-Control-Max-Age", "86400"); 237 | } 238 | 239 | std::atomic_bool started {false}; 240 | Server server; 241 | 242 | string api_key {}; 243 | Uri ui_proxy {}; 244 | bool enable_cors = false; 245 | 246 | std::thread server_thread; 247 | weak_ptr db_instance; 248 | }; 249 | 250 | DashHttpServer server; 251 | 252 | inline DashHttpServer &GetServer(ClientContext &) { 253 | return server; 254 | } 255 | 256 | } // namespace duckdb 257 | -------------------------------------------------------------------------------- /src/gen/file_404_html.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_404_HTML = { 6 | // Content 7 | "PCFET0NUWVBFIGh0bWw+PGh0bWwgbGFuZz0iZW4iPjxoZWFkPjxtZXRhIGNoYXJTZXQ9InV0Zi04Ii8+PG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xIi8+PGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSIvX25leHQvc3RhdGljL2Nzcy8yMWJiODQyNWJkNzE0NmNjLmNzcyIgZGF0YS1wcmVjZWRlbmNlPSJuZXh0Ii8+PGxpbmsgcmVsPSJwcmVsb2FkIiBhcz0ic2NyaXB0IiBmZXRjaFByaW9yaXR5PSJsb3ciIGhyZWY9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzL3dlYnBhY2stOWE2OGYwMTk2YTA2MGViMC5qcyIvPjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy84ZDQ2Njg0OS0zZTFjMTZhYWJlMzNjYWZmLmpzIiBhc3luYz0iIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvNDI0LWU3MjRmZDBkNDUyMzZjNTAuanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy9tYWluLWFwcC1lM2E5MmMxOGZjOTgzZmUxLmpzIiBhc3luYz0iIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvMjczYWNkYzAtMDc0NjUxYzZkY2NlNTdhYS5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzLzY5Ni1iNjM2ZmUzNGI3OWE0ZTAwLmpzIiBhc3luYz0iIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvMjktZjZhZWRlYjA0MTMzZGE3Zi5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzL2FwcC9sYXlvdXQtYTIyN2I3Yzg4NGQ3NTRlYi5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PG1ldGEgbmFtZT0icm9ib3RzIiBjb250ZW50PSJub2luZGV4Ii8+PG1ldGEgbmFtZT0ibmV4dC1zaXplLWFkanVzdCIgY29udGVudD0iIi8+PHRpdGxlPjQwNDogVGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZC48L3RpdGxlPjx0aXRsZT5EYXNoPC90aXRsZT48bWV0YSBuYW1lPSJhcHBsZS1tb2JpbGUtd2ViLWFwcC10aXRsZSIgY29udGVudD0iRGFzaCIvPjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy9wb2x5ZmlsbHMtNDIzNzJlZDEzMDQzMWIwYS5qcyIgbm9Nb2R1bGU9IiI+PC9zY3JpcHQ+PC9oZWFkPjxib2R5IGNsYXNzPSJtaW4tMTAwZHZoIGJnLWJhY2tncm91bmQgYW50aWFsaWFzZWQiPjxtYWluPjxkaXYgY2xhc3M9InctZnVsbCBtaW4taC1bMTAwZHZoXSBzdXBwb3J0cy1baGVpZ2h0OjEwMGR2aF06bWluLWgtZHZoIj48c2NyaXB0PigoZSx0LHIsaSxuLGEscyxvKT0+e2xldCBsPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudCxkPVsibGlnaHQiLCJkYXJrIl07ZnVuY3Rpb24gdSh0KXt2YXIgcjsoQXJyYXkuaXNBcnJheShlKT9lOltlXSkuZm9yRWFjaChlPT57bGV0IHI9ImNsYXNzIj09PWUsaT1yJiZhP24ubWFwKGU9PmFbZV18fGUpOm47cj8obC5jbGFzc0xpc3QucmVtb3ZlKC4uLmkpLGwuY2xhc3NMaXN0LmFkZCh0KSk6bC5zZXRBdHRyaWJ1dGUoZSx0KX0pLHI9dCxvJiZkLmluY2x1ZGVzKHIpJiYobC5zdHlsZS5jb2xvclNjaGVtZT1yKX1pZihpKXUoaSk7ZWxzZSB0cnl7bGV0IGU9bG9jYWxTdG9yYWdlLmdldEl0ZW0odCl8fHIsaT1zJiYic3lzdGVtIj09PWU/d2luZG93Lm1hdGNoTWVkaWEoIihwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykiKS5tYXRjaGVzPyJkYXJrIjoibGlnaHQiOmU7dShpKX1jYXRjaChlKXt9fSkoImNsYXNzIiwidGhlbWUiLCJzeXN0ZW0iLG51bGwsWyJsaWdodCIsImRhcmsiXSxudWxsLHRydWUsdHJ1ZSk8L3NjcmlwdD48ZGl2IGNsYXNzPSJmbGV4IGZsZXgtcm93IG1pbi1oLVsxMDBkdmhdIHN1cHBvcnRzLVtoZWlnaHQ6MTAwZHZoXTptaW4taC1kdmggdy1mdWxsIj48ZGl2IHN0eWxlPSJmb250LWZhbWlseTpzeXN0ZW0tdWksJnF1b3Q7U2Vnb2UgVUkmcXVvdDssUm9ib3RvLEhlbHZldGljYSxBcmlhbCxzYW5zLXNlcmlmLCZxdW90O0FwcGxlIENvbG9yIEVtb2ppJnF1b3Q7LCZxdW90O1NlZ29lIFVJIEVtb2ppJnF1b3Q7O2hlaWdodDoxMDB2aDt0ZXh0LWFsaWduOmNlbnRlcjtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2FsaWduLWl0ZW1zOmNlbnRlcjtqdXN0aWZ5LWNvbnRlbnQ6Y2VudGVyIj48ZGl2PjxzdHlsZT5ib2R5e2NvbG9yOiMwMDA7YmFja2dyb3VuZDojZmZmO21hcmdpbjowfS5uZXh0LWVycm9yLWgxe2JvcmRlci1yaWdodDoxcHggc29saWQgcmdiYSgwLDAsMCwuMyl9QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTpkYXJrKXtib2R5e2NvbG9yOiNmZmY7YmFja2dyb3VuZDojMDAwfS5uZXh0LWVycm9yLWgxe2JvcmRlci1yaWdodDoxcHggc29saWQgcmdiYSgyNTUsMjU1LDI1NSwuMyl9fTwvc3R5bGU+PGgxIGNsYXNzPSJuZXh0LWVycm9yLWgxIiBzdHlsZT0iZGlzcGxheTppbmxpbmUtYmxvY2s7bWFyZ2luOjAgMjBweCAwIDA7cGFkZGluZzowIDIzcHggMCAwO2ZvbnQtc2l6ZToyNHB4O2ZvbnQtd2VpZ2h0OjUwMDt2ZXJ0aWNhbC1hbGlnbjp0b3A7bGluZS1oZWlnaHQ6NDlweCI+NDA0PC9oMT48ZGl2IHN0eWxlPSJkaXNwbGF5OmlubGluZS1ibG9jayI+PGgyIHN0eWxlPSJmb250LXNpemU6MTRweDtmb250LXdlaWdodDo0MDA7bGluZS1oZWlnaHQ6NDlweDttYXJnaW46MCI+VGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZC48L2gyPjwvZGl2PjwvZGl2PjwvZGl2PjwvZGl2PjwvZGl2PjxzZWN0aW9uIGFyaWEtbGFiZWw9Ik5vdGlmaWNhdGlvbnMgYWx0K1QiIHRhYmluZGV4PSItMSIgYXJpYS1saXZlPSJwb2xpdGUiIGFyaWEtcmVsZXZhbnQ9ImFkZGl0aW9ucyB0ZXh0IiBhcmlhLWF0b21pYz0iZmFsc2UiPjwvc2VjdGlvbj48L21haW4+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzL3dlYnBhY2stOWE2OGYwMTk2YTA2MGViMC5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdD4oc2VsZi5fX25leHRfZj1zZWxmLl9fbmV4dF9mfHxbXSkucHVzaChbMF0pPC9zY3JpcHQ+PHNjcmlwdD5zZWxmLl9fbmV4dF9mLnB1c2goWzEsIjE6XCIkU3JlYWN0LmZyYWdtZW50XCJcbjI6SVszNzczMCxbXSxcIkNsaWVudFNlZ21lbnRSb290XCJdXG4zOklbMzAwNDMsW1wiMzQ2XCIsXCJzdGF0aWMvY2h1bmtzLzI3M2FjZGMwLTA3NDY1MWM2ZGNjZTU3YWEuanNcIixcIjY5NlwiLFwic3RhdGljL2NodW5rcy82OTYtYjYzNmZlMzRiNzlhNGUwMC5qc1wiLFwiMjlcIixcInN0YXRpYy9jaHVua3MvMjktZjZhZWRlYjA0MTMzZGE3Zi5qc1wiLFwiMTc3XCIsXCJzdGF0aWMvY2h1bmtzL2FwcC9sYXlvdXQtYTIyN2I3Yzg4NGQ3NTRlYi5qc1wiXSxcImRlZmF1bHRcIl1cbjQ6SVs0NjE1NSxbXSxcIlwiXVxuNTpJWzcyNDcxLFtdLFwiXCJdXG43OklbMzE3NyxbXSxcIk91dGxldEJvdW5kYXJ5XCJdXG5hOklbMzE3NyxbXSxcIlZpZXdwb3J0Qm91bmRhcnlcIl1cbmM6SVszMTc3LFtdLFwiTWV0YWRhdGFCb3VuZGFyeVwiXVxuZTpJWzk0OTkwLFtdLFwiXCJdXG46SExbXCIvX25leHQvc3RhdGljL2Nzcy8yMWJiODQyNWJkNzE0NmNjLmNzc1wiLFwic3R5bGVcIl1cbiJdKTwvc2NyaXB0PjxzY3JpcHQ+c2VsZi5fX25leHRfZi5wdXNoKFsxLCIwOntcIlBcIjpudWxsLFwiYlwiOlwiTUJvMWFBWDFfVDAxRDExa3RfRkplXCIsXCJwXCI6XCJcIixcImNcIjpbXCJcIixcIl9ub3QtZm91bmRcIl0sXCJpXCI6ZmFsc2UsXCJmXCI6W1tbXCJcIix7XCJjaGlsZHJlblwiOltcIi9fbm90LWZvdW5kXCIse1wiY2hpbGRyZW5cIjpbXCJfX1BBR0VfX1wiLHt9XX1dfSxcIiR1bmRlZmluZWRcIixcIiR1bmRlZmluZWRcIix0cnVlXSxbXCJcIixbXCIkXCIsXCIkMVwiLFwiY1wiLHtcImNoaWxkcmVuXCI6W1tbXCIkXCIsXCJsaW5rXCIsXCIwXCIse1wicmVsXCI6XCJzdHlsZXNoZWV0XCIsXCJocmVmXCI6XCIvX25leHQvc3RhdGljL2Nzcy8yMWJiODQyNWJkNzE0NmNjLmNzc1wiLFwicHJlY2VkZW5jZVwiOlwibmV4dFwiLFwiY3Jvc3NPcmlnaW5cIjpcIiR1bmRlZmluZWRcIixcIm5vbmNlXCI6XCIkdW5kZWZpbmVkXCJ9XV0sW1wiJFwiLFwiJEwyXCIsbnVsbCx7XCJDb21wb25lbnRcIjpcIiQzXCIsXCJzbG90c1wiOntcImNoaWxkcmVuXCI6W1wiJFwiLFwiJEw0XCIsbnVsbCx7XCJwYXJhbGxlbFJvdXRlcktleVwiOlwiY2hpbGRyZW5cIixcImVycm9yXCI6XCIkdW5kZWZpbmVkXCIsXCJlcnJvclN0eWxlc1wiOlwiJHVuZGVmaW5lZFwiLFwiZXJyb3JTY3JpcHRzXCI6XCIkdW5kZWZpbmVkXCIsXCJ0ZW1wbGF0ZVwiOltcIiRcIixcIiRMNVwiLG51bGwse31dLFwidGVtcGxhdGVTdHlsZXNcIjpcIiR1bmRlZmluZWRcIixcInRlbXBsYXRlU2NyaXB0c1wiOlwiJHVuZGVmaW5lZFwiLFwibm90Rm91bmRcIjpbW1tcIiRcIixcInRpdGxlXCIsbnVsbCx7XCJjaGlsZHJlblwiOlwiNDA0OiBUaGlzIHBhZ2UgY291bGQgbm90IGJlIGZvdW5kLlwifV0sW1wiJFwiLFwiZGl2XCIsbnVsbCx7XCJzdHlsZVwiOntcImZvbnRGYW1pbHlcIjpcInN5c3RlbS11aSxcXFwiU2Vnb2UgVUlcXFwiLFJvYm90byxIZWx2ZXRpY2EsQXJpYWwsc2Fucy1zZXJpZixcXFwiQXBwbGUgQ29sb3IgRW1vamlcXFwiLFxcXCJTZWdvZSBVSSBFbW9qaVxcXCJcIixcImhlaWdodFwiOlwiMTAwdmhcIixcInRleHRBbGlnblwiOlwiY2VudGVyXCIsXCJkaXNwbGF5XCI6XCJmbGV4XCIsXCJmbGV4RGlyZWN0aW9uXCI6XCJjb2x1bW5cIixcImFsaWduSXRlbXNcIjpcImNlbnRlclwiLFwianVzdGlmeUNvbnRlbnRcIjpcImNlbnRlclwifSxcImNoaWxkcmVuXCI6W1wiJFwiLFwiZGl2XCIsbnVsbCx7XCJjaGlsZHJlblwiOltbXCIkXCIsXCJzdHlsZVwiLG51bGwse1wiZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUxcIjp7XCJfX2h0bWxcIjpcImJvZHl7Y29sb3I6IzAwMDtiYWNrZ3JvdW5kOiNmZmY7bWFyZ2luOjB9Lm5leHQtZXJyb3ItaDF7Ym9yZGVyLXJpZ2h0OjFweCBzb2xpZCByZ2JhKDAsMCwwLC4zKX1AbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOmRhcmspe2JvZHl7Y29sb3I6I2ZmZjtiYWNrZ3JvdW5kOiMwMDB9Lm5leHQtZXJyb3ItaDF7Ym9yZGVyLXJpZ2h0OjFweCBzb2xpZCByZ2JhKDI1NSwyNTUsMjU1LC4zKX19XCJ9fV0sW1wiJFwiLFwiaDFcIixudWxsLHtcImNsYXNzTmFtZVwiOlwibmV4dC1lcnJvci1oMVwiLFwic3R5bGVcIjp7XCJkaXNwbGF5XCI6XCJpbmxpbmUtYmxvY2tcIixcIm1hcmdpblwiOlwiMCAyMHB4IDAgMFwiLFwicGFkZGluZ1wiOlwiMCAyM3B4IDAgMFwiLFwiZm9udFNpemVcIjoyNCxcImZvbnRXZWlnaHRcIjo1MDAsXCJ2ZXJ0aWNhbEFsaWduXCI6XCJ0b3BcIixcImxpbmVIZWlnaHRcIjpcIjQ5cHhcIn0sXCJjaGlsZHJlblwiOjQwNH1dLFtcIiRcIixcImRpdlwiLG51bGwse1wic3R5bGVcIjp7XCJkaXNwbGF5XCI6XCJpbmxpbmUtYmxvY2tcIn0sXCJjaGlsZHJlblwiOltcIiRcIixcImgyXCIsbnVsbCx7XCJzdHlsZVwiOntcImZvbnRTaXplXCI6MTQsXCJmb250V2VpZ2h0XCI6NDAwLFwibGluZUhlaWdodFwiOlwiNDlweFwiLFwibWFyZ2luXCI6MH0sXCJjaGlsZHJlblwiOlwiVGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZC5cIn1dfV1dfV19XV0sW11dLFwiZm9yYmlkZGVuXCI6XCIkdW5kZWZpbmVkXCIsXCJ1bmF1dGhvcml6ZWRcIjpcIiR1bmRlZmluZWRcIn1dfSxcInBhcmFtc1wiOnt9LFwicHJvbWlzZVwiOlwiJEA2XCJ9XV19XSx7XCJjaGlsZHJlblwiOltcIi9fbm90LWZvdW5kXCIsW1wiJFwiLFwiJDFcIixcImNcIix7XCJjaGlsZHJlblwiOltudWxsLFtcIiRcIixcIiRMNFwiLG51bGwse1wicGFyYWxsZWxSb3V0ZXJLZXlcIjpcImNoaWxkcmVuXCIsXCJlcnJvclwiOlwiJHVuZGVmaW5lZFwiLFwiZXJyb3JTdHlsZXNcIjpcIiR1bmRlZmluZWRcIixcImVycm9yU2NyaXB0c1wiOlwiJHVuZGVmaW5lZFwiLFwidGVtcGxhdGVcIjpbXCIkXCIsXCIkTDVcIixudWxsLHt9XSxcInRlbXBsYXRlU3R5bGVzXCI6XCIkdW5kZWZpbmVkXCIsXCJ0ZW1wbGF0ZVNjcmlwdHNcIjpcIiR1bmRlZmluZWRcIixcIm5vdEZvdW5kXCI6XCIkdW5kZWZpbmVkXCIsXCJmb3JiaWRkZW5cIjpcIiR1bmRlZmluZWRcIixcInVuYXV0aG9yaXplZFwiOlwiJHVuZGVmaW5lZFwifV1dfV" 8 | "0se1wiY2hpbGRyZW5cIjpbXCJfX1BBR0VfX1wiLFtcIiRcIixcIiQxXCIsXCJjXCIse1wiY2hpbGRyZW5cIjpbW1tcIiRcIixcInRpdGxlXCIsbnVsbCx7XCJjaGlsZHJlblwiOlwiNDA0OiBUaGlzIHBhZ2UgY291bGQgbm90IGJlIGZvdW5kLlwifV0sW1wiJFwiLFwiZGl2XCIsbnVsbCx7XCJzdHlsZVwiOlwiJDA6ZjowOjE6MTpwcm9wczpjaGlsZHJlbjoxOnByb3BzOnNsb3RzOmNoaWxkcmVuOnByb3BzOm5vdEZvdW5kOjA6MTpwcm9wczpzdHlsZVwiLFwiY2hpbGRyZW5cIjpbXCIkXCIsXCJkaXZcIixudWxsLHtcImNoaWxkcmVuXCI6W1tcIiRcIixcInN0eWxlXCIsbnVsbCx7XCJkYW5nZXJvdXNseVNldElubmVySFRNTFwiOntcIl9faHRtbFwiOlwiYm9keXtjb2xvcjojMDAwO2JhY2tncm91bmQ6I2ZmZjttYXJnaW46MH0ubmV4dC1lcnJvci1oMXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMCwwLDAsLjMpfUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6ZGFyayl7Ym9keXtjb2xvcjojZmZmO2JhY2tncm91bmQ6IzAwMH0ubmV4dC1lcnJvci1oMXtib3JkZXItcmlnaHQ6MXB4IHNvbGlkIHJnYmEoMjU1LDI1NSwyNTUsLjMpfX1cIn19XSxbXCIkXCIsXCJoMVwiLG51bGwse1wiY2xhc3NOYW1lXCI6XCJuZXh0LWVycm9yLWgxXCIsXCJzdHlsZVwiOlwiJDA6ZjowOjE6MTpwcm9wczpjaGlsZHJlbjoxOnByb3BzOnNsb3RzOmNoaWxkcmVuOnByb3BzOm5vdEZvdW5kOjA6MTpwcm9wczpjaGlsZHJlbjpwcm9wczpjaGlsZHJlbjoxOnByb3BzOnN0eWxlXCIsXCJjaGlsZHJlblwiOjQwNH1dLFtcIiRcIixcImRpdlwiLG51bGwse1wic3R5bGVcIjpcIiQwOmY6MDoxOjE6cHJvcHM6Y2hpbGRyZW46MTpwcm9wczpzbG90czpjaGlsZHJlbjpwcm9wczpub3RGb3VuZDowOjE6cHJvcHM6Y2hpbGRyZW46cHJvcHM6Y2hpbGRyZW46Mjpwcm9wczpzdHlsZVwiLFwiY2hpbGRyZW5cIjpbXCIkXCIsXCJoMlwiLG51bGwse1wic3R5bGVcIjpcIiQwOmY6MDoxOjE6cHJvcHM6Y2hpbGRyZW46MTpwcm9wczpzbG90czpjaGlsZHJlbjpwcm9wczpub3RGb3VuZDowOjE6cHJvcHM6Y2hpbGRyZW46cHJvcHM6Y2hpbGRyZW46Mjpwcm9wczpjaGlsZHJlbjpwcm9wczpzdHlsZVwiLFwiY2hpbGRyZW5cIjpcIlRoaXMgcGFnZSBjb3VsZCBub3QgYmUgZm91bmQuXCJ9XX1dXX1dfV1dLFwiJHVuZGVmaW5lZFwiLG51bGwsW1wiJFwiLFwiJEw3XCIsbnVsbCx7XCJjaGlsZHJlblwiOltcIiRMOFwiLFwiJEw5XCIsbnVsbF19XV19XSx7fSxudWxsLGZhbHNlXX0sbnVsbCxmYWxzZV19LG51bGwsZmFsc2VdLFtcIiRcIixcIiQxXCIsXCJoXCIse1wiY2hpbGRyZW5cIjpbW1wiJFwiLFwibWV0YVwiLG51bGwse1wibmFtZVwiOlwicm9ib3RzXCIsXCJjb250ZW50XCI6XCJub2luZGV4XCJ9XSxbXCIkXCIsXCIkMVwiLFwidDJXcUk5Z3lkNEdiV1A5ZmswajlfXCIse1wiY2hpbGRyZW5cIjpbW1wiJFwiLFwiJExhXCIsbnVsbCx7XCJjaGlsZHJlblwiOlwiJExiXCJ9XSxbXCIkXCIsXCJtZXRhXCIsbnVsbCx7XCJuYW1lXCI6XCJuZXh0LXNpemUtYWRqdXN0XCIsXCJjb250ZW50XCI6XCJcIn1dXX1dLFtcIiRcIixcIiRMY1wiLG51bGwse1wiY2hpbGRyZW5cIjpcIiRMZFwifV1dfV0sZmFsc2VdXSxcIm1cIjpcIiR1bmRlZmluZWRcIixcIkdcIjpbXCIkZVwiLFwiJHVuZGVmaW5lZFwiXSxcInNcIjpmYWxzZSxcIlNcIjp0cnVlfVxuIl0pPC9zY3JpcHQ+PHNjcmlwdD5zZWxmLl9fbmV4dF9mLnB1c2goWzEsIjY6e31cbiJdKTwvc2NyaXB0PjxzY3JpcHQ+c2VsZi5fX25leHRfZi5wdXNoKFsxLCJiOltbXCIkXCIsXCJtZXRhXCIsXCIwXCIse1wiY2hhclNldFwiOlwidXRmLThcIn1dLFtcIiRcIixcIm1ldGFcIixcIjFcIix7XCJuYW1lXCI6XCJ2aWV3cG9ydFwiLFwiY29udGVudFwiOlwid2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTFcIn1dXVxuODpudWxsXG4iXSk8L3NjcmlwdD48c2NyaXB0PnNlbGYuX19uZXh0X2YucHVzaChbMSwiOTpudWxsXG5kOltdXG4iXSk8L3NjcmlwdD48L2JvZHk+PC9odG1sPg==", // 9 | 10908, // 10 | "text/html", // 11 | "/404.html/", // 12 | 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /scripts/install_npm_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # ------------------------- 5 | # Configuration 6 | # ------------------------- 7 | NODE_VERSION="v18.14.2" # Which Node version to download from nodejs.org 8 | INSTALL_DIR="./node_install" # Where to extract the portable Node 9 | DOWNLOAD_DIR="./node_download" # Where to save the tarball before extraction 10 | 11 | # ---------------------------------------------------- 12 | # 0) Root check & distribution detection 13 | # ---------------------------------------------------- 14 | if [ "$(id -u)" -eq 0 ]; then 15 | SUDO="" 16 | else 17 | if command -v sudo &>/dev/null; then 18 | SUDO="sudo" 19 | else 20 | echo "Error: sudo not found and not running as root." 21 | exit 1 22 | fi 23 | fi 24 | 25 | # OS release 26 | if [ -f /etc/os-release ]; then 27 | . /etc/os-release 28 | echo "Detected distribution: $NAME ($ID)" 29 | DISTRO_FAMILY="$ID_LIKE" 30 | DISTRO_ID="$ID" 31 | elif [ -f /etc/debian_version ]; then 32 | echo "Detected distribution: Debian (no /etc/os-release)" 33 | DISTRO_ID="debian" 34 | elif [ -f /etc/redhat-release ]; then 35 | echo "Detected distribution: Red Hat-based (no /etc/os-release)" 36 | DISTRO_ID="rhel" 37 | else 38 | echo "Could not detect Linux distribution." 39 | DISTRO_ID="unknown" 40 | fi 41 | 42 | # Check if musl or glibc 43 | if ldd --version 2>&1 | grep -q "musl"; then 44 | echo "Detected musl-based system (Alpine/musl)." 45 | MUSL_SYSTEM=true 46 | else 47 | echo "Detected glibc-based system." 48 | MUSL_SYSTEM=false 49 | fi 50 | 51 | # ---------------------------------------------------- 52 | # 1) Attempt portable tarball install from nodejs.org 53 | # ---------------------------------------------------- 54 | install_node_via_tarball() { 55 | echo "Attempting to install Node.js $NODE_VERSION via official tarball..." 56 | # Dependencies: tar, curl (or wget). We'll assume tar is present; we can ensure curl below. 57 | 58 | # Install curl if missing (on Debian/Ubuntu) 59 | if ! command -v curl &>/dev/null; then 60 | if [[ "$DISTRO_ID" =~ ubuntu|debian ]]; then 61 | $SUDO apt-get update 62 | $SUDO apt-get install -y curl 63 | elif [[ "$DISTRO_ID" =~ fedora ]]; then 64 | $SUDO dnf install -y curl 65 | elif [[ "$DISTRO_ID" =~ centos|rhel|almalinux|rocky ]]; then 66 | $SUDO yum install -y curl 67 | elif [ "$DISTRO_ID" = "arch" ]; then 68 | $SUDO pacman -Sy --noconfirm curl 69 | elif [ "$DISTRO_ID" = "opensuse" ] || [[ "$DISTRO_ID" =~ sles ]]; then 70 | $SUDO zypper refresh 71 | $SUDO zypper install -y curl 72 | elif $MUSL_SYSTEM; then 73 | $SUDO apk update 74 | $SUDO apk add curl 75 | fi 76 | fi 77 | 78 | # Architecture detection 79 | MACHINE="$(uname -m | tr '[:upper:]' '[:lower:]')" # e.g. x86_64, aarch64 80 | case "$MACHINE" in 81 | x86_64|amd64) 82 | ARCH="x64" 83 | ;; 84 | *arm64*|*aarch64*) 85 | ARCH="arm64" 86 | ;; 87 | *armv7l*|*armv6l*) 88 | # Node has specific builds for armv7l, armv6l, etc. 89 | # We'll guess armv7l for 32-bit ARM: 90 | ARCH="armv7l" 91 | ;; 92 | *) 93 | echo "Unsupported architecture: $MACHINE" 94 | return 1 95 | ;; 96 | esac 97 | 98 | # If Linux, determine musl vs. glibc in the tarball name 99 | UNAME_SYS="$(uname | tr '[:upper:]' '[:lower:]')" # "linux" or "darwin" 100 | if [ "$UNAME_SYS" = "linux" ]; then 101 | if $MUSL_SYSTEM; then 102 | # Node official musl builds exist for Node >=16 on x64 & arm64 103 | TARBALL_OS="linux-musl" 104 | else 105 | TARBALL_OS="linux" 106 | fi 107 | elif [ "$UNAME_SYS" = "darwin" ]; then 108 | TARBALL_OS="darwin" 109 | else 110 | echo "This script currently supports Linux or Darwin (macOS) only." 111 | return 1 112 | fi 113 | 114 | TARBALL_BASENAME="node-$NODE_VERSION-$TARBALL_OS-$ARCH" 115 | TARBALL_FILE="$TARBALL_BASENAME.tar.gz" 116 | DOWNLOAD_URL="https://nodejs.org/dist/$NODE_VERSION/$TARBALL_FILE" 117 | 118 | mkdir -p "$DOWNLOAD_DIR" "$INSTALL_DIR" 119 | 120 | echo "Downloading $DOWNLOAD_URL ..." 121 | if ! curl -fSL "$DOWNLOAD_URL" -o "$DOWNLOAD_DIR/$TARBALL_FILE"; then 122 | echo "Failed to download $DOWNLOAD_URL" 123 | return 1 124 | fi 125 | 126 | echo "Extracting Node.js to $INSTALL_DIR ..." 127 | tar -xf "$DOWNLOAD_DIR/$TARBALL_FILE" -C "$INSTALL_DIR" 128 | 129 | # Our extracted folder will be: $INSTALL_DIR/node-v18.14.2-linux-x64, for example 130 | EXTRACTED_DIR="$INSTALL_DIR/$TARBALL_BASENAME" 131 | NODE_BIN="$EXTRACTED_DIR/bin/node" 132 | NPM_BIN="$EXTRACTED_DIR/bin/npm" 133 | 134 | if [ ! -x "$NODE_BIN" ] || [ ! -x "$NPM_BIN" ]; then 135 | echo "Missing node or npm in $EXTRACTED_DIR" 136 | return 1 137 | fi 138 | 139 | echo "Portable Node installed to: $EXTRACTED_DIR" 140 | echo "Added to PATH: $NODE_BIN" 141 | # add to PATH 142 | export PATH="$EXTRACTED_DIR/bin:$PATH" 143 | 144 | echo "Verify extracted Node.js installation..." 145 | if ! "$NODE_BIN" --version; then 146 | echo "Node.js not found or failed to run." 147 | return 1 148 | fi 149 | 150 | echo "Verifying npm installation..." 151 | if ! npm --version; then 152 | echo "npm not found or failed to run." 153 | exit 1 # Use exit if this is a standalone script 154 | fi 155 | 156 | echo "Node.js and npm installed successfully." 157 | 158 | 159 | 160 | 161 | # Optionally create symlinks in /usr/local/bin or similar: 162 | $SUDO ln -sf "$NODE_BIN" /usr/local/bin/node 163 | $SUDO ln -sf "$NPM_BIN" /usr/local/bin/npm 164 | 165 | return 0 166 | } 167 | 168 | # ---------------------------------------------------- 169 | # 2) NodeSource (Node 18.x) as a fallback 170 | # ---------------------------------------------------- 171 | install_node18_nodesource() { 172 | echo "Attempting to install Node.js 18.x from NodeSource..." 173 | 174 | if $MUSL_SYSTEM; then 175 | echo "NodeSource does not publish Alpine packages. Cannot use NodeSource on Alpine." 176 | return 1 177 | fi 178 | 179 | case "$DISTRO_ID" in 180 | ubuntu|debian) 181 | $SUDO apt-get update 182 | $SUDO apt-get install -y curl ca-certificates 183 | if ! curl -fsSL https://deb.nodesource.com/setup_18.x | $SUDO bash -; then 184 | return 1 185 | fi 186 | $SUDO apt-get install -y nodejs || return 1 187 | ;; 188 | fedora) 189 | $SUDO dnf install -y curl 190 | if ! curl -fsSL https://rpm.nodesource.com/setup_18.x | $SUDO bash -; then 191 | return 1 192 | fi 193 | $SUDO dnf install -y nodejs || return 1 194 | ;; 195 | centos|rhel|almalinux|rocky) 196 | $SUDO yum install -y curl 197 | if ! curl -fsSL https://rpm.nodesource.com/setup_18.x | $SUDO bash -; then 198 | return 1 199 | fi 200 | $SUDO yum install -y nodejs || return 1 201 | ;; 202 | opensuse*|sles) 203 | echo "NodeSource doesn't officially support openSUSE/SLES directly." 204 | echo "Cannot install Node 18.x via NodeSource on $DISTRO_ID." 205 | return 1 206 | ;; 207 | arch) 208 | echo "NodeSource doesn't provide scripts for Arch. Cannot use NodeSource." 209 | return 1 210 | ;; 211 | *) 212 | if [[ "$DISTRO_FAMILY" =~ debian ]]; then 213 | $SUDO apt-get update 214 | $SUDO apt-get install -y curl ca-certificates 215 | if ! curl -fsSL https://deb.nodesource.com/setup_18.x | $SUDO bash -; then 216 | return 1 217 | fi 218 | $SUDO apt-get install -y nodejs || return 1 219 | elif [[ "$DISTRO_FAMILY" =~ rhel|fedora ]]; then 220 | $SUDO yum install -y curl 221 | if ! curl -fsSL https://rpm.nodesource.com/setup_18.x | $SUDO bash -; then 222 | return 1 223 | fi 224 | $SUDO yum install -y nodejs || return 1 225 | else 226 | echo "Unsupported distribution for NodeSource: $DISTRO_ID" 227 | return 1 228 | fi 229 | ;; 230 | esac 231 | 232 | echo "Successfully installed Node.js 18.x from NodeSource." 233 | return 0 234 | } 235 | 236 | # ---------------------------------------------------- 237 | # 3) Fallback: System repos (older Node) 238 | # ---------------------------------------------------- 239 | install_node_from_system() { 240 | echo "Falling back to installing Node.js/npm from system repos..." 241 | if $MUSL_SYSTEM; then 242 | echo "Alpine-based system. Using apk..." 243 | $SUDO apk update 244 | $SUDO apk add --no-cache nodejs npm 245 | else 246 | case "$DISTRO_ID" in 247 | ubuntu|debian) 248 | $SUDO apt-get update 249 | $SUDO apt-get install -y nodejs npm 250 | ;; 251 | fedora) 252 | $SUDO dnf install -y nodejs npm 253 | ;; 254 | centos|rhel|almalinux|rocky) 255 | $SUDO yum install -y nodejs npm 256 | ;; 257 | opensuse*|sles) 258 | $SUDO zypper refresh 259 | $SUDO zypper install -y nodejs npm 260 | ;; 261 | arch) 262 | $SUDO pacman -Sy --noconfirm nodejs npm 263 | ;; 264 | *) 265 | if [[ "$DISTRO_FAMILY" =~ debian ]]; then 266 | $SUDO apt-get update 267 | $SUDO apt-get install -y nodejs npm 268 | elif [[ "$DISTRO_FAMILY" =~ rhel|fedora ]]; then 269 | $SUDO yum install -y nodejs npm 270 | else 271 | echo "Unsupported distribution: $DISTRO_ID" 272 | exit 1 273 | fi 274 | ;; 275 | esac 276 | fi 277 | echo "Installed Node.js/npm from system repositories." 278 | } 279 | 280 | # ---------------------------------------------------- 281 | # 4) Main logic 282 | # ---------------------------------------------------- 283 | install_npm() { 284 | # If npm is already on PATH, do nothing 285 | if command -v npm &>/dev/null; then 286 | echo "npm is already installed." 287 | return 288 | fi 289 | 290 | echo "npm not found. Trying portable tarball method first..." 291 | 292 | if install_node_via_tarball; then 293 | echo "Installed Node.js via tarball. Done." 294 | else 295 | echo "Tarball install failed or is unsupported. Attempting NodeSource next..." 296 | if install_node18_nodesource; then 297 | echo "Installed Node.js via NodeSource. Done." 298 | else 299 | echo "NodeSource failed or unsupported. Installing from system repos..." 300 | install_node_from_system 301 | fi 302 | fi 303 | } 304 | 305 | install_npm 306 | -------------------------------------------------------------------------------- /src/gen/file_index_html.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_INDEX_HTML = { 6 | // Content 7 | "PCFET0NUWVBFIGh0bWw+PGh0bWwgbGFuZz0iZW4iPjxoZWFkPjxtZXRhIGNoYXJTZXQ9InV0Zi04Ii8+PG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xIi8+PGxpbmsgcmVsPSJwcmVsb2FkIiBocmVmPSIvX25leHQvc3RhdGljL21lZGlhLzczMjNiOWQwODczMDZhZGItcy5wLndvZmYyIiBhcz0iZm9udCIgY3Jvc3NvcmlnaW49IiIgdHlwZT0iZm9udC93b2ZmMiIvPjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iL19uZXh0L3N0YXRpYy9jc3MvMjFiYjg0MjViZDcxNDZjYy5jc3MiIGRhdGEtcHJlY2VkZW5jZT0ibmV4dCIvPjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iL19uZXh0L3N0YXRpYy9jc3MvNjBiMmUxMGQwYzUxZGMzMC5jc3MiIGRhdGEtcHJlY2VkZW5jZT0ibmV4dCIvPjxsaW5rIHJlbD0icHJlbG9hZCIgYXM9InNjcmlwdCIgZmV0Y2hQcmlvcml0eT0ibG93IiBocmVmPSIvX25leHQvc3RhdGljL2NodW5rcy93ZWJwYWNrLTlhNjhmMDE5NmEwNjBlYjAuanMiLz48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvOGQ0NjY4NDktM2UxYzE2YWFiZTMzY2FmZi5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzLzQyNC1lNzI0ZmQwZDQ1MjM2YzUwLmpzIiBhc3luYz0iIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvbWFpbi1hcHAtZTNhOTJjMThmYzk4M2ZlMS5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzLzI3M2FjZGMwLTA3NDY1MWM2ZGNjZTU3YWEuanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy82OTYtYjYzNmZlMzRiNzlhNGUwMC5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzLzI5LWY2YWVkZWIwNDEzM2RhN2YuanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy9hcHAvbGF5b3V0LWEyMjdiN2M4ODRkNzU0ZWIuanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy82YmI4MDgwNi1hNWQ2MDhhZDM5MDIwZWVlLmpzIiBhc3luYz0iIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvOTNiZmE4OGYtY2MzOTFmMzIwODAwMzkzNS5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzLzc1MTQ2ZDdkLTA5YWExYTI3ZWY4ZmZkODUuanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy8zYzFjOWY0YS0xMzk0MWE5OTcwMzE5NGFkLmpzIiBhc3luYz0iIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iL19uZXh0L3N0YXRpYy9jaHVua3MvMzJmYTM4NTUtMjI5YzcwYTlmOTE3MjlkMS5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzLzBiNDJjZTczLThhNDZhNDQ2Mzg1NmVjYjguanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy84NzItNmU2MjdlNmEyOTNlZDM5Ni5qcyIgYXN5bmM9IiI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzL2FwcC9wYWdlLTJhMWZiYTA2NWRlMzljYjguanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxtZXRhIG5hbWU9Im5leHQtc2l6ZS1hZGp1c3QiIGNvbnRlbnQ9IiIvPjx0aXRsZT5EYXNoPC90aXRsZT48bWV0YSBuYW1lPSJhcHBsZS1tb2JpbGUtd2ViLWFwcC10aXRsZSIgY29udGVudD0iRGFzaCIvPjxsaW5rIHJlbD0ibWFuaWZlc3QiIGhyZWY9Ii9tYW5pZmVzdC5qc29uIi8+PGxpbmsgcmVsPSJpY29uIiBocmVmPSIvZmF2aWNvbi5pY28iIHR5cGU9ImltYWdlL3gtaWNvbiIgc2l6ZXM9IjQ4eDQ4Ii8+PGxpbmsgcmVsPSJpY29uIiBocmVmPSIvaWNvbi5wbmc/ZjQ1MjFhNTlkZjBkOGYwYiIgdHlwZT0iaW1hZ2UvcG5nIiBzaXplcz0iOTZ4OTYiLz48bGluayByZWw9ImFwcGxlLXRvdWNoLWljb24iIGhyZWY9Ii9hcHBsZS1pY29uLnBuZz9lNDdjNDJkOTlmMmQyNDRmIiB0eXBlPSJpbWFnZS9wbmciIHNpemVzPSIxODB4MTgwIi8+PHNjcmlwdCBzcmM9Ii9fbmV4dC9zdGF0aWMvY2h1bmtzL3BvbHlmaWxscy00MjM3MmVkMTMwNDMxYjBhLmpzIiBub01vZHVsZT0iIj48L3NjcmlwdD48L2hlYWQ+PGJvZHkgY2xhc3M9Im1pbi0xMDBkdmggYmctYmFja2dyb3VuZCBhbnRpYWxpYXNlZCI+PG1haW4+PGRpdiBjbGFzcz0idy1mdWxsIG1pbi1oLVsxMDBkdmhdIHN1cHBvcnRzLVtoZWlnaHQ6MTAwZHZoXTptaW4taC1kdmgiPjxzY3JpcHQ+KChlLHQscixpLG4sYSxzLG8pPT57bGV0IGw9ZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LGQ9WyJsaWdodCIsImRhcmsiXTtmdW5jdGlvbiB1KHQpe3ZhciByOyhBcnJheS5pc0FycmF5KGUpP2U6W2VdKS5mb3JFYWNoKGU9PntsZXQgcj0iY2xhc3MiPT09ZSxpPXImJmE/bi5tYXAoZT0+YVtlXXx8ZSk6bjtyPyhsLmNsYXNzTGlzdC5yZW1vdmUoLi4uaSksbC5jbGFzc0xpc3QuYWRkKHQpKTpsLnNldEF0dHJpYnV0ZShlLHQpfSkscj10LG8mJmQuaW5jbHVkZXMocikmJihsLnN0eWxlLmNvbG9yU2NoZW1lPXIpfWlmKGkpdShpKTtlbHNlIHRyeXtsZXQgZT1sb2NhbFN0b3JhZ2UuZ2V0SXRlbSh0KXx8cixpPXMmJiJzeXN0ZW0iPT09ZT93aW5kb3cubWF0Y2hNZWRpYSgiKHByZWZlcnMtY29sb3Itc2NoZW1lOiBkYXJrKSIpLm1hdGNoZXM/ImRhcmsiOiJsaWdodCI6ZTt1KGkpfWNhdGNoKGUpe319KSgiY2xhc3MiLCJ0aGVtZSIsInN5c3RlbSIsbnVsbCxbImxpZ2h0IiwiZGFyayJdLG51bGwsdHJ1ZSx0cnVlKTwvc2NyaXB0PjxkaXYgY2xhc3M9ImZsZXggZmxleC1yb3cgbWluLWgtWzEwMGR2aF0gc3VwcG9ydHMtW2hlaWdodDoxMDBkdmhdOm1pbi1oLWR2aCB3LWZ1bGwiPjxkaXYgY2xhc3M9IjEwMGR2aCB3LWZ1bGwgZmxleCBpdGVtcy1jZW50ZXIganVzdGlmeS1jZW50ZXIiPjxkaXYgY2xhc3M9ImZsZXggZmxleC1jb2wgaXRlbXMtY2VudGVyIGp1c3RpZnktY2VudGVyIHNwYWNlLXktMiI+PHNwYW4gY2xhc3M9InJlbGF0aXZlIGZsZXggaC0xMCB3LTEwIHNocmluay0wIG92ZXJmbG93LWhpZGRlbiByb3VuZGVkLWZ1bGwgbXQtMiI+PC9zcGFuPjxkaXYgY2xhc3M9InRleHQtbXV0ZWQtZm9yZWdyb3VuZCI+IExvYWRpbmcuLi48L2Rpdj48ZGl2IGNsYXNzPSJ0ZXh0LW11dGVkLWZvcmVncm91bmQiPkxvYWRpbmcgc3RvcmVkIGNvbm5lY3Rpb25zIGNvbmZpZ3M8L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj48L2Rpdj48c2VjdGlvbiBhcmlhLWxhYmVsPSJOb3RpZmljYXRpb25zIGFsdCtUIiB0YWJpbmRleD0iLTEiIGFyaWEtbGl2ZT0icG9saXRlIiBhcmlhLXJlbGV2YW50PSJhZGRpdGlvbnMgdGV4dCIgYXJpYS1hdG9taWM9ImZhbHNlIj48L3NlY3Rpb24+PC9tYWluPjxzY3JpcHQgc3JjPSIvX25leHQvc3RhdGljL2NodW5rcy93ZWJwYWNrLTlhNjhmMDE5NmEwNjBlYjAuanMiIGFzeW5jPSIiPjwvc2NyaXB0PjxzY3JpcHQ+KHNlbGYuX19uZXh0X2Y9c2VsZi5fX25leHRfZnx8W10pLnB1c2goWzBdKTwvc2NyaXB0PjxzY3JpcHQ+c2VsZi5fX25leHRfZi5wdXNoKFsxLCIxOlwiJFNyZWFjdC5mcmFnbWVudFwiXG4yOklbMzc3MzAsW10sXCJDbGllbnRTZWdtZW50Um9vdFwiXVxuMzpJWzMwMDQzLFtcIjM0NlwiLFwic3RhdGljL2NodW5rcy8yNzNhY2RjMC0wNzQ2NTFjNmRjY2U1N2FhLmpzXCIsXCI2OTZcIixcInN0YXRpYy9jaHVua3MvNjk2LWI2MzZmZTM0Yjc5YTRlMDAuanNcIixcIjI5XCIsXCJzdGF0aWMvY2h1bmtzLzI5LWY2YWVkZWIwNDEzM2RhN2YuanNcIixcIjE3N1wiLFwic3RhdGljL2NodW5rcy9hcHAvbGF5b3V0LWEyMjdiN2M4ODRkNzU0ZWIuanNcIl0sXCJkZWZhdWx0XCJdXG40OklbNDYxNTUsW10sXCJcIl1cbjU6SVs3MjQ3MSxbXSxcIlwiXVxuNzpJWzQyNzkwLFtdLFwiQ2xpZW50UGFnZVJvb3RcIl1cbjg6SVszNTIwOSxbXCIzNDZcIixcInN0YXRpYy9jaHVua3MvMjczYWNkYzAtMDc0NjUxYzZkY2NlNTdhYS5qc1wiLFwiNjU1XCIsXCJzdGF0aWMvY2h1bmtzLzZiYjgwODA2LWE1ZDYwOGFkMzkwMjBlZWUuanNcIixcIjgyMlwiLFwic3RhdGljL2NodW5rcy85M2JmYTg4Zi1jYzM5MWYzMjA4MDAzOTM1LmpzXCIsXCI0NDdcIixcInN0YXRpYy9jaHVua3MvNzUxNDZkN2QtMDlhYTFhMjdlZjhmZmQ4NS5qc1wiLFwiOTIxXCIsXCJzdGF0aWMvY2h1bmtzLzNjMWM5ZjRhLTEzOTQxYTk5NzAzMTk0YWQuanNcIixcIjIyNFwiLFwic3RhdGljL2NodW5rcy8zMmZhMzg1NS0yMjljNzBhOWY5MTcyOWQxLmpzXCIsXCI5OTNcIixcInN0YXRpYy9jaHVua3MvMGI0MmNlNzMtOGE0NmE0NDYzODU2ZWNiOC5qc1wiLFwiNjk2XCIsXCJzdGF0aWMvY2h1bmtzLzY5Ni1iNjM2ZmUzNGI3OWE0ZTAwLmpzXCIsXCI4NzJcIixcInN0YXRpYy9jaHVua3MvODcyLTZlNjI3ZTZhMjkzZWQzOTYuanNcIixcIjI5XCIsXCJzdGF0aWMvY2h1bmtzLzI5LWY2YWVkZWIwNDEzM2RhN2YuanNcIixcIjk3NFwiLFwic3RhdGljL2NodW5rcy9hcHAvcGFnZS0yYTFmYmEwNjVkZTM5Y2I4LmpzXCJdLFwiZGVmYXVsdFwiXVxuYjpJWzMxNzcsW10sXCJPdXRsZXRCb3VuZGFyeVwiXVxuZTpJWzMxNzcsW10sXCJWaWV3cG9ydEJvdW5kYXJ5XCJdXG4xMDpJWzMxNzcsW10sXCJNZXRhZGF0YUJvdW5kYXJ5XCJdXG4xMjpJWzk0OTkwLFtdLFwiXCJdXG46SExbXCIvX25leHQvc3RhdGljL2Nzcy8yMWJiODQyNWJkNzE0NmNjLmNzc1wiLFwic3R5bGVcIl1cbjpITFtcIi9fbmV4dC9zdGF0aWMvbWVkaWEvNzMyM2I5ZDA4NzMwNmFkYi1zLnAud29mZjJcIixcImZvbnRcIix7XCJjcm9zc09yaWdpblwiOlwiXCIsXCJ0eXBlXCI6XCJmb250L3dvZmYyXCJ9XVxuOkhMW1wiL19uZXh0L3N0YXRpYy9jc3MvNjBiMmUxMGQwYzUxZGMzMC5jc3NcIixcInN0eWxlXCJdXG4iXSk8L3NjcmlwdD48c2NyaXB0PnNlbGYuX19uZXh0X2YucHVzaChbMSwiMDp7XCJQXCI6bnVsbCxcImJcIjpcIk1CbzFhQVgxX1QwMUQxMWt0X0ZKZVwiLFwicFwiOlwiXCIsXCJjXCI6W1wiXCIsXCJcIl0sXCJpXCI6ZmFsc2UsXCJmXCI6W1tbXCJcIix7XCJjaGlsZHJlblwiOltcIl9fUEFHRV9fXCIse31dfSxcIiR1bmRlZmluZWRcIixcIiR1bmRlZmluZWRcIix0cnVlXSxbXCJcIixbXCIkXCIsXCIkMVwiLFwiY1wiLHtcImNoaWxkcmVuXCI6W1tbXCIkXCIsXCJsaW5rXCIsXCIwXCIse1wicmVsXCI6XCJzdHlsZXNoZWV0XCIsXCJocmVmXCI6XCIvX25leHQvc3RhdGljL2Nzcy8yMWJiODQyNWJkNzE0NmNjLmNzc1wiLFwicHJlY2VkZW5jZVwiOlwibmV4dFwiLFwiY3Jvc3NPcmlnaW5cIjpcIiR1bmRlZmluZWRcIixcIm5vbmNlXCI6XCIkdW5kZWZpbmVkXCJ9XV0sW1wiJFwiLFwiJEwyXCIsbnVsbCx7XCJDb21wb25lbnRcIjpcIiQzXCIsXCJzbG90c1wiOntcImNoaWxkcmVuXCI6W1wiJFwiLFwiJEw0XCIsbnVsbCx7XCJwYXJhbGxlbFJvdXRlcktleVwiOlwiY2hpbGRyZW5cIixcImVycm9yXCI6XCIkdW5kZWZpbmVkXCIsXCJlcnJvclN0eWxlc1wiOlwiJHVuZGVmaW5lZFwiLFwiZXJyb3JTY3JpcHRzXCI6XCIkdW5kZWZpbmVkXCIsXCJ0ZW1wbGF0ZVwiOltcIiRcIixcIiRMNVwiLG51bGwse31dLFwidGVtcGxhdGVTdHlsZXNcIjpcIiR1bmRlZmluZWRcIixcInRlbXBsYXRlU2NyaXB0c1wiOlwiJHVuZGVmaW5lZFwiLFwibm90Rm91bmRcIjpbW1tcIiRcIixcInRpdGxlXCIsbnVsbCx7XCJjaGlsZHJlblwiOlwiNDA0OiBUaGlzIHBhZ2UgY291bGQgbm90IGJlIGZvdW5kLlwifV0sW1wiJFwiLFwiZGl2XCIsbnVsbCx7XCJzdHlsZVwiOntcImZvbnRGYW1pbHlcIjpcInN5c3RlbS11aSxcXFwiU2Vnb2UgVUlcXFwiLFJvYm90byxIZWx2ZXRpY2EsQXJpYWwsc2Fucy1zZXJpZixcXFwiQXBwbGUgQ29sb3IgRW1vamlcXFwiLFxcXCJTZWdvZSBVSSBFbW9qaVxcXCJcIixcImhlaWdodFwiOlwiMTAwdmhcIixcInRleHRBbGlnblwiOlwiY2VudGVyXCIsXCJkaXNwbGF5XCI6XCJmbGV4XCIsXCJmbGV4RGlyZWN0aW9uXCI6XCJjb2x1bW5cIixcImFsaWduSXRlbXNcIj" 8 | "pcImNlbnRlclwiLFwianVzdGlmeUNvbnRlbnRcIjpcImNlbnRlclwifSxcImNoaWxkcmVuXCI6W1wiJFwiLFwiZGl2XCIsbnVsbCx7XCJjaGlsZHJlblwiOltbXCIkXCIsXCJzdHlsZVwiLG51bGwse1wiZGFuZ2Vyb3VzbHlTZXRJbm5lckhUTUxcIjp7XCJfX2h0bWxcIjpcImJvZHl7Y29sb3I6IzAwMDtiYWNrZ3JvdW5kOiNmZmY7bWFyZ2luOjB9Lm5leHQtZXJyb3ItaDF7Ym9yZGVyLXJpZ2h0OjFweCBzb2xpZCByZ2JhKDAsMCwwLC4zKX1AbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOmRhcmspe2JvZHl7Y29sb3I6I2ZmZjtiYWNrZ3JvdW5kOiMwMDB9Lm5leHQtZXJyb3ItaDF7Ym9yZGVyLXJpZ2h0OjFweCBzb2xpZCByZ2JhKDI1NSwyNTUsMjU1LC4zKX19XCJ9fV0sW1wiJFwiLFwiaDFcIixudWxsLHtcImNsYXNzTmFtZVwiOlwibmV4dC1lcnJvci1oMVwiLFwic3R5bGVcIjp7XCJkaXNwbGF5XCI6XCJpbmxpbmUtYmxvY2tcIixcIm1hcmdpblwiOlwiMCAyMHB4IDAgMFwiLFwicGFkZGluZ1wiOlwiMCAyM3B4IDAgMFwiLFwiZm9udFNpemVcIjoyNCxcImZvbnRXZWlnaHRcIjo1MDAsXCJ2ZXJ0aWNhbEFsaWduXCI6XCJ0b3BcIixcImxpbmVIZWlnaHRcIjpcIjQ5cHhcIn0sXCJjaGlsZHJlblwiOjQwNH1dLFtcIiRcIixcImRpdlwiLG51bGwse1wic3R5bGVcIjp7XCJkaXNwbGF5XCI6XCJpbmxpbmUtYmxvY2tcIn0sXCJjaGlsZHJlblwiOltcIiRcIixcImgyXCIsbnVsbCx7XCJzdHlsZVwiOntcImZvbnRTaXplXCI6MTQsXCJmb250V2VpZ2h0XCI6NDAwLFwibGluZUhlaWdodFwiOlwiNDlweFwiLFwibWFyZ2luXCI6MH0sXCJjaGlsZHJlblwiOlwiVGhpcyBwYWdlIGNvdWxkIG5vdCBiZSBmb3VuZC5cIn1dfV1dfV19XV0sW11dLFwiZm9yYmlkZGVuXCI6XCIkdW5kZWZpbmVkXCIsXCJ1bmF1dGhvcml6ZWRcIjpcIiR1bmRlZmluZWRcIn1dfSxcInBhcmFtc1wiOnt9LFwicHJvbWlzZVwiOlwiJEA2XCJ9XV19XSx7XCJjaGlsZHJlblwiOltcIl9fUEFHRV9fXCIsW1wiJFwiLFwiJDFcIixcImNcIix7XCJjaGlsZHJlblwiOltbXCIkXCIsXCIkTDdcIixudWxsLHtcIkNvbXBvbmVudFwiOlwiJDhcIixcInNlYXJjaFBhcmFtc1wiOnt9LFwicGFyYW1zXCI6XCIkMDpmOjA6MToxOnByb3BzOmNoaWxkcmVuOjE6cHJvcHM6cGFyYW1zXCIsXCJwcm9taXNlc1wiOltcIiRAOVwiLFwiJEBhXCJdfV0sXCIkdW5kZWZpbmVkXCIsW1tcIiRcIixcImxpbmtcIixcIjBcIix7XCJyZWxcIjpcInN0eWxlc2hlZXRcIixcImhyZWZcIjpcIi9fbmV4dC9zdGF0aWMvY3NzLzYwYjJlMTBkMGM1MWRjMzAuY3NzXCIsXCJwcmVjZWRlbmNlXCI6XCJuZXh0XCIsXCJjcm9zc09yaWdpblwiOlwiJHVuZGVmaW5lZFwiLFwibm9uY2VcIjpcIiR1bmRlZmluZWRcIn1dXSxbXCIkXCIsXCIkTGJcIixudWxsLHtcImNoaWxkcmVuXCI6W1wiJExjXCIsXCIkTGRcIixudWxsXX1dXX1dLHt9LG51bGwsZmFsc2VdfSxudWxsLGZhbHNlXSxbXCIkXCIsXCIkMVwiLFwiaFwiLHtcImNoaWxkcmVuXCI6W251bGwsW1wiJFwiLFwiJDFcIixcImFSRUwzbmh3UDQxUFIzeGZOQ2pKT1wiLHtcImNoaWxkcmVuXCI6W1tcIiRcIixcIiRMZVwiLG51bGwse1wiY2hpbGRyZW5cIjpcIiRMZlwifV0sW1wiJFwiLFwibWV0YVwiLG51bGwse1wibmFtZVwiOlwibmV4dC1zaXplLWFkanVzdFwiLFwiY29udGVudFwiOlwiXCJ9XV19XSxbXCIkXCIsXCIkTDEwXCIsbnVsbCx7XCJjaGlsZHJlblwiOlwiJEwxMVwifV1dfV0sZmFsc2VdXSxcIm1cIjpcIiR1bmRlZmluZWRcIixcIkdcIjpbXCIkMTJcIixcIiR1bmRlZmluZWRcIl0sXCJzXCI6ZmFsc2UsXCJTXCI6dHJ1ZX1cbiJdKTwvc2NyaXB0PjxzY3JpcHQ+c2VsZi5fX25leHRfZi5wdXNoKFsxLCI2Ont9XG45Ont9XG5hOnt9XG4iXSk8L3NjcmlwdD48c2NyaXB0PnNlbGYuX19uZXh0X2YucHVzaChbMSwiZjpbW1wiJFwiLFwibWV0YVwiLFwiMFwiLHtcImNoYXJTZXRcIjpcInV0Zi04XCJ9XSxbXCIkXCIsXCJtZXRhXCIsXCIxXCIse1wibmFtZVwiOlwidmlld3BvcnRcIixcImNvbnRlbnRcIjpcIndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xXCJ9XV1cbmM6bnVsbFxuIl0pPC9zY3JpcHQ+PHNjcmlwdD5zZWxmLl9fbmV4dF9mLnB1c2goWzEsImQ6bnVsbFxuMTE6W1tcIiRcIixcImxpbmtcIixcIjBcIix7XCJyZWxcIjpcIm1hbmlmZXN0XCIsXCJocmVmXCI6XCIvbWFuaWZlc3QuanNvblwiLFwiY3Jvc3NPcmlnaW5cIjpcIiR1bmRlZmluZWRcIn1dLFtcIiRcIixcImxpbmtcIixcIjFcIix7XCJyZWxcIjpcImljb25cIixcImhyZWZcIjpcIi9mYXZpY29uLmljb1wiLFwidHlwZVwiOlwiaW1hZ2UveC1pY29uXCIsXCJzaXplc1wiOlwiNDh4NDhcIn1dLFtcIiRcIixcImxpbmtcIixcIjJcIix7XCJyZWxcIjpcImljb25cIixcImhyZWZcIjpcIi9pY29uLnBuZz9mNDUyMWE1OWRmMGQ4ZjBiXCIsXCJ0eXBlXCI6XCJpbWFnZS9wbmdcIixcInNpemVzXCI6XCI5Nng5NlwifV0sW1wiJFwiLFwibGlua1wiLFwiM1wiLHtcInJlbFwiOlwiYXBwbGUtdG91Y2gtaWNvblwiLFwiaHJlZlwiOlwiL2FwcGxlLWljb24ucG5nP2U0N2M0MmQ5OWYyZDI0NGZcIixcInR5cGVcIjpcImltYWdlL3BuZ1wiLFwic2l6ZXNcIjpcIjE4MHgxODBcIn1dXVxuIl0pPC9zY3JpcHQ+PC9ib2R5PjwvaHRtbD4=", // 9 | 11736, // 10 | "text/html", // 11 | "/index.html/", // 12 | 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /scripts/build_ui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import tarfile 4 | import zipfile 5 | import urllib.request 6 | import subprocess 7 | import shutil 8 | import sys 9 | import glob 10 | import base64 11 | 12 | def is_npm_installed(): 13 | """ 14 | Check if 'npm' is on PATH. 15 | Returns True if found, False otherwise. 16 | """ 17 | return shutil.which("npm") is not None 18 | 19 | def install_node_and_npm( 20 | version="v18.14.2", 21 | download_dir="node_download", 22 | install_dir="node_install" 23 | ): 24 | """ 25 | Downloads and unpacks a portable Node.js + npm into `install_dir`, 26 | returning (node_path, npm_path). 27 | 28 | - Works on Windows, macOS (darwin), and Linux (glibc or musl/Alpine). 29 | - No shell commands are needed for extracting the archive (pure Python). 30 | - If your system is Alpine Linux (musl), it will attempt to download the 31 | 'linux-musl-ARCH' build, which is available starting from Node 16+. 32 | 33 | Usage example: 34 | node_path, npm_path = install_node_and_npm( 35 | version="v18.14.2", 36 | download_dir="node_download", 37 | install_dir="node_install" 38 | ) 39 | """ 40 | system = platform.system().lower() # e.g. "windows", "linux", "darwin" 41 | machine = platform.machine().lower() # e.g. "x86_64", "amd64", "arm64", etc. 42 | 43 | # Detect CPU arch for Node's naming: 44 | if machine in ("x86_64", "amd64"): 45 | arch = "x64" 46 | elif "arm64" in machine or "aarch64" in machine: 47 | arch = "arm64" 48 | elif "arm" in machine: 49 | # Node has multiple ARM variants (armv6l, armv7l, etc.). 50 | # Adjust as needed if you need a specific one. 51 | arch = "armv7l" 52 | else: 53 | raise RuntimeError(f"Unsupported CPU architecture: {machine}") 54 | 55 | # Check if we’re on musl (Alpine) or glibc if Linux: 56 | is_musl = False 57 | if system == "linux": 58 | try: 59 | output = subprocess.check_output(["ldd", "--version"], stderr=subprocess.STDOUT, text=True) 60 | if "musl" in output.lower(): 61 | is_musl = True 62 | except Exception: 63 | pass # If we can't run ldd or parse output, assume glibc 64 | 65 | base_url = f"https://nodejs.org/dist/{version}" 66 | 67 | # Determine which archive name to fetch: 68 | if system == "windows": 69 | # Node for Windows is a .zip 70 | archive_name = f"node-{version}-win-{arch}.zip" 71 | 72 | elif system == "darwin": 73 | # Node for macOS 74 | archive_name = f"node-{version}-darwin-{arch}.tar.gz" 75 | 76 | else: 77 | # system == "linux" 78 | if is_musl: 79 | # Alpine-based (musl) 80 | # Node official builds for musl exist starting from Node 16+. 81 | # e.g. "node-v18.14.2-linux-musl-x64.tar.gz" 82 | archive_name = f"node-{version}-linux-musl-{arch}.tar.gz" 83 | else: 84 | # glibc-based Linux 85 | archive_name = f"node-{version}-linux-{arch}.tar.gz" 86 | 87 | download_url = f"{base_url}/{archive_name}" 88 | print(f"Downloading Node.js from: {download_url}") 89 | local_archive = os.path.join(download_dir, archive_name) 90 | 91 | # Create directories if needed 92 | os.makedirs(download_dir, exist_ok=True) 93 | os.makedirs(install_dir, exist_ok=True) 94 | 95 | # 1) Download the archive 96 | print(f"Downloading {download_url} ...") 97 | with urllib.request.urlopen(download_url) as response: 98 | data = response.read() 99 | 100 | # 2) Save archive locally 101 | with open(local_archive, "wb") as f: 102 | f.write(data) 103 | 104 | # 3) Extract into install_dir 105 | print(f"Extracting {archive_name} ...") 106 | if system == "windows": 107 | with zipfile.ZipFile(local_archive, "r") as zf: 108 | zf.extractall(install_dir) 109 | 110 | # Windows folder name (e.g. "node-v18.14.2-win-x64") 111 | extracted_folder_name = f"node-{version}-win-{arch}" 112 | node_executable = os.path.join(install_dir, extracted_folder_name, "node.exe") 113 | npm_executable = os.path.join(install_dir, extracted_folder_name, "npm.cmd") 114 | 115 | else: 116 | # macOS or Linux 117 | with tarfile.open(local_archive, "r:gz") as tf: 118 | tf.extractall(path=install_dir) 119 | 120 | # Example: node-v18.14.2-linux-x64, node-v18.14.2-linux-musl-x64, node-v18.14.2-darwin-x64 121 | subdir_system = "linux-musl" if (is_musl and system == "linux") else system 122 | extracted_folder_name = f"node-{version}-{subdir_system}-{arch}" 123 | 124 | node_executable = os.path.join(install_dir, extracted_folder_name, "bin", "node") 125 | npm_executable = os.path.join(install_dir, extracted_folder_name, "bin", "npm") 126 | 127 | # 4) Validate 128 | if not os.path.exists(node_executable): 129 | raise RuntimeError(f"Node binary not found at {node_executable}") 130 | if not os.path.exists(npm_executable): 131 | raise RuntimeError(f"npm binary not found at {npm_executable}") 132 | 133 | print(f"Node installed to: {node_executable}") 134 | print(f"npm installed to: {npm_executable}") 135 | 136 | return node_executable, npm_executable 137 | 138 | 139 | def check_command_version(command, path): 140 | try: 141 | print(f"Checking {command} version at {path}") 142 | subprocess.run([path, "--version"], check=True) 143 | except subprocess.CalledProcessError as e: 144 | raise RuntimeError(f"Failed to check {command} version at {path}. Error: {e}") 145 | except FileNotFoundError: 146 | raise RuntimeError(f"{command} not found at {path}") 147 | 148 | def install_tools(): 149 | # 1) Check if npm is already installed 150 | if is_npm_installed(): 151 | print("npm is already installed on the system; no installation needed.") 152 | return 153 | 154 | # 2) Otherwise, download and unpack Node (which includes npm) 155 | node_path, npm_path = install_node_and_npm( 156 | version="v18.14.2", # Or choose a different Node version 157 | download_dir="node_dl", # Scratch folder for the archive 158 | install_dir="node_install" # Final install location 159 | ) 160 | 161 | # check if npm is installed 162 | print("Checking Node.js version at", node_path) 163 | check_command_version("Node.js", node_path) 164 | 165 | print("Checking npm version at", npm_path) 166 | check_command_version("npm", npm_path) 167 | 168 | # add npm to PATH 169 | os.environ["PATH"] += os.pathsep + os.path.dirname(npm_path) 170 | 171 | # check if npm is installed 172 | if not is_npm_installed(): 173 | raise RuntimeError("npm is not installed or not on PATH") 174 | 175 | 176 | 177 | # test 178 | def build_ui(): 179 | # go to the dash-ui directory 180 | dash_ui_dir = os.path.join(os.path.dirname(__file__), "..", "dash-ui") 181 | # check if the dash-ui directory exists 182 | if not os.path.exists(dash_ui_dir): 183 | print("dash-ui directory not found at", dash_ui_dir) 184 | print("Please clone the dash-ui repository in the root directory of the extension repository.") 185 | sys.exit(1) 186 | 187 | # change the current working directory to dash-ui 188 | os.chdir(dash_ui_dir) 189 | 190 | # check if npm is installed 191 | if not shutil.which("npm"): 192 | print("npm not found. Please install npm.") 193 | sys.exit(1) 194 | 195 | # check if pnpm is installed, if not install it 196 | if not shutil.which("pnpm"): 197 | print("pnpm not found. Installing pnpm...") 198 | os.system("npm install -g pnpm") 199 | 200 | # install dependencies 201 | print("Installing dependencies...") 202 | os.system("pnpm install") 203 | 204 | # build the UI 205 | print("Building UI...") 206 | os.system("pnpm run build") 207 | 208 | root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 209 | target_file = os.path.join(root_dir, "src", "gen", "files.cpp") 210 | 211 | CONTENT_TEMPLATE = """ 212 | #pragma once 213 | #include "files.hpp" 214 | namespace duckdb { 215 | const File %var_name% = { 216 | // Content 217 | %content%, // 218 | %content_length%, // 219 | "%content_type%", // 220 | "%path%", // 221 | 222 | }; 223 | } 224 | """ 225 | 226 | FILE_TEMPLATE = """ 227 | #include "files.hpp" 228 | 229 | #include "duckdb/common/optional_ptr.hpp" 230 | #include "duckdb/common/string_util.hpp" 231 | 232 | %include% 233 | 234 | #include 235 | 236 | namespace duckdb { 237 | std::vector files = { 238 | %content% 239 | }; 240 | 241 | optional_ptr GetFile(Path path, const bool try_resolve_404) { 242 | if (path.empty() || path == "/") { 243 | path = "index.html"; 244 | } 245 | 246 | // Append / before and after 247 | path = "/" + path + "/"; 248 | path = StringUtil::Replace(path, "//", "/"); 249 | 250 | for (auto &file : files) { 251 | if (file.path == path) { 252 | return file; 253 | } 254 | } 255 | 256 | if (try_resolve_404) { 257 | GetFile("/404.html/", false); 258 | } 259 | 260 | return nullptr; 261 | } 262 | 263 | } 264 | """ 265 | 266 | 267 | def normalize_path(path: str) -> str: 268 | path = f"/{path}/" 269 | return path.replace("//", "/") 270 | 271 | def get_content_type(file: str) -> str: 272 | content_types = { 273 | "html": "text/html", 274 | "css": "text/css", 275 | "js": "application/javascript", 276 | "png": "image/png", 277 | "jpg": "image/jpeg", 278 | "ico": "image/x-icon", 279 | "woff": "font/woff", 280 | "woff2": "font/woff2", 281 | "ttf": "font/ttf", 282 | "svg": "image/svg+xml", 283 | "json": "application/json", 284 | "txt": "text/plain", 285 | } 286 | extension = file.split(".")[-1] 287 | return content_types[extension] 288 | 289 | 290 | def generate_ui_files(): 291 | base_path = os.path.join(root_dir, "dash-ui", "out") 292 | target_dir = os.path.dirname(target_file) 293 | if os.path.exists(target_dir): 294 | shutil.rmtree(target_dir) 295 | os.makedirs(target_dir) 296 | 297 | if not os.path.exists(base_path): 298 | raise Exception(f"Path {base_path} does not exist. Build the UI first.") 299 | 300 | files = glob.glob(os.path.join(base_path, "**", "*"), recursive=True) 301 | 302 | content_list: list[str] = [] 303 | 304 | for file in files: 305 | if not os.path.isfile(file): 306 | continue 307 | content = open( 308 | file, 309 | "rb", 310 | ).read() 311 | 312 | # Convert to Base64 313 | encoded_content = base64.b64encode(content).decode("utf-8") 314 | 315 | # Goal: "base64 encoded content chunk" "base64 encoded content chunk" ... 316 | # we need to stay below the sting length limit of 65535 317 | 318 | CHUNK_SIZE = 16380 // 2 319 | chunks = [encoded_content[i : i + CHUNK_SIZE] for i in range(0, len(encoded_content), CHUNK_SIZE)] 320 | chunk_strings = [f'"{chunk}"' for chunk in chunks] 321 | 322 | transformed_content = "\n ".join(chunk_strings) 323 | 324 | 325 | path = normalize_path(file.replace(base_path, "")) 326 | 327 | # on windows: replace \ with \\ to avoid escape sequences 328 | path_as_name = "file" + path.replace("/", "_").replace(".", "_").replace("-", "_").replace("__", "_").replace("\\", "\\\\") 329 | # if ends with _ remove it 330 | if path_as_name[-1] == "_": 331 | path_as_name = path_as_name[:-1] 332 | 333 | final_path = target_dir + "/" + path_as_name + ".hpp" 334 | 335 | templated = ( 336 | CONTENT_TEMPLATE.replace("%path%", path) 337 | .replace("%content%", transformed_content) 338 | .replace("%content_length%", str(len(encoded_content))) 339 | .replace("%content_type%", get_content_type(file)) 340 | .replace("%var_name%", path_as_name.upper()) 341 | ) 342 | 343 | # create path on the way to the file 344 | os.makedirs(os.path.dirname(final_path), exist_ok=True) 345 | 346 | with open(final_path, "w") as f: 347 | f.write(templated) 348 | content_list.append(path_as_name) 349 | 350 | content = ( 351 | FILE_TEMPLATE.replace("%content%", ",".join(content_list).upper()) 352 | .lstrip() 353 | .replace("%include%", "\n".join([f'#include "{f}.hpp"' for f in content_list])) 354 | ) 355 | 356 | with open(target_file, "w") as f: 357 | f.write(content) 358 | 359 | 360 | def main(): 361 | print("*** Building UI ***") 362 | install_tools() 363 | build_ui() 364 | generate_ui_files() 365 | print("*** Done ***") 366 | 367 | 368 | print("Building UI...") 369 | main() -------------------------------------------------------------------------------- /src/gen/file_next_static_media_7323b9d087306adb_s_p_woff2.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_MEDIA_7323B9D087306ADB_S_P_WOFF2 = { 6 | // Content 7 | "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" 8 | "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", // 9 | 13144, // 10 | "font/woff2", // 11 | "/_next/static/media/7323b9d087306adb-s.p.woff2/", // 12 | 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /e2e_tests/responses/all_types_compact.py: -------------------------------------------------------------------------------- 1 | ALL_TYPES_COMPACT = { 2 | "meta": [ 3 | {"name": "bool", "type": "BOOLEAN"}, 4 | {"name": "tinyint", "type": "TINYINT"}, 5 | {"name": "smallint", "type": "SMALLINT"}, 6 | {"name": "int", "type": "INTEGER"}, 7 | {"name": "bigint", "type": "BIGINT"}, 8 | {"name": "hugeint", "type": "HUGEINT"}, 9 | {"name": "uhugeint", "type": "UHUGEINT"}, 10 | {"name": "utinyint", "type": "UTINYINT"}, 11 | {"name": "usmallint", "type": "USMALLINT"}, 12 | {"name": "uint", "type": "UINTEGER"}, 13 | {"name": "ubigint", "type": "UBIGINT"}, 14 | {"name": "varint", "type": "VARINT"}, 15 | {"name": "date", "type": "DATE"}, 16 | {"name": "time", "type": "TIME"}, 17 | {"name": "timestamp", "type": "TIMESTAMP"}, 18 | {"name": "timestamp_s", "type": "TIMESTAMP_S"}, 19 | {"name": "timestamp_ms", "type": "TIMESTAMP_MS"}, 20 | {"name": "timestamp_ns", "type": "TIMESTAMP_NS"}, 21 | {"name": "time_tz", "type": "TIME WITH TIME ZONE"}, 22 | {"name": "timestamp_tz", "type": "TIMESTAMP WITH TIME ZONE"}, 23 | {"name": "float", "type": "FLOAT"}, 24 | {"name": "double", "type": "DOUBLE"}, 25 | {"name": "dec_4_1", "type": "DECIMAL(4,1)"}, 26 | {"name": "dec_9_4", "type": "DECIMAL(9,4)"}, 27 | {"name": "dec_18_6", "type": "DECIMAL(18,6)"}, 28 | {"name": "dec38_10", "type": "DECIMAL(38,10)"}, 29 | {"name": "uuid", "type": "UUID"}, 30 | {"name": "interval", "type": "INTERVAL"}, 31 | {"name": "varchar", "type": "VARCHAR"}, 32 | {"name": "blob", "type": "BLOB"}, 33 | {"name": "bit", "type": "BIT"}, 34 | {"name": "small_enum", "type": "ENUM('DUCK_DUCK_ENUM', 'GOOSE')"}, 35 | { 36 | "name": "medium_enum", 37 | "type": "ENUM('enum_0', 'enum_1', 'enum_2', 'enum_3', 'enum_4', 'enum_5', 'enum_6', 'enum_7', 'enum_8', 'enum_9', 'enum_10', 'enum_11', 'enum_12', 'enum_13', 'enum_14', 'enum_15', 'enum_16', 'enum_17', 'enum_18', 'enum_19', 'enum_20', 'enum_21', 'enum_22', 'enum_23', 'enum_24', 'enum_25', 'enum_26', 'enum_27', 'enum_28', 'enum_29', 'enum_30', 'enum_31', 'enum_32', 'enum_33', 'enum_34', 'enum_35', 'enum_36', 'enum_37', 'enum_38', 'enum_39', 'enum_40', 'enum_41', 'enum_42', 'enum_43', 'enum_44', 'enum_45', 'enum_46', 'enum_47', 'enum_48', 'enum_49', 'enum_50', 'enum_51', 'enum_52', 'enum_53', 'enum_54', 'enum_55', 'enum_56', 'enum_57', 'enum_58', 'enum_59', 'enum_60', 'enum_61', 'enum_62', 'enum_63', 'enum_64', 'enum_65', 'enum_66', 'enum_67', 'enum_68', 'enum_69', 'enum_70', 'enum_71', 'enum_72', 'enum_73', 'enum_74', 'enum_75', 'enum_76', 'enum_77', 'enum_78', 'enum_79', 'enum_80', 'enum_81', 'enum_82', 'enum_83', 'enum_84', 'enum_85', 'enum_86', 'enum_87', 'enum_88', 'enum_89', 'enum_90', 'enum_91', 'enum_92', 'enum_93', 'enum_94', 'enum_95', 'enum_96', 'enum_97', 'enum_98', 'enum_99', 'enum_100', 'enum_101', 'enum_102', 'enum_103', 'enum_104', 'enum_105', 'enum_106', 'enum_107', 'enum_108', 'enum_109', 'enum_110', 'enum_111', 'enum_112', 'enum_113', 'enum_114', 'enum_115', 'enum_116', 'enum_117', 'enum_118', 'enum_119', 'enum_120', 'enum_121', 'enum_122', 'enum_123', 'enum_124', 'enum_125', 'enum_126', 'enum_127', 'enum_128', 'enum_129', 'enum_130', 'enum_131', 'enum_132', 'enum_133', 'enum_134', 'enum_135', 'enum_136', 'enum_137', 'enum_138', 'enum_139', 'enum_140', 'enum_141', 'enum_142', 'enum_143', 'enum_144', 'enum_145', 'enum_146', 'enum_147', 'enum_148', 'enum_149', 'enum_150', 'enum_151', 'enum_152', 'enum_153', 'enum_154', 'enum_155', 'enum_156', 'enum_157', 'enum_158', 'enum_159', 'enum_160', 'enum_161', 'enum_162', 'enum_163', 'enum_164', 'enum_165', 'enum_166', 'enum_167', 'enum_168', 'enum_169', 'enum_170', 'enum_171', 'enum_172', 'enum_173', 'enum_174', 'enum_175', 'enum_176', 'enum_177', 'enum_178', 'enum_179', 'enum_180', 'enum_181', 'enum_182', 'enum_183', 'enum_184', 'enum_185', 'enum_186', 'enum_187', 'enum_188', 'enum_189', 'enum_190', 'enum_191', 'enum_192', 'enum_193', 'enum_194', 'enum_195', 'enum_196', 'enum_197', 'enum_198', 'enum_199', 'enum_200', 'enum_201', 'enum_202', 'enum_203', 'enum_204', 'enum_205', 'enum_206', 'enum_207', 'enum_208', 'enum_209', 'enum_210', 'enum_211', 'enum_212', 'enum_213', 'enum_214', 'enum_215', 'enum_216', 'enum_217', 'enum_218', 'enum_219', 'enum_220', 'enum_221', 'enum_222', 'enum_223', 'enum_224', 'enum_225', 'enum_226', 'enum_227', 'enum_228', 'enum_229', 'enum_230', 'enum_231', 'enum_232', 'enum_233', 'enum_234', 'enum_235', 'enum_236', 'enum_237', 'enum_238', 'enum_239', 'enum_240', 'enum_241', 'enum_242', 'enum_243', 'enum_244', 'enum_245', 'enum_246', 'enum_247', 'enum_248', 'enum_249', 'enum_250', 'enum_251', 'enum_252', 'enum_253', 'enum_254', 'enum_255', 'enum_256', 'enum_257', 'enum_258', 'enum_259', 'enum_260', 'enum_261', 'enum_262', 'enum_263', 'enum_264', 'enum_265', 'enum_266', 'enum_267', 'enum_268', 'enum_269', 'enum_270', 'enum_271', 'enum_272', 'enum_273', 'enum_274', 'enum_275', 'enum_276', 'enum_277', 'enum_278', 'enum_279', 'enum_280', 'enum_281', 'enum_282', 'enum_283', 'enum_284', 'enum_285', 'enum_286', 'enum_287', 'enum_288', 'enum_289', 'enum_290', 'enum_291', 'enum_292', 'enum_293', 'enum_294', 'enum_295', 'enum_296', 'enum_297', 'enum_298', 'enum_299')", 38 | }, 39 | {"name": "large_enum", "type": "ENUM('enum_0', 'enum_69999')"}, 40 | {"name": "int_array", "type": "INTEGER[]"}, 41 | {"name": "double_array", "type": "DOUBLE[]"}, 42 | {"name": "date_array", "type": "DATE[]"}, 43 | {"name": "timestamp_array", "type": "TIMESTAMP[]"}, 44 | {"name": "timestamptz_array", "type": "TIMESTAMP WITH TIME ZONE[]"}, 45 | {"name": "varchar_array", "type": "VARCHAR[]"}, 46 | {"name": "nested_int_array", "type": "INTEGER[][]"}, 47 | {"name": "struct", "type": "STRUCT(a INTEGER, b VARCHAR)"}, 48 | {"name": "struct_of_arrays", "type": "STRUCT(a INTEGER[], b VARCHAR[])"}, 49 | {"name": "array_of_structs", "type": "STRUCT(a INTEGER, b VARCHAR)[]"}, 50 | {"name": "map", "type": "MAP(VARCHAR, VARCHAR)"}, 51 | {"name": "union", "type": 'UNION("name" VARCHAR, age SMALLINT)'}, 52 | {"name": "fixed_int_array", "type": "INTEGER[3]"}, 53 | {"name": "fixed_varchar_array", "type": "VARCHAR[3]"}, 54 | {"name": "fixed_nested_int_array", "type": "INTEGER[3][3]"}, 55 | {"name": "fixed_nested_varchar_array", "type": "VARCHAR[3][3]"}, 56 | {"name": "fixed_struct_array", "type": "STRUCT(a INTEGER, b VARCHAR)[3]"}, 57 | {"name": "struct_of_fixed_array", "type": "STRUCT(a INTEGER[3], b VARCHAR[3])"}, 58 | {"name": "fixed_array_of_int_list", "type": "INTEGER[][3]"}, 59 | {"name": "list_of_fixed_int_array", "type": "INTEGER[3][]"}, 60 | ], 61 | "data": [ 62 | [ 63 | False, 64 | -128, 65 | -32768, 66 | -2147483648, 67 | -9223372036854775808, 68 | "-170141183460469231731687303715884105728", 69 | "0", 70 | 0, 71 | 0, 72 | 0, 73 | 0, 74 | "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", 75 | "5877642-06-25 (BC)", 76 | "00:00:00", 77 | "290309-12-22 (BC) 00:00:00", 78 | "290309-12-22 (BC) 00:00:00", 79 | "290309-12-22 (BC) 00:00:00", 80 | "1677-09-22 00:00:00", 81 | "00:00:00+15:59:59", 82 | "290309-12-22 (BC) 00:00:00+00", 83 | -3.4028234663852886e38, 84 | -1.7976931348623157e308, 85 | -999.9, 86 | -99999.9999, 87 | -1000000000000.0, 88 | -1e28, 89 | "00000000-0000-0000-0000-000000000000", 90 | "00:00:00", 91 | "🦆🦆🦆🦆🦆🦆", 92 | "thisisalongblob\\x00withnullbytes", 93 | "0010001001011100010101011010111", 94 | "DUCK_DUCK_ENUM", 95 | "enum_0", 96 | "enum_0", 97 | [], 98 | [], 99 | [], 100 | [], 101 | [], 102 | [], 103 | [], 104 | {"a": None, "b": None}, 105 | {"a": None, "b": None}, 106 | [], 107 | {}, 108 | "Frank", 109 | [None, 2, 3], 110 | ["a", None, "c"], 111 | [[None, 2, 3], None, [None, 2, 3]], 112 | [["a", None, "c"], None, ["a", None, "c"]], 113 | [{"a": None, "b": None}, {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, {"a": None, "b": None}], 114 | {"a": [None, 2, 3], "b": ["a", None, "c"]}, 115 | [[], [42, 999, None, None, -42], []], 116 | [[None, 2, 3], [4, 5, 6], [None, 2, 3]], 117 | ], 118 | [ 119 | True, 120 | 127, 121 | 32767, 122 | 2147483647, 123 | 9223372036854775807, 124 | "170141183460469231731687303715884105727", 125 | "340282366920938463463374607431768211455", 126 | 255, 127 | 65535, 128 | 4294967295, 129 | 18446744073709551615, 130 | "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", 131 | "5881580-07-10", 132 | "24:00:00", 133 | "294247-01-10 04:00:54.775806", 134 | "294247-01-10 04:00:54", 135 | "294247-01-10 04:00:54.775", 136 | "2262-04-11 23:47:16.854775806", 137 | "24:00:00-15:59:59", 138 | "294247-01-10 04:00:54.775806+00", 139 | 3.4028234663852886e38, 140 | 1.7976931348623157e308, 141 | 999.9, 142 | 99999.9999, 143 | 1000000000000.0, 144 | 1e28, 145 | "ffffffff-ffff-ffff-ffff-ffffffffffff", 146 | "83 years 3 months 999 days 00:16:39.999999", 147 | "goo", 148 | "\\x00\\x00\\x00a", 149 | "10101", 150 | "GOOSE", 151 | "enum_299", 152 | "enum_69999", 153 | [42, 999, None, None, -42], 154 | [42.0, "nan", "inf", "-inf", None, -42.0], 155 | ["1970-01-01", "infinity", "-infinity", None, "2022-05-12"], 156 | ["1970-01-01 00:00:00", "infinity", "-infinity", None, "2022-05-12 16:23:45"], 157 | ["1970-01-01 00:00:00+00", "infinity", "-infinity", None, "2022-05-12 23:23:45+00"], 158 | ["🦆🦆🦆🦆🦆🦆", "goose", None, ""], 159 | [[], [42, 999, None, None, -42], None, [], [42, 999, None, None, -42]], 160 | {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, 161 | {"a": [42, 999, None, None, -42], "b": ["🦆🦆🦆🦆🦆🦆", "goose", None, ""]}, 162 | [{"a": None, "b": None}, {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, None], 163 | {"key1": "🦆🦆🦆🦆🦆🦆", "key2": "goose"}, 164 | 5, 165 | [4, 5, 6], 166 | ["d", "e", "f"], 167 | [[4, 5, 6], [None, 2, 3], [4, 5, 6]], 168 | [["d", "e", "f"], ["a", None, "c"], ["d", "e", "f"]], 169 | [{"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}, {"a": None, "b": None}, {"a": 42, "b": "🦆🦆🦆🦆🦆🦆"}], 170 | {"a": [4, 5, 6], "b": ["d", "e", "f"]}, 171 | [[42, 999, None, None, -42], [], [42, 999, None, None, -42]], 172 | [[4, 5, 6], [None, 2, 3], [4, 5, 6]], 173 | ], 174 | [ 175 | None, 176 | None, 177 | None, 178 | None, 179 | None, 180 | None, 181 | None, 182 | None, 183 | None, 184 | None, 185 | None, 186 | None, 187 | None, 188 | None, 189 | None, 190 | None, 191 | None, 192 | None, 193 | None, 194 | None, 195 | None, 196 | None, 197 | None, 198 | None, 199 | None, 200 | None, 201 | None, 202 | None, 203 | None, 204 | None, 205 | None, 206 | None, 207 | None, 208 | None, 209 | None, 210 | None, 211 | None, 212 | None, 213 | None, 214 | None, 215 | None, 216 | None, 217 | None, 218 | None, 219 | None, 220 | None, 221 | None, 222 | None, 223 | None, 224 | None, 225 | None, 226 | None, 227 | None, 228 | None, 229 | ], 230 | ], 231 | "statistics": { 232 | "rows": 3, 233 | }, 234 | } 235 | -------------------------------------------------------------------------------- /src/gen/file_next_static_chunks_902_02a4e3e7dd6de078_js.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "files.hpp" 4 | namespace duckdb { 5 | const File FILE_NEXT_STATIC_CHUNKS_902_02A4E3E7DD6DE078_JS = { 6 | // Content 7 | "KHNlbGYud2VicGFja0NodW5rX05fRT1zZWxmLndlYnBhY2tDaHVua19OX0V8fFtdKS5wdXNoKFtbOTAyXSx7NDQ1Njc6KCk9Pnt9LDg1OTAyOih0LGUsYSk9PnsidXNlIHN0cmljdCI7YS5yKGUpLGEuZChlLHtkZWZhdWx0OigpPT5HfSk7dmFyIG49YSg3NjQ5NSksaT1hKDYwOTY3KSxyPWEoMzI4MTMpLHM9YSg2NTU4NiksbD1hLm4ocyksbz1hKDkyMDA3KSxjPWEubihvKTthKDQ0NTY3KTt2YXIgZD1hKDcwNzkzKSx1PWEoMTA1NDgpLGg9YSg5MTg4NSk7ZnVuY3Rpb24gcCh0KXtyZXR1cm4oMCxuLmpzeCkoaC5ELHtlbWJlZGRlZDohMCxyZWxhdGlvblN0YXRlOnQucmVsYXRpb24saW5wdXRNYW5hZ2VyOnQuaW5wdXRNYW5hZ2VyLHVwZGF0ZVJlbGF0aW9uOnQub25SZWxhdGlvblVwZGF0ZX0pfXZhciB3PWEoODgxNDkpO2xldCBnPSc8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtc2hlZXQgaWNvbi1zbWFsbGVyIj48cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjMiIHJ4PSIyIiByeT0iMiIvPjxsaW5lIHgxPSIzIiB4Mj0iMjEiIHkxPSI5IiB5Mj0iOSIvPjxsaW5lIHgxPSIzIiB4Mj0iMjEiIHkxPSIxNSIgeTI9IjE1Ii8+PGxpbmUgeDE9IjkiIHgyPSI5IiB5MT0iOSIgeTI9IjIxIi8+PGxpbmUgeDE9IjE1IiB4Mj0iMTUiIHkxPSI5IiB5Mj0iMjEiLz48L3N2Zz4nLHY9JzxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1jaGFydC1zcGxpbmUgaWNvbi1zbWFsbGVyIj48cGF0aCBkPSJNMyAzdjE2YTIgMiAwIDAgMCAyIDJoMTYiLz48cGF0aCBkPSJNNyAxNmMuNS0yIDEuNS03IDQtNyAyIDAgMiAzIDQgMyAyLjUgMCA0LjUtNSA1LTciLz48L3N2Zz4nLFM9J1xuPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIlxuICBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiXG4gIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNldHRpbmdzLWljb24gbHVjaWRlLXNldHRpbmdzIj5cbiAgPGcgdHJhbnNmb3JtPSJzY2FsZSgwLjg1KSB0cmFuc2xhdGUoMiAyKSI+XG4gICAgPHBhdGggZD0iTTEyLjIyIDJoLS40NGEyIDIgMCAwIDAtMiAydi4xOGEyIDIgMCAwIDEtMSAxLjczbC0uNDMuMjVhMiAyIDAgMCAxLTIgMGwtLjE1LS4wOGEyIDIgMCAwIDAtMi43My43M2wtLjIyLjM4YTIgMiAwIDAgMCAuNzMgMi43M2wuMTUuMWEyIDIgMCAwIDEgMSAxLjcydi41MWEyIDIgMCAwIDEtMSAxLjc0bC0uMTUuMDlhMiAyIDAgMCAwLS43MyAyLjczbC4yMi4zOGEyIDIgMCAwIDAgMi43My43M2wuMTUtLjA4YTIgMiAwIDAgMSAyIDBsLjQzLjI1YTIgMiAwIDAgMSAxIDEuNzNWMjBhMiAyIDAgMCAwIDIgMmguNDRhMiAyIDAgMCAwIDItMnYtLjE4YTIgMiAwIDAgMSAxLTEuNzNsLjQzLS4yNWEyIDIgMCAwIDEgMiAwbC4xNS4wOGEyIDIgMCAwIDAgMi43My0uNzNsLjIyLS4zOWEyIDIgMCAwIDAtLjczLTIuNzNsLS4xNS0uMDhhMiAyIDAgMCAxLTEtMS43NHYtLjVhMiAyIDAgMCAxIDEtMS43NGwuMTUtLjA5YTIgMiAwIDAgMCAuNzMtMi43M2wtLjIyLS4zOGEyIDIgMCAwIDAtMi43My0uNzNsLS4xNS4wOGEyIDIgMCAwIDEtMiAwbC0uNDMtLjI1YTIgMiAwIDAgMS0xLTEuNzNWNGEyIDIgMCAwIDAtMi0yeiIvPlxuICAgIDxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz5cbiAgPC9nPlxuPC9zdmc+Jy50cmltKCkseT0nXG48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiXG4gIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCJcbiAgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtc3F1YXJlLWFycm93LWRvd24taWNvbiBsdWNpZGUtc3F1YXJlLWFycm93LWRvd24iPlxuICA8ZyB0cmFuc2Zvcm09InNjYWxlKDAuODUpIHRyYW5zbGF0ZSgyIDIpIj5cbiAgICA8cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjMiIHJ4PSIyIi8+XG4gICAgPHBhdGggZD0iTTEyIDh2OCIvPlxuICAgIDxwYXRoIGQ9Im04IDEyIDQgNCA0LTQiLz5cbiAgPC9nPlxuPC9zdmc+Jy50cmltKCksbT0nXG48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiXG4gIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCJcbiAgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtc2VhcmNoIj5cbiAgPGcgdHJhbnNmb3JtPSJzY2FsZSgwLjg1KSB0cmFuc2xhdGUoMiAyKSI+XG4gICAgPGNpcmNsZSBjeD0iMTEiIGN5PSIxMSIgcj0iOCIvPlxuICAgIDxwYXRoIGQ9Im0yMSAyMS00LjMtNC4zIi8+XG4gIDwvZz5cbjwvc3ZnPicudHJpbSgpO3ZhciB4PWEoMzE4MTUpO2Z1bmN0aW9uIGYodCl7cmV0dXJuIHQmJiJvYmplY3QiPT10eXBlb2YgdCYmInZpZXdTdGF0ZSJpbiB0fXZhciBrPWEoNDAxNDUpLEM9YSg4MTAyNiksYj1hKDYyOTI3KTtmdW5jdGlvbiBJKHQsZSl7cmV0dXJuIHQuYmxvY2tJZD09PWUuYmxvY2tJZCYmdC5pbnB1dE5hbWU9PT1lLmlucHV0TmFtZX12YXIgTT1hKDc5MDE4KTtjbGFzcyBUe3N0YXRpYyBnZXQgaXNSZWFkT25seVN1cHBvcnRlZCgpe3JldHVybiEwfXN0YXRpYyBnZXQgdG9vbGJveCgpe3Rocm93IEVycm9yKCJUb29sYm94IGNvbmZpZ3VyYXRpb24gbXVzdCBiZSBwcm92aWRlZCBieSBzdWJjbGFzcyIpfWdldFJlbGF0aW9uSWQodCl7dCh0aGlzLmRhdGEuaWQpfWdldEludGVyYWN0aXZlSWQodCl7dCh0aGlzLmludGVyYWN0aXZlSWQpfXVwZGF0ZUFuZFJlbmRlcih0KXt0aGlzLmRhdGE9dCx0aGlzLnJlbmRlcigpfWFzeW5jIHJlcnVuUXVlcnkoKXtsZXQgdD17Li4udGhpcy5kYXRhLnF1ZXJ5LnZpZXdQYXJhbWV0ZXJzfTthd2FpdCAoMCxNLm01KSh0aGlzLmRhdGEsdCx0aGlzLnVwZGF0ZUFuZFJlbmRlci5iaW5kKHRoaXMpLHRoaXMuaW5wdXRNYW5hZ2VyKX1zZXRJbnB1dFZhbHVlKHQsZSl7dGhpcy5yZXJ1blF1ZXJ5KCl9Z2V0QW5kVXBkYXRlSW5wdXREZXBlbmRlbmNpZXModCl7bGV0IGU9KDAsYi5LTSkodCksYT1bXTtmb3IobGV0IHQgb2YgZSl7bGV0IGU9e2Jsb2NrSWQ6dGhpcy5pbnRlcmFjdGl2ZUlkLGlucHV0TmFtZTp0LGNhbGxGdW5jdGlvbjphc3luYyBlPT57dGhpcy5zZXRJbnB1dFZhbHVlKHQsZSl9fTthLnB1c2goZSl9Zm9yKGxldCB0IG9mIHRoaXMuY3VycmVudElucHV0RGVwZW5kZW5jaWVzKWEuZmluZChlPT5JKHQsZSkpfHx0aGlzLmlucHV0TWFuYWdlci5yZW1vdmVJbnB1dERlcGVuZGVuY3kodCk7Zm9yKGxldCB0IG9mIGEpdGhpcy5jdXJyZW50SW5wdXREZXBlbmRlbmNpZXMuZmluZChlPT5JKGUsdCkpfHx0aGlzLmlucHV0TWFuYWdlci5yZWdpc3RlcklucHV0RGVwZW5kZW5jeSh0KTt0aGlzLmN1cnJlbnRJbnB1dERlcGVuZGVuY2llcz1hfXNldFNob3dDb2RlRmVuY2UodCl7dGhpcy5kYXRhPXsuLi50aGlzLmRhdGEsdmlld1N0YXRlOnsuLi50aGlzLmRhdGEudmlld1N0YXRlLGNvZGVGZW5jZVN0YXRlOnsuLi50aGlzLmRhdGEudmlld1N0YXRlLmNvZGVGZW5jZVN0YXRlLHNob3c6dH19fSx0aGlzLnJlbmRlcigpfXJlbmRlcigpe3JldHVybiB0aGlzLndyYXBwZXJ8fCh0aGlzLndyYXBwZXI9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2IiksdGhpcy53cmFwcGVyLnN0eWxlLmJhY2tncm91bmRDb2xvcj0iaW5oZXJpdCIsdGhpcy5yZWFjdFJvb3Q9KDAsay5jcmVhdGVSb290KSh0aGlzLndyYXBwZXIpKSx0aGlzLnJlbmRlckNvbXBvbmVudCgpLHRoaXMud3JhcHBlcn1yZW5kZXJDb21wb25lbnQoKXt0aGlzLnJlYWN0Um9vdC5yZW5kZXIoKDAsbi5qc3gpKEEse2lucHV0TWFuYWdlcjp0aGlzLmlucHV0TWFuYWdlcixpbml0aWFsRGF0YTp0aGlzLmRhdGEsb25EYXRhQ2hhbmdlOnRoaXMub25EYXRhQ2hhbmdlZC5iaW5kKHRoaXMpfSkpfW9uRGF0YUNoYW5nZWQodCl7dC5xdWVyeS5iYXNlUXVlcnkhPT10aGlzLmRhdGEucXVlcnkuYmFzZVF1ZXJ5JiZ0aGlzLmdldEFuZFVwZGF0ZUlucHV0RGVwZW5kZW5jaWVzKHQucXVlcnkuYmFzZVF1ZXJ5KSx0aGlzLmRhdGE9dH1yZW5kZXJTZXR0aW5ncygpe2xldCB0PXRoaXMuZGF0YS52aWV3U3RhdGUuY29kZUZlbmNlU3RhdGUuc2hvdztyZXR1cm5be3RpdGxlOnQ/IkhpZGUgUXVlcnkiOiJTaG93IFF1ZXJ5IixjbG9zZU9uQWN0aXZhdGU6ITAsaWNvbjp0Pyc8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtZXllLWNsb3NlZCBpY29uLXNtYWxsZXIiPjxwYXRoIGQ9Im0xNSAxOC0uNzIyLTMuMjUiLz48cGF0aCBkPSJNMiA4YTEwLjY0NSAxMC42NDUgMCAwIDAgMjAgMCIvPjxwYXRoIGQ9Im0yMCAxNS0xLjcyNi0yLjA1Ii8+PHBhdGggZD0ibTQgMTUgMS43MjYtMi4wNSIvPjxwYXRoIGQ9Im05IDE4IC43MjItMy4yNSIvPjwvc3ZnPic6JzxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1leWUgaWNvbi1zbWFsbGVyIj48cGF0aCBkPSJNMi4wNjIgMTIuMzQ4YTEgMSAwIDAgMSAwLS42OTYgMTAuNzUgMTAuNzUgMCAwIDEgMTkuODc2IDAgMSAxIDAgMCAxIDAgLjY5NiAxMC43NSAxMC43NSAwIDAgMS0xOS44NzYgMCIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz48L3N2Zz4nLG9uQWN0aXZhdGU6KCk9Pnt0aGlzLnNldFNob3dDb2RlRmVuY2UoIXQpfX0se3RpdGxlOiJSdW4gUXVlcnkiLGljb246JzxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1wbGF5LWljb24gbHVjaWRlLXBsYXkiPlxuIDxnIHRyYW5zZm9ybT0ic2NhbGUoMC44NSkgdHJhbnNsYXRlKDIgMikiPlxuPHBhdGggZD0iTTUgNWEyIDIgMCAwIDEgMy4wMDgtMS43MjhsMTEuOTk3IDYuOTk4YTIgMiAwIDAgMSAuMDAzIDMuNDU4bC0xMiA3QTIgMiAwIDAgMSA1IDE5eiIvPlxuXG4gIDwvZz5cbjwvc3ZnPlxuJyxjbG9zZU9uQWN0aXZhdGU6ITAsb25BY3RpdmF0ZTooKT0+e3RoaXMucmVydW5RdWVyeSgpfX1dfXNhdmUoKXtyZXR1cm4gdGhpcy5kYXRhfWRlc3Ryb3koKXt0aGlzLnJlYWN0Um9vdCYmdGhpcy5yZWFjdFJvb3QudW5tb3VudCgpfWNvbnN0cnVjdG9yKHtkYXRhOnQsYXBpOmUscmVhZE9ubHk6YSxjb25maW" 8 | "c6bn0saSl7dGhpcy53cmFwcGVyPW51bGwsdGhpcy5yZWFjdFJvb3Q9bnVsbCx0aGlzLmFwaT1lLHRoaXMucmVhZE9ubHk9YSx0aGlzLmRhdGE9dDtsZXQgcj10aGlzLmFwaS5ibG9ja3MuZ2V0QmxvY2tzQ291bnQoKTtmb3IobGV0IHQ9MDt0PHI7dCsrKXtsZXQgZT10aGlzLmFwaS5ibG9ja3MuZ2V0QmxvY2tCeUluZGV4KHQpO2lmKGUmJigwLHUueUUpKGUubmFtZSkpe2xldCB0PSIiLGE9ZT0+e3Q9ZX07ZS5jYWxsKCJnZXRSZWxhdGlvbklkIixhKSx0PT09dGhpcy5kYXRhLmlkJiYodGhpcy5kYXRhLmlkPSgwLEMucikoKSl9fWlmKCFuLmdldElucHV0TWFuYWdlcil0aHJvdyBFcnJvcigiR2V0SW5wdXRNYW5hZ2VyIGZ1bmN0aW9uIGlzIHJlcXVpcmVkIik7dGhpcy5pbnB1dE1hbmFnZXI9bi5nZXRJbnB1dE1hbmFnZXIoaSksdGhpcy5pbnRlcmFjdGl2ZUlkPSgwLEMucikoMzIpLHRoaXMuY3VycmVudElucHV0RGVwZW5kZW5jaWVzPVtdLHRoaXMuaW5wdXRNYW5hZ2VyJiZ0aGlzLmdldEFuZFVwZGF0ZUlucHV0RGVwZW5kZW5jaWVzKHRoaXMuZGF0YS5xdWVyeS5iYXNlUXVlcnkpfX1mdW5jdGlvbiBBKHQpe2xldHtpbml0aWFsRGF0YTplLG9uRGF0YUNoYW5nZTphLGlucHV0TWFuYWdlcjpyfT10LFtzLGxdPSgwLGkudXNlU3RhdGUpKGUpO3JldHVybigwLGkudXNlRWZmZWN0KSgoKT0+e2woZSl9LFtlXSksKDAsbi5qc3gpKHAse29uUmVsYXRpb25VcGRhdGU6ZnVuY3Rpb24odCl7bCh0KSxhKHQpfSxyZWxhdGlvbjpzLGlucHV0TWFuYWdlcjpyfSl9Y2xhc3MgViBleHRlbmRzIFR7c3RhdGljIGdldCB0b29sYm94KCl7cmV0dXJue3RpdGxlOiJEYXRhIFRhYmxlIixpY29uOmd9fWFzeW5jIHNldFZpZXdUeXBlKHQpe3RoaXMuZGF0YT17Li4udGhpcy5kYXRhLHZpZXdTdGF0ZTp7Li4udGhpcy5kYXRhLnZpZXdTdGF0ZSxzZWxlY3RlZFZpZXc6dH19O2xldCBlPXsuLi50aGlzLmRhdGEucXVlcnkudmlld1BhcmFtZXRlcnMsdHlwZTp0fTthd2FpdCAoMCxNLm01KSh0aGlzLmRhdGEsZSx0aGlzLnVwZGF0ZUFuZFJlbmRlci5iaW5kKHRoaXMpLHRoaXMuaW5wdXRNYW5hZ2VyKX1zaG93Q2hhcnRTZXR0aW5ncyh0KXt0aGlzLmRhdGE9ey4uLnRoaXMuZGF0YSx2aWV3U3RhdGU6ey4uLnRoaXMuZGF0YS52aWV3U3RhdGUsY2hhcnRTdGF0ZTp7Li4udGhpcy5kYXRhLnZpZXdTdGF0ZS5jaGFydFN0YXRlLHZpZXc6ey4uLnRoaXMuZGF0YS52aWV3U3RhdGUuY2hhcnRTdGF0ZS52aWV3LHNob3dDb25maWc6dH19fX0sdGhpcy5yZW5kZXIoKX1yZW5kZXJTZXR0aW5ncygpe2xldCB0PXN1cGVyLnJlbmRlclNldHRpbmdzKCksZT10aGlzLmRhdGEudmlld1N0YXRlLmNoYXJ0U3RhdGUudmlldy5zaG93Q29uZmlnLGE9dGhpcy5kYXRhLnZpZXdTdGF0ZS5zZWxlY3RlZFZpZXc7cmV0dXJuWy4uLnQsLi4uW3t0eXBlOiJ0YWJsZSIsbGFiZWw6IlZpZXcgYXMgVGFibGUiLGljb246Z30se3R5cGU6ImNoYXJ0IixsYWJlbDoiVmlldyBhcyBDaGFydCIsaWNvbjp2fV0uZmlsdGVyKHQ9PnQudHlwZSE9PWEpLm1hcCh0PT4oe3RpdGxlOnQubGFiZWwsaWNvbjp0Lmljb24sY2xvc2VPbkFjdGl2YXRlOiEwLG9uQWN0aXZhdGU6KCk9Pnt0aGlzLnNldFZpZXdUeXBlKHQudHlwZSl9fSkpLC4uLiJjaGFydCI9PT1hP1t7dGl0bGU6ZT8iSGlkZSBDaGFydCBTZXR0aW5ncyI6IlNob3cgQ2hhcnQgU2V0dGluZ3MiLGljb246ZT8nPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNhcHRpb25zLW9mZiBpY29uLXNtYWxsZXIiPjxwYXRoIGQ9Ik0xMC41IDVIMTlhMiAyIDAgMCAxIDIgMnY4LjUiLz48cGF0aCBkPSJNMTcgMTFoLS41Ii8+PHBhdGggZD0iTTE5IDE5SDVhMiAyIDAgMCAxLTItMlY3YTIgMiAwIDAgMSAyLTIiLz48cGF0aCBkPSJtMiAyIDIwIDIwIi8+PHBhdGggZD0iTTcgMTFoNCIvPjxwYXRoIGQ9Ik03IDE1aDIuNSIvPjwvc3ZnPic6UyxjbG9zZU9uQWN0aXZhdGU6ITAsb25BY3RpdmF0ZTooKT0+e3RoaXMuc2hvd0NoYXJ0U2V0dGluZ3MoIWUpfX1dOltdXX1jb25zdHJ1Y3Rvcih7ZGF0YTp0LGFwaTplLHJlYWRPbmx5OmEsY29uZmlnOm59LGkpe2YodCl8fCh0PSgwLHcuTCkoInRhYmxlIikpLHN1cGVyKHtkYXRhOnQsYXBpOmUscmVhZE9ubHk6YSxjb25maWc6bn0seC5SOSl9fXZhciBEPWEoNzM4ODApLFI9YSgzMjEyMik7Y2xhc3MgTntzdGF0aWMgZ2V0IGlzUmVhZE9ubHlTdXBwb3J0ZWQoKXtyZXR1cm4hMH1zdGF0aWMgZ2V0IGNvbnRlbnRsZXNzKCl7cmV0dXJuITB9ZHJhd1ZpZXcoKXtsZXQgdD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTtyZXR1cm4gdC5zdHlsZS5oZWlnaHQ9IjI0cHgiLHQuc3R5bGUuZGlzcGxheT0iZmxleCIsdC5zdHlsZS5qdXN0aWZ5Q29udGVudD0iY2VudGVyIix0LnN0eWxlLmFsaWduSXRlbXM9ImNlbnRlciIsdC5zdHlsZS53aWR0aD0iMTAwJSIsdC5pbm5lckhUTUw9JzxkaXYgc3R5bGU9ImhlaWdodDogMXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiBoc2wodmFyKC0tYm9yZGVyKSk7IHdpZHRoOiAxMDAlOyI+PC9kaXY+Jyx0fXJlbmRlcigpe3JldHVybiB0aGlzLl9lbGVtZW50fXNhdmUodCl7cmV0dXJue319c3RhdGljIGdldCB0b29sYm94KCl7cmV0dXJue2ljb246Ui5iNyx0aXRsZToiRGl2aWRlciJ9fXN0YXRpYyBnZXQgcGFzdGVDb25maWcoKXtyZXR1cm57dGFnczpbIkhSIl19fW9uUGFzdGUodCl7dGhpcy5kYXRhPXt9fWNvbnN0cnVjdG9yKHtkYXRhOnQsY29uZmlnOmUsYXBpOmF9KXt0aGlzLmFwaT1hLHRoaXMuX0NTUz17YmxvY2s6dGhpcy5hcGkuc3R5bGVzLmJsb2NrLHdyYXBwZXI6ImNlLWRlbGltaXRlciJ9LHRoaXMuX2VsZW1lbnQ9dGhpcy5kcmF3VmlldygpLHRoaXMuZGF0YT10fX12YXIgRT1hKDkwNzA4KSxqPWEoOTMwOTApLEY9YSg4NzAzOCksQj1hKDMxNDQzKSxIPWEoMTM4NTcpLFE9YSgxNjMwNykscT1hKDE1MjE3KSxPPWEoNjU2NDYpLEw9YSgyNTE2Nyk7Y2xhc3MgVSBleHRlbmRzIFR7c2V0U2hvd0NvbmZpZyh0KXt0aGlzLmRhdGE9ey4uLnRoaXMuZGF0YSx2aWV3U3RhdGU6ey4uLnRoaXMuZGF0YS52aWV3U3RhdGUsaW5wdXRUZXh0U3RhdGU6ey4uLnRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUsc2hvd0NvbmZpZzp0fX19LHRoaXMucmVuZGVyKCl9b25WYWx1ZUNoYW5nZWQodCl7bGV0IGU9e2ludGVyYWN0aXZlSWQ6dGhpcy5pbnRlcmFjdGl2ZUlkLGlucHV0TmFtZTp0aGlzLmRhdGEudmlld1N0YXRlLmlucHV0VGV4dFN0YXRlLm5hbWUsaW5wdXRWYWx1ZTp7dmFsdWU6dH19O3RoaXMuaW5wdXRNYW5hZ2VyLm9uSW5wdXRWYWx1ZUNoYW5nZShlKX1vbklucHV0TmFtZUNoYW5nZWQodCxlKXtsZXQgYT17YmxvY2tJZDp0aGlzLmludGVyYWN0aXZlSWQsaW5wdXROYW1lOmUsaW5wdXRWYWx1ZTp7dmFsdWU6dGhpcy5jdXJyZW50U2VsZWN0VmFsdWV9fSxuPXtibG9ja0lkOnRoaXMuaW50ZXJhY3RpdmVJZCxpbnB1dE5hbWU6dCxpbnB1dFZhbHVlOnt2YWx1ZTp0aGlzLmN1cnJlbnRTZWxlY3RWYWx1ZX19O3RoaXMuaW5wdXRNYW5hZ2VyLnVwZGF0ZUlucHV0U291cmNlKGEsbil9b25EYXRhQ2hhbmdlZCh0KXtpZihzdXBlci5vbkRhdGFDaGFuZ2VkKHQpLHRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUudmFsdWUhPT10aGlzLmN1cnJlbnRTZWxlY3RWYWx1ZSYmKHRoaXMuY3VycmVudFNlbGVjdFZhbHVlPXRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUudmFsdWUsdGhpcy5vblZhbHVlQ2hhbmdlZCh0aGlzLmN1cnJlbnRTZWxlY3RWYWx1ZSkpLHRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUubmFtZSE9PXRoaXMuY3VycmVudFNlbGVjdE5hbWUmJnRoaXMuY3VycmVudFNlbGVjdE5hbWUpe2xldCB0PXRoaXMuY3VycmVudFNlbGVjdE5hbWUsZT10aGlzLmRhdGEudmlld1N0YXRlLmlucHV0VGV4dFN0YXRlLm5hbWU7dGhpcy5vbklucHV0TmFtZUNoYW5nZWQoZSx0KSx0aGlzLmN1cnJlbnRTZWxlY3ROYW1lPXRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUubmFtZX19cmVuZGVyU2V0dGluZ3MoKXt2YXIgdDt0aGlzLmRhdGEudmlld1N0YXRlLmNvZGVGZW5jZVN0YXRlLnNob3c7bGV0IGU9bnVsbCE9PSh0PXRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUuc2hvd0NvbmZpZykmJnZvaWQgMCE9PXQmJnQ7cmV0dXJuWy4uLnN1cGVyLnJlbmRlclNldHRpbmdzKCkse3RpdGxlOmU/IkhpZGUgQ29uZmlnIjoiU2hvdyBDb25maWciLGNsb3NlT25BY3RpdmF0ZTohMCxpY29uOlMsb25BY3RpdmF0ZTooKT0+e3RoaXMuc2V0U2hvd0NvbmZpZyghZSl9fV19Y29uc3RydWN0b3Ioe2RhdGE6dCxhcGk6ZSxyZWFkT25seTphLGNvbmZpZzpufSl7aWYoZih0KXx8KHQ9ZnVuY3Rpb24odCl7bGV0IGU9IiI7InNlbGVjdCI9PT10P2U9IlNFTEVDVCAnT3B0aW9uICcgfHwgcmFuZ2UgZnJvbSByYW5nZSg1KTsgLS0gRS5nLiBTRUxFQ1QgRElTVElOQ1QgdHlwZSBGUk9NIHRhYmxlIjoiZnVsbHRleHQiPT09dCYmKGU9IlNFTEVDVCAnU3VnZ2VzdGlvbicgTElNSVQgMDsgLS0gTm8gZGF0YSBpbml0aWFsbHkiKTtsZXQgYT0oMCxDLnIpKCksbj17dHlwZToicXVlcnkiLGJhc2VRdWVyeTplLGlkOmEsbmFtZToic2VsZWN0XyIrYS5zdWJzdHJpbmcoMCw4KX0saT0oMCxiLmFTKSgpLHI9e2Nvbm5lY3Rpb25JZDpMLk5GLGlkOmEsbmFtZToiTmV3IFF1ZXJ5Iixzb3VyY2U6bn0scz0oMCxiLk5DKShyLGksZSksbD0oMCxPLnBVKSgiTmV3IERhdGEgRWxlbWVudCIsdm9pZCAwLFtdLCEwKTtyZXR1cm4gbC5pbnB1dFRleHRTdGF0ZS5pbnB1dFR5cGU9dCxsLnNlbGVjdGVkVmlldz0ic2VsZWN0Iix7Li4ucixxdWVyeTpzLHZpZXdTdGF0ZTpsLGV4ZWN1dGlvblN0YXRlOntzdGF0ZToibm90LXN0YXJ0ZWQifX19KG4udHlwZSkpLHN1cGVyKHtkYXRhOnQsYXBpOmUscmVhZE9ubHk6YSxjb25maWc6bn0seC52bSksdGhpcy5jdXJyZW50U2VsZWN0VmFsdWU9dGhpcy5kYXRhLnZpZXdTdGF0ZS5pbnB1dFRleHRTdGF0ZS52YWx1ZSx0aGlzLmN1cnJlbnRTZWxlY3ROYW1lPXRoaXMuZGF0YS52aWV3U3RhdGUuaW5wdXRUZXh0U3RhdGUubmFtZSx0aGlzLmlucHV0TWFuYWdlcil7dmFyIGk7bGV0IHQ9e2Jsb2NrSWQ6dGhpcy5pbnRlcmFjdGl2ZUlkLGlucHV0TmFtZTp0aGlzLmRhdGEudmlld1N0YXRlLmlucHV0VGV4dFN0YXRlLm5hbWUsaW5wdXRWYWx1ZTp7dmFsdWU6dGhpcy5jdXJyZW50U2VsZWN0VmFsdWV9fTtudWxsPT09KGk9dGhpcy5pbnB1dE1hbmFnZXIpfHx2b2lkIDA9PT1pfHxpLnJlZ2lzdGVySW5wdXRTb3VyY2UodCl9fX1jbGFzcyBfIGV4dGVuZHMgVXtzdGF0aWMgZ2V0IHRvb2xib3goKXtyZXR1cm57dGl0bGU6IlNlbGVjdCBJbnB1dCIsaWNvbjp5fX19Y2xhc3MgeiBleHRlbmRzIFV7c3RhdGljIGdldCB0b29sYm94KCl7cmV0dXJue3RpdGxlOiJGdWxsdGV4dCBJbnB1dCIsaWNvbjptfX19Y2xhc3MgUCBleHRlbmRzIFZ7c3RhdGljIGdldCB0b29sYm94KCl7cmV0dXJue3RpdGxlOiJDaGFydCIsaWNvbjp2fX1jb25zdHJ1Y3Rvcih7ZGF0YTp0LGFwaTplLHJlYWRPbmx5OmEsY29uZmlnOm59KXtmKHQpfHwodD0oMCx3LkwpKCJjaGFydCIpKSxzdXBlcih7ZGF0YTp0LGFwaTplLHJlYWRPbmx5OmEsY29uZmlnOm59LHguUjkpfX1mdW5jdGlvbiBHKHQpe2xldHtyZWFkT25seTplPSExLG9uVG9nZ2xlUmVhZE9ubHk6YX09dCxzPSgwLGkudXNlUmVmKShudWxsKSx7c2V0RWRpdG9yOm8scmVtb3ZlRWRpdG9yOmh9PSgwLGQuVSkoKTtyZXR1cm4oMCxpLnVzZUVmZmVjdCkoKCk9PntpZihzLmN1cnJlbnQpcmV0dXJuO2xldCBlPW5ldyB1LmVOLGE9bmV3IFNldCxuPW5ldyByLkEoe2hvbGRlcjp0LmlkLHBsYWNlaG9sZGVyOiJTdGFydCB3cml0aW5nIGhlcmUuLiIscmVhZE9ubHk6ITEsb25SZWFkeTooKT0+e25ldyhjKCkpKHtlZGl0b3I6bn0pLG5ldyhsKCkpKG4pLG8odC5pZCx7ZWRpdG9yOm4sbWFuYWdlcjplfSksdC5vblJlYWR5" 9 | "JiZ0Lm9uUmVhZHkobil9LG9uQ2hhbmdlOihlLGEpPT57dmFyIGk7bGV0IHI9QXJyYXkuaXNBcnJheShhKT9hOlthXSxzPSgwLGQuVSkoKS5nZXRFZGl0b3IodC5pZCk7cyYmcy5tYW5hZ2VyLm9uQmxvY2tDaGFuZ2VFdmVudChyKSxudWxsPT09KGk9dC5vbkJsb2NrQ2hhbmdlRXZlbnQpfHx2b2lkIDA9PT1pfHxpLmNhbGwodCxyKSxuLnNhdmUoKS50aGVuKGU9Pnt2YXIgYTtudWxsPT09KGE9dC5vblNhdmVkKXx8dm9pZCAwPT09YXx8YS5jYWxsKHQsZSl9KX0sdG9vbHM6ZnVuY3Rpb24odCl7bGV0IGU9e3BsYWNlaG9sZGVyOiJBZGQgYSBuZXcgcmVsYXRpb24iLGdldElucHV0TWFuYWdlcjp0fTtyZXR1cm57W3guUjldOntjbGFzczpWLGlubGluZVRvb2xiYXI6ITAsc2hvcnRjdXQ6IkNNRCtTSElGVCtSIixjb25maWc6ZX0sW3guTGxdOntjbGFzczpQLGlubGluZVRvb2xiYXI6ITAsY29uZmlnOmV9LFt4LnZtXTp7Y2xhc3M6XyxpbmxpbmVUb29sYmFyOiEwLGNvbmZpZzp7Li4uZSx0eXBlOiJzZWxlY3QifX0sW3guWHpdOntjbGFzczp6LGlubGluZVRvb2xiYXI6ITAsY29uZmlnOnsuLi5lLHR5cGU6ImZ1bGx0ZXh0In19LFt4LmhKXTp7Y2xhc3M6cS5BLGlubGluZVRvb2xiYXI6WyJtYXJrZXIiLCJsaW5rIl0sY29uZmlnOntwbGFjZWhvbGRlcjoiSGVhZGVyIn0sc2hvcnRjdXQ6IkNNRCtTSElGVCtIIn0sW3guaTVdOntjbGFzczpRLkEsaW5saW5lVG9vbGJhcjohMCxzaG9ydGN1dDoiQ01EK1NISUZUK0wifSxbeC5BTl06RC5BLG1hcmtlcjp7Y2xhc3M6Qi5BLHNob3J0Y3V0OiJDTUQrU0hJRlQrTSJ9LFt4LlowXTpOLGlubGluZUNvZGU6e2NsYXNzOkUuQSxzaG9ydGN1dDoiQ01EK1NISUZUK0MifSxsaW5rVG9vbDpGLkEsZW1iZWQ6ai5BLHRhYmxlOntjbGFzczpILkEsaW5saW5lVG9vbGJhcjohMCxzaG9ydGN1dDoiQ01EK0FMVCtUIn19fSh0PT5hLmhhcyh0KT9lOihhLmFkZCh0KSxudWxsKSksZGF0YTp0LmluaXRpYWxEYXRhfSk7cmV0dXJuIHMuY3VycmVudD1uLCgpPT57cy5jdXJyZW50JiZzLmN1cnJlbnQuZGVzdHJveSYmKHMuY3VycmVudC5kZXN0cm95KCksaCh0LmlkKSl9fSxbZV0pLCgwLG4uanN4KSgiZGl2Iix7aWQ6dC5pZCxjbGFzc05hbWU6InctZnVsbCBtaW4taC1bNDAwcHhdIHAtMiJ9KX19fV0pOw==", // 10 | 17908, // 11 | "application/javascript", // 12 | "/_next/static/chunks/902.02a4e3e7dd6de078.js/", // 13 | 14 | }; 15 | } 16 | --------------------------------------------------------------------------------