├── .github ├── ISSUE_TEMPLATE │ └── bug_report.yml └── workflows │ ├── InternalIssuesCreateMirror.yml │ ├── InternalIssuesUpdateMirror.yml │ ├── MainDistributionPipeline.yml │ └── MysqlTests.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── extension_config.cmake ├── mysqlconfigure ├── src ├── CMakeLists.txt ├── include │ ├── mysql_connection.hpp │ ├── mysql_filter_pushdown.hpp │ ├── mysql_result.hpp │ ├── mysql_scanner.hpp │ ├── mysql_scanner_extension.hpp │ ├── mysql_storage.hpp │ ├── mysql_text_writer.hpp │ ├── mysql_utils.hpp │ ├── mysql_version.h │ └── storage │ │ ├── mysql_catalog.hpp │ │ ├── mysql_catalog_set.hpp │ │ ├── mysql_execute_query.hpp │ │ ├── mysql_index.hpp │ │ ├── mysql_index_entry.hpp │ │ ├── mysql_index_set.hpp │ │ ├── mysql_insert.hpp │ │ ├── mysql_optimizer.hpp │ │ ├── mysql_schema_entry.hpp │ │ ├── mysql_schema_set.hpp │ │ ├── mysql_table_entry.hpp │ │ ├── mysql_table_set.hpp │ │ ├── mysql_transaction.hpp │ │ └── mysql_transaction_manager.hpp ├── mysql_connection.cpp ├── mysql_execute.cpp ├── mysql_extension.cpp ├── mysql_filter_pushdown.cpp ├── mysql_scanner.cpp ├── mysql_storage.cpp ├── mysql_utils.cpp └── storage │ ├── CMakeLists.txt │ ├── mysql_catalog.cpp │ ├── mysql_catalog_set.cpp │ ├── mysql_clear_cache.cpp │ ├── mysql_execute_query.cpp │ ├── mysql_index.cpp │ ├── mysql_index_entry.cpp │ ├── mysql_index_set.cpp │ ├── mysql_insert.cpp │ ├── mysql_optimizer.cpp │ ├── mysql_schema_entry.cpp │ ├── mysql_schema_set.cpp │ ├── mysql_table_entry.cpp │ ├── mysql_table_set.cpp │ ├── mysql_transaction.cpp │ └── mysql_transaction_manager.cpp ├── test ├── remove_root_password.sql ├── sql │ ├── attach_alter.test │ ├── attach_clear_cache.test │ ├── attach_constraints.test │ ├── attach_create_index.test │ ├── attach_database_size.test │ ├── attach_delete.test │ ├── attach_dsn.test │ ├── attach_dynamic_filter.test │ ├── attach_external_access.test │ ├── attach_fake_booleans.test │ ├── attach_filter_pushdown.test │ ├── attach_identifiers_insert.test │ ├── attach_keywords.test │ ├── attach_large_insert.test │ ├── attach_limit.test │ ├── attach_many_nulls.test │ ├── attach_mysql_geometry.test │ ├── attach_no_database.test │ ├── attach_nonexistent_database.test │ ├── attach_read_only.test │ ├── attach_schemas.test │ ├── attach_secret.test │ ├── attach_simple.test │ ├── attach_ssl.test │ ├── attach_timestamp_issue_16.test │ ├── attach_timestamp_tz_roundtrip.test │ ├── attach_timezone_insert.test │ ├── attach_transactions.test │ ├── attach_types.test │ ├── attach_types_blob.test │ ├── attach_unicode.test │ ├── attach_update.test │ ├── attach_uri.test │ ├── attach_views.test │ ├── attach_zero_date.test │ ├── failed_to_connect.test │ ├── insert_backslash.test │ ├── mysql_execute.test │ ├── mysql_query.test │ ├── scan_bit.test │ ├── scan_blob.test │ ├── scan_bool.test │ ├── scan_datetime.test │ ├── scan_decimal.test │ ├── scan_enum.test │ ├── scan_numeric_types.test │ ├── scan_text.test │ └── test_case_insensitivity.test └── test_data.sql ├── vcpkg.json └── vcpkg_ports └── libmysql ├── Add-target-include-directories.patch ├── export-cmake-targets.patch ├── fix_dup_symbols.patch ├── homebrew.patch ├── ignore-boost-version.patch ├── portfile.cmake ├── posix_changes.patch ├── remove_executables.patch ├── scanner_changes.patch ├── system-libs.patch ├── usage ├── vcpkg-cmake-wrapper.cmake └── vcpkg.json /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: > 7 | DuckDB has several repositories for different components, please make sure you're raising your issue in the correct one: 8 | * [Our docs/website](https://github.com/duckdb/duckdb-web/issues/new) 9 | * [SQLite scanner](https://github.com/duckdblabs/sqlite_scanner/issues/new) 10 | * [DuckDB Core](https://github.com/duckdb/duckdb/issues/new) 11 | 12 | If none of the above repositories are applicable, feel free to raise it in this one 13 | 14 | - type: textarea 15 | attributes: 16 | label: What happens? 17 | description: A short, clear and concise description of what the bug is. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: To Reproduce 24 | description: Steps to reproduce the behavior. Bonus points if those are only SQL queries. 25 | validations: 26 | required: true 27 | 28 | - type: markdown 29 | attributes: 30 | value: "# Environment (please complete the following information):" 31 | - type: input 32 | attributes: 33 | label: "OS:" 34 | placeholder: e.g. iOS 35 | validations: 36 | required: true 37 | - type: input 38 | attributes: 39 | label: "MySQL Version:" 40 | placeholder: e.g. 8.1 41 | validations: 42 | required: true 43 | - type: input 44 | attributes: 45 | label: "DuckDB Version:" 46 | placeholder: e.g. 0.9 47 | validations: 48 | required: true 49 | - type: input 50 | attributes: 51 | label: "DuckDB Client:" 52 | placeholder: e.g. Python 53 | validations: 54 | required: true 55 | 56 | - type: markdown 57 | attributes: 58 | value: "# Identity Disclosure:" 59 | - type: input 60 | attributes: 61 | label: "Full Name:" 62 | placeholder: e.g. John Doe 63 | validations: 64 | required: true 65 | - type: input 66 | attributes: 67 | label: "Affiliation:" 68 | placeholder: e.g. Oracle 69 | validations: 70 | required: true 71 | 72 | - type: markdown 73 | attributes: 74 | value: | 75 | If the above is not given and is not obvious from your GitHub profile page, we might close your issue without further review. Please refer to the [reasoning behind this rule](https://berthub.eu/articles/posts/anonymous-help/) if you have questions. 76 | 77 | # Before Submitting 78 | 79 | - type: checkboxes 80 | attributes: 81 | label: Have you tried this on the latest `main` branch? 82 | description: | 83 | * **Python**: `pip install duckdb --upgrade --pre` 84 | * **R**: `install.packages('duckdb', repos=c('https://duckdb.r-universe.dev', 'https://cloud.r-project.org'))` 85 | * **Other Platforms**: You can find links to binaries [here](https://duckdb.org/docs/installation/) or compile from source. 86 | 87 | options: 88 | - label: I agree 89 | required: true 90 | 91 | - type: checkboxes 92 | attributes: 93 | label: Have you tried the steps to reproduce? Do they include all relevant data and configuration? Does the issue you report still appear there? 94 | options: 95 | - label: I agree 96 | required: true 97 | -------------------------------------------------------------------------------- /.github/workflows/InternalIssuesCreateMirror.yml: -------------------------------------------------------------------------------- 1 | name: Create or Label Mirror Issue 2 | on: 3 | issues: 4 | types: 5 | - labeled 6 | 7 | env: 8 | GH_TOKEN: ${{ secrets.DUCKDBLABS_BOT_TOKEN }} 9 | TITLE_PREFIX: "[duckdb_mysql/#${{ github.event.issue.number }}]" 10 | PUBLIC_ISSUE_TITLE: ${{ github.event.issue.title }} 11 | 12 | jobs: 13 | create_or_label_issue: 14 | if: github.event.label.name == 'reproduced' || github.event.label.name == 'under review' 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Get mirror issue number 18 | run: | 19 | gh issue list --repo duckdblabs/duckdb-internal --search "${TITLE_PREFIX}" --json title,number --state all --jq ".[] | select(.title | startswith(\"$TITLE_PREFIX\")).number" > mirror_issue_number.txt 20 | echo "MIRROR_ISSUE_NUMBER=$(cat mirror_issue_number.txt)" >> $GITHUB_ENV 21 | 22 | - name: Print whether mirror issue exists 23 | run: | 24 | if [ "$MIRROR_ISSUE_NUMBER" == "" ]; then 25 | echo "Mirror issue with title prefix '$TITLE_PREFIX' does not exist yet" 26 | else 27 | echo "Mirror issue with title prefix '$TITLE_PREFIX' exists with number $MIRROR_ISSUE_NUMBER" 28 | fi 29 | 30 | - name: Create or label issue 31 | run: | 32 | export LABEL="mysql" 33 | if [ "$MIRROR_ISSUE_NUMBER" == "" ]; then 34 | gh issue create --repo duckdblabs/duckdb-internal --label "$LABEL" --title "$TITLE_PREFIX - $PUBLIC_ISSUE_TITLE" --body "See https://github.com/duckdb/duckdb_mysql/issues/${{ github.event.issue.number }}" 35 | fi 36 | -------------------------------------------------------------------------------- /.github/workflows/InternalIssuesUpdateMirror.yml: -------------------------------------------------------------------------------- 1 | name: Update Mirror Issue 2 | on: 3 | issues: 4 | types: 5 | - closed 6 | - reopened 7 | 8 | env: 9 | GH_TOKEN: ${{ secrets.DUCKDBLABS_BOT_TOKEN }} 10 | TITLE_PREFIX: "[duckdb_mysql/#${{ github.event.issue.number }}]" 11 | 12 | jobs: 13 | update_mirror_issue: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Get mirror issue number 17 | run: | 18 | gh issue list --repo duckdblabs/duckdb-internal --search "${TITLE_PREFIX}" --json title,number --state all --jq ".[] | select(.title | startswith(\"$TITLE_PREFIX\")).number" > mirror_issue_number.txt 19 | echo "MIRROR_ISSUE_NUMBER=$(cat mirror_issue_number.txt)" >> $GITHUB_ENV 20 | 21 | - name: Print whether mirror issue exists 22 | run: | 23 | if [ "$MIRROR_ISSUE_NUMBER" == "" ]; then 24 | echo "Mirror issue with title prefix '$TITLE_PREFIX' does not exist yet" 25 | else 26 | echo "Mirror issue with title prefix '$TITLE_PREFIX' exists with number $MIRROR_ISSUE_NUMBER" 27 | fi 28 | 29 | - name: Add comment with status to mirror issue 30 | run: | 31 | if [ "$MIRROR_ISSUE_NUMBER" != "" ]; then 32 | gh issue comment --repo duckdblabs/duckdb-internal $MIRROR_ISSUE_NUMBER --body "The issue has been ${{ github.event.action }} (https://github.com/duckdb/duckdb_mysql/issues/${{ github.event.issue.number }})." 33 | fi 34 | 35 | - name: Add closed label to mirror issue 36 | if: github.event.action == 'closed' 37 | run: | 38 | if [ "$MIRROR_ISSUE_NUMBER" != "" ]; then 39 | gh issue edit --repo duckdblabs/duckdb-internal $MIRROR_ISSUE_NUMBER --add-label "public closed" --remove-label "public reopened" 40 | fi 41 | 42 | - name: Reopen mirror issue and add reopened label 43 | if: github.event.action == 'reopened' 44 | run: | 45 | if [ "$MIRROR_ISSUE_NUMBER" != "" ]; then 46 | gh issue reopen --repo duckdblabs/duckdb-internal $MIRROR_ISSUE_NUMBER 47 | gh issue edit --repo duckdblabs/duckdb-internal $MIRROR_ISSUE_NUMBER --add-label "public reopened" --remove-label "public closed" 48 | fi 49 | -------------------------------------------------------------------------------- /.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 | merge_group: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }}-${{ github.ref != 'refs/heads/main' || github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | duckdb-stable-build: 17 | name: Build extension binaries 18 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v1.3.0 19 | with: 20 | extension_name: mysql_scanner 21 | duckdb_version: main 22 | ci_tools_version: v1.3.0 23 | exclude_archs: 'wasm_mvp;wasm_eh;wasm_threads;windows_amd64_rtools;windows_amd64_mingw;linux_amd64_musl' 24 | build_duckdb_shell: false 25 | 26 | duckdb-stable-deploy: 27 | name: Deploy extension binaries 28 | needs: duckdb-stable-build 29 | uses: duckdb/extension-ci-tools/.github/workflows/_extension_deploy.yml@v1.3.0 30 | secrets: inherit 31 | with: 32 | duckdb_version: main 33 | ci_tools_version: v1.3.0 34 | extension_name: mysql_scanner 35 | exclude_archs: 'wasm_mvp;wasm_eh;wasm_threads;windows_amd64_rtools;windows_amd64_mingw;linux_amd64_musl' 36 | deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' }} 37 | -------------------------------------------------------------------------------- /.github/workflows/MysqlTests.yml: -------------------------------------------------------------------------------- 1 | name: MySQL Tests 2 | on: [push, pull_request,repository_dispatch, merge_group] 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }}-${{ github.ref != 'refs/heads/main' || github.sha }} 5 | cancel-in-progress: true 6 | defaults: 7 | run: 8 | shell: bash 9 | 10 | jobs: 11 | macos: 12 | name: MySQL tests (${{ matrix.osx_build_arch }}) 13 | runs-on: macos-latest 14 | strategy: 15 | matrix: 16 | # Add commits/tags to build against other DuckDB versions 17 | duckdb_version: [ '' ] 18 | vcpkg_version: [ '2023.10.19' ] 19 | vcpkg_triplet: [ 'x64-osx' ] 20 | include: 21 | - vcpkg_triplet: 'x64-osx' 22 | osx_build_arch: 'x86_64' 23 | duckdb_arch: 'osx_amd64' 24 | 25 | env: 26 | VCPKG_TARGET_TRIPLET: ${{ matrix.vcpkg_triplet }} 27 | OSX_BUILD_ARCH: ${{ matrix.osx_build_arch }} 28 | GEN: Ninja 29 | VCPKG_TOOLCHAIN_PATH: ${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | with: 34 | fetch-depth: 0 35 | submodules: 'true' 36 | 37 | - name: Install 38 | run: | 39 | brew install ninja 40 | brew install autoconf 41 | brew install automake 42 | brew install autoconf-archive 43 | brew install libevent 44 | brew install jemalloc 45 | brew install mysql 46 | brew services start mysql 47 | touch build.ninja 48 | 49 | - name: Setup Ccache 50 | uses: hendrikmuhs/ccache-action@v1.2.11 # Note: pinned due to GLIBC incompatibility in later releases 51 | with: 52 | key: ${{ github.job }}-${{ matrix.duckdb_version }} 53 | save: ${{ github.ref == 'refs/heads/main' || github.repository != 'duckdb/duckdb' }} 54 | 55 | - uses: actions/setup-python@v2 56 | with: 57 | python-version: '3.11' 58 | 59 | - name: Setup vcpkg 60 | uses: lukka/run-vcpkg@v11.1 61 | with: 62 | vcpkgGitCommitId: 5e5d0e1cd7785623065e77eff011afdeec1a3574 63 | 64 | - name: Setup MySQL 65 | if: ${{ matrix.osx_build_arch == 'x86_64'}} 66 | run: | 67 | mysql -u root < test/test_data.sql 68 | mysql -u root -e "SELECT 42" 69 | 70 | - name: Build extension 71 | shell: bash 72 | run: | 73 | make release 74 | 75 | - name: Test Extension 76 | if: ${{ matrix.osx_build_arch == 'x86_64'}} 77 | shell: bash 78 | env: 79 | MYSQL_TEST_DATABASE_AVAILABLE: 1 80 | run: | 81 | make test 82 | 83 | - name: Log Errors 84 | if: failure() 85 | shell: bash 86 | run: | 87 | cat /Users/runner/work/duckdb_mysql/duckdb_mysql/vcpkg/buildtrees/libmysql/*.log 88 | cat /Users/runner/work/duckdb_mysql/duckdb_mysql/build/release/*.log 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "duckdb"] 2 | path = duckdb 3 | url = https://github.com/duckdb/duckdb 4 | [submodule "extension-ci-tools"] 5 | path = extension-ci-tools 6 | url = https://github.com/duckdb/extension-ci-tools.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.0) 2 | set(TARGET_NAME mysql_scanner) 3 | project(${TARGET_NAME}) 4 | 5 | find_package(libmysql REQUIRED) 6 | set(EXTENSION_NAME ${TARGET_NAME}_extension) 7 | 8 | set(MYSQL_INCLUDE_DIR 9 | ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/include/mysql) 10 | include_directories(${MYSQL_INCLUDE_DIR}) 11 | 12 | add_subdirectory(src) 13 | 14 | set(PARAMETERS "-no-warnings") 15 | build_loadable_extension(${TARGET_NAME} ${PARAMETERS} ${ALL_OBJECT_FILES}) 16 | 17 | # Loadable binary 18 | target_include_directories(${TARGET_NAME}_loadable_extension 19 | PRIVATE include ${MYSQL_INCLUDE_DIR}) 20 | target_link_libraries(${TARGET_NAME}_loadable_extension ${MYSQL_LIBRARIES}) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018-2025 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. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | # Configuration of extension 4 | EXT_NAME=mysql_scanner 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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DuckDB MySQL extension 2 | 3 | The MySQL extension allows DuckDB to directly read and write data from a MySQL database instance. The data can be queried directly from the underlying MySQL database. Data can be loaded from MySQL tables into DuckDB tables, or vice versa. 4 | 5 | ## Reading Data from MySQL 6 | 7 | 8 | To make a MySQL database accessible to DuckDB use the `ATTACH` command: 9 | 10 | ```sql 11 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS mysqlscanner (TYPE mysql_scanner); 12 | USE mysqlscanner; 13 | ``` 14 | 15 | The connection string determines the parameters for how to connect to MySQL as a set of `key=value` pairs. Any options not provided are read from the corresponding environment variables if set, and otherwise replaced by their default values, as per the table below. 16 | 17 | | Setting | Description | Environment Variable | Default | 18 | |----------|----------------------------|----------------------|--------------| 19 | | host | Name of host to connect to | `MYSQL_HOST` | localhost | 20 | | user | MySQL user name | `MYSQL_USER` | current_user | 21 | | password | MySQL password | `MYSQL_PWD` | | 22 | | database | Database name | `MYSQL_DATABASE` | NULL | 23 | | port | Port number | `MYSQL_TCP_PORT` | 0 | 24 | | socket | Unix socket file name | `MYSQL_UNIX_PORT` | NULL | 25 | | compress | Compress MySQL packet | `MYSQL_COMPRESS` | 1 | 26 | 27 | 28 | The tables in the file can be read as if they were normal DuckDB tables, but the underlying data is read directly from MySQL at query time. 29 | 30 | ```sql 31 | D SHOW TABLES; 32 | ┌───────────────────────────────────────┐ 33 | │ name │ 34 | │ varchar │ 35 | ├───────────────────────────────────────┤ 36 | │ signed_integers │ 37 | └───────────────────────────────────────┘ 38 | D SELECT * FROM signed_integers; 39 | ┌──────┬────────┬──────────┬─────────────┬──────────────────────┐ 40 | │ t │ s │ m │ i │ b │ 41 | │ int8 │ int16 │ int32 │ int32 │ int64 │ 42 | ├──────┼────────┼──────────┼─────────────┼──────────────────────┤ 43 | │ -128 │ -32768 │ -8388608 │ -2147483648 │ -9223372036854775808 │ 44 | │ 127 │ 32767 │ 8388607 │ 2147483647 │ 9223372036854775807 │ 45 | │ NULL │ NULL │ NULL │ NULL │ NULL │ 46 | └──────┴────────┴──────────┴─────────────┴──────────────────────┘ 47 | ``` 48 | 49 | It might be desirable to create a copy of the MySQL databases in DuckDB to prevent the system from re-reading the tables from MySQL continuously, particularly for large tables. 50 | 51 | Data can be copied over from MySQL to DuckDB using standard SQL, for example: 52 | 53 | ```sql 54 | CREATE TABLE duckdb_table AS FROM mysqlscanner.mysql_table; 55 | ``` 56 | 57 | ## Writing Data to MySQL 58 | 59 | In addition to reading data from MySQL, create tables, ingest data into MySQL and make other modifications to a MySQL database using standard SQL queries. 60 | 61 | This allows you to use DuckDB to, for example, export data that is stored in a MySQL database to Parquet, or read data from a Parquet file into MySQL. 62 | 63 | Below is a brief example of how to create a new table in MySQL and load data into it. 64 | 65 | ```sql 66 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS mysql_db (TYPE mysql_scanner); 67 | CREATE TABLE mysql_db.tbl(id INTEGER, name VARCHAR); 68 | INSERT INTO mysql_db.tbl VALUES (42, 'DuckDB'); 69 | ``` 70 | Many operations on MySQL tables are supported. All these operations directly modify the MySQL database, and the result of subsequent operations can then be read using MySQL. 71 | Note that if modifications are not desired, `ATTACH` can be run with the `READ_ONLY` property which prevents making modifications to the underlying database. For example: 72 | 73 | ```sql 74 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS mysql_db (TYPE mysql_scanner, READ_ONLY); 75 | ``` 76 | 77 | Below is a list of supported operations. 78 | 79 | ###### CREATE TABLE 80 | ```sql 81 | CREATE TABLE mysql_db.tbl(id INTEGER, name VARCHAR); 82 | ``` 83 | 84 | ###### INSERT INTO 85 | ```sql 86 | INSERT INTO mysql_db.tbl VALUES (42, 'DuckDB'); 87 | ``` 88 | 89 | ###### SELECT 90 | ```sql 91 | SELECT * FROM mysql_db.tbl; 92 | ┌───────┬─────────┐ 93 | │ id │ name │ 94 | │ int64 │ varchar │ 95 | ├───────┼─────────┤ 96 | │ 42 │ DuckDB │ 97 | └───────┴─────────┘ 98 | ``` 99 | 100 | ###### COPY 101 | ```sql 102 | COPY mysql_db.tbl TO 'data.parquet'; 103 | COPY mysql_db.tbl FROM 'data.parquet'; 104 | ``` 105 | 106 | ###### UPDATE 107 | ```sql 108 | UPDATE mysql_db.tbl SET name='Woohoo' WHERE id=42; 109 | ``` 110 | 111 | ###### DELETE 112 | ```sql 113 | DELETE FROM mysql_db.tbl WHERE id=42; 114 | ``` 115 | 116 | ###### ALTER TABLE 117 | ```sql 118 | ALTER TABLE mysql_db.tbl ADD COLUMN k INTEGER; 119 | ``` 120 | 121 | ###### DROP TABLE 122 | ```sql 123 | DROP TABLE mysql_db.tbl; 124 | ``` 125 | 126 | ###### CREATE VIEW 127 | ```sql 128 | CREATE VIEW mysql_db.v1 AS SELECT 42; 129 | ``` 130 | 131 | ###### CREATE SCHEMA/DROP SCHEMA 132 | ```sql 133 | CREATE SCHEMA mysql_db.s1; 134 | CREATE TABLE mysql_db.s1.integers(i int); 135 | INSERT INTO mysql_db.s1.integers VALUES (42); 136 | SELECT * FROM mysql_db.s1.integers; 137 | ┌───────┐ 138 | │ i │ 139 | │ int32 │ 140 | ├───────┤ 141 | │ 42 │ 142 | └───────┘ 143 | DROP SCHEMA mysql_db.s1; 144 | ``` 145 | 146 | ###### Transactions 147 | ```sql 148 | CREATE TABLE mysql_db.tmp(i INTEGER); 149 | BEGIN; 150 | INSERT INTO mysql_db.tmp VALUES (42); 151 | SELECT * FROM mysql_db.tmp; 152 | ┌───────┐ 153 | │ i │ 154 | │ int64 │ 155 | ├───────┤ 156 | │ 42 │ 157 | └───────┘ 158 | ROLLBACK; 159 | SELECT * FROM mysql_db.tmp; 160 | ┌────────┐ 161 | │ i │ 162 | │ int64 │ 163 | ├────────┤ 164 | │ 0 rows │ 165 | └────────┘ 166 | ``` 167 | 168 | > Note that DDL statements are not transactional in MySQL. 169 | 170 | ## Settings 171 | | name | description | default | 172 | |------------------------------------|----------------------------------------------------------------|---------| 173 | | mysql_experimental_filter_pushdown | Whether or not to use filter pushdown (currently experimental) | true | 174 | | mysql_tinyint1_as_boolean | Whether or not to convert TINYINT(1) columns to BOOLEAN | true | 175 | | mysql_debug_show_queries | DEBUG SETTING: print all queries sent to MySQL to stdout | false | 176 | | mysql_bit1_as_boolean | Whether or not to convert BIT(1) columns to BOOLEAN | true | 177 | 178 | ## Schema Cache 179 | 180 | To avoid having to continuously fetch schema data from MySQL, DuckDB keeps schema information - such as the names of tables, their columns, etc - cached. If changes are made to the schema through a different connection to the MySQL instance, such as new columns being added to a table, the cached schema information might be outdated. In this case, the function `mysql_clear_cache` can be executed to clear the internal caches. 181 | 182 | ```sql 183 | CALL mysql_clear_cache(); 184 | ``` 185 | 186 | ## Development 187 | 188 | #### Dependencies 189 | 190 | The package depends on `vcpkg`, and has several platform-specific dependencies that must be installed in order for compilation to succeed. 191 | 192 | ##### Submodules 193 | 194 | The DuckDB submodule must be initialized prior to building. 195 | 196 | ```bash 197 | git submodule init 198 | git pull --recurse-submodules 199 | ``` 200 | 201 | ##### vcpkg 202 | 203 | `vcpkg` must be installed and configured for building. For more information, see [here](https://github.com/duckdb/extension-template/tree/main#managing-dependencies). 204 | 205 | ```bash 206 | git clone https://github.com/Microsoft/vcpkg.git 207 | ./vcpkg/bootstrap-vcpkg.sh 208 | export VCPKG_TOOLCHAIN_PATH=`pwd`/vcpkg/scripts/buildsystems/vcpkg.cmake 209 | ``` 210 | 211 | ##### Ubuntu 212 | 213 | ```bash 214 | sudo apt-get install -y ninja-build cmake build-essential make ccache curl zip unzip tar 215 | sudo apt-get install -y pkg-config autoconf autoconf-archive 216 | ``` 217 | 218 | ##### MacOS 219 | 220 | ```bash 221 | brew install pkg-config ninja automake autoconf autoconf-archive libevent 222 | ``` 223 | 224 | ## Building & Loading the Extension 225 | To build, type: 226 | 227 | ``` 228 | make 229 | ``` 230 | 231 | To run, run the bundled `duckdb` shell: 232 | ``` 233 | ./build/release/duckdb -unsigned 234 | ``` 235 | 236 | Then, load the MySQL extension like so: 237 | ```SQL 238 | LOAD 'build/release/extension/mysql_scanner/mysql_scanner.duckdb_extension'; 239 | ``` 240 | 241 | ## Testing 242 | 243 | Tests can be run with the following command: 244 | 245 | ```bash 246 | make test 247 | ``` 248 | 249 | Note that most test will require to have a mysql server running to actually run. To run these tests, setup the mysql server 250 | and set the environment variable `MYSQL_TEST_DATABASE_AVAILABLE=1`. 251 | -------------------------------------------------------------------------------- /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(mysql_scanner 5 | SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR} 6 | LOAD_TESTS 7 | DONT_LINK 8 | ) -------------------------------------------------------------------------------- /mysqlconfigure: -------------------------------------------------------------------------------- 1 | rm -rf mysql 2 | mkdir mysql 3 | curl -s -L https://github.com/mysql/mysql-server/archive/refs/tags/mysql-8.1.0.tar.gz | tar xz --strip-components 1 -C mysql -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(include) 2 | 3 | add_subdirectory(storage) 4 | 5 | add_library( 6 | mysql_ext_library OBJECT 7 | mysql_connection.cpp 8 | mysql_execute.cpp 9 | mysql_extension.cpp 10 | mysql_filter_pushdown.cpp 11 | mysql_scanner.cpp 12 | mysql_storage.cpp 13 | mysql_utils.cpp) 14 | set(ALL_OBJECT_FILES 15 | ${ALL_OBJECT_FILES} $ 16 | PARENT_SCOPE) 17 | -------------------------------------------------------------------------------- /src/include/mysql_connection.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_connection.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/common/shared_ptr.hpp" 12 | #include "duckdb/common/mutex.hpp" 13 | #include "mysql_utils.hpp" 14 | #include "mysql_result.hpp" 15 | 16 | namespace duckdb { 17 | class MySQLBinaryWriter; 18 | class MySQLTextWriter; 19 | struct MySQLBinaryReader; 20 | class MySQLSchemaEntry; 21 | class MySQLTableEntry; 22 | class MySQLStatement; 23 | class MySQLResult; 24 | struct IndexInfo; 25 | 26 | struct OwnedMySQLConnection { 27 | explicit OwnedMySQLConnection(MYSQL *conn = nullptr) : connection(conn) { 28 | } 29 | ~OwnedMySQLConnection() { 30 | if (!connection) { 31 | return; 32 | } 33 | mysql_close(connection); 34 | connection = nullptr; 35 | } 36 | 37 | MYSQL *connection; 38 | }; 39 | 40 | class MySQLConnection { 41 | public: 42 | explicit MySQLConnection(shared_ptr connection = nullptr); 43 | ~MySQLConnection(); 44 | // disable copy constructors 45 | MySQLConnection(const MySQLConnection &other) = delete; 46 | MySQLConnection &operator=(const MySQLConnection &) = delete; 47 | //! enable move constructors 48 | MySQLConnection(MySQLConnection &&other) noexcept; 49 | MySQLConnection &operator=(MySQLConnection &&) noexcept; 50 | 51 | public: 52 | static MySQLConnection Open(const string &connection_string); 53 | void Execute(const string &query); 54 | unique_ptr Query(const string &query, optional_ptr context = nullptr); 55 | 56 | vector GetIndexInfo(const string &table_name); 57 | 58 | bool IsOpen(); 59 | void Close(); 60 | 61 | shared_ptr GetConnection() { 62 | return connection; 63 | } 64 | string GetDSN() { 65 | return dsn; 66 | } 67 | 68 | MYSQL *GetConn() { 69 | if (!connection || !connection->connection) { 70 | throw InternalException("MySQLConnection::GetConn - no connection available"); 71 | } 72 | return connection->connection; 73 | } 74 | 75 | static void DebugSetPrintQueries(bool print); 76 | static bool DebugPrintQueries(); 77 | 78 | private: 79 | MYSQL_RES *MySQLExecute(const string &query); 80 | 81 | mutex query_lock; 82 | shared_ptr connection; 83 | string dsn; 84 | }; 85 | 86 | } // namespace duckdb 87 | -------------------------------------------------------------------------------- /src/include/mysql_filter_pushdown.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_filter_pushdown.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/planner/table_filter.hpp" 12 | #include "duckdb/planner/filter/conjunction_filter.hpp" 13 | #include "duckdb/planner/filter/constant_filter.hpp" 14 | 15 | namespace duckdb { 16 | 17 | class MySQLFilterPushdown { 18 | public: 19 | static string TransformFilters(const vector &column_ids, optional_ptr filters, 20 | const vector &names); 21 | 22 | private: 23 | static string TransformFilter(string &column_name, TableFilter &filter); 24 | static string TransformComparison(ExpressionType type); 25 | static string CreateExpression(string &column_name, vector> &filters, string op); 26 | static string TransformConstant(const Value &val); 27 | }; 28 | 29 | } // namespace duckdb 30 | -------------------------------------------------------------------------------- /src/include/mysql_result.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_result.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "mysql_utils.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct MySQLField { 16 | string name; 17 | LogicalType type; 18 | }; 19 | 20 | class MySQLResult { 21 | public: 22 | MySQLResult(MYSQL_RES *res_p, idx_t field_count) : res(res_p), field_count(field_count) { 23 | } 24 | MySQLResult(MYSQL_RES *res_p, vector fields_p) 25 | : res(res_p), field_count(fields_p.size()), fields(std::move(fields_p)) { 26 | } 27 | MySQLResult(idx_t affected_rows) : affected_rows(affected_rows) { 28 | } 29 | ~MySQLResult() { 30 | if (res) { 31 | mysql_free_result(res); 32 | } 33 | } 34 | 35 | public: 36 | string GetString(idx_t col) { 37 | D_ASSERT(res); 38 | return string(GetNonNullValue(col), lengths[col]); 39 | } 40 | string_t GetStringT(idx_t col) { 41 | D_ASSERT(res); 42 | return string_t(GetNonNullValue(col), lengths[col]); 43 | } 44 | int32_t GetInt32(idx_t col) { 45 | return atoi(GetNonNullValue(col)); 46 | } 47 | int64_t GetInt64(idx_t col) { 48 | return atoll(GetNonNullValue(col)); 49 | } 50 | bool GetBool(idx_t col) { 51 | return strcmp(GetNonNullValue(col), "t"); 52 | } 53 | bool IsNull(idx_t col) { 54 | return !GetValueInternal(col); 55 | } 56 | bool Next() { 57 | if (!res) { 58 | throw InternalException("MySQLResult::Next called without result"); 59 | } 60 | mysql_row = mysql_fetch_row(res); 61 | lengths = mysql_fetch_lengths(res); 62 | return mysql_row; 63 | } 64 | idx_t AffectedRows() { 65 | if (affected_rows == idx_t(-1)) { 66 | throw InternalException("MySQLResult::AffectedRows called for result " 67 | "that didn't affect any rows"); 68 | } 69 | return affected_rows; 70 | } 71 | idx_t ColumnCount() { 72 | return field_count; 73 | } 74 | const vector &Fields() { 75 | return fields; 76 | } 77 | 78 | private: 79 | MYSQL_RES *res = nullptr; 80 | idx_t affected_rows = idx_t(-1); 81 | MYSQL_ROW mysql_row = nullptr; 82 | unsigned long *lengths = nullptr; 83 | idx_t field_count = 0; 84 | vector fields; 85 | 86 | char *GetNonNullValue(idx_t col) { 87 | auto val = GetValueInternal(col); 88 | if (!val) { 89 | throw InternalException("MySQLResult::GetNonNullValue called for a NULL value"); 90 | } 91 | return val; 92 | } 93 | 94 | char *GetValueInternal(idx_t col) { 95 | if (!mysql_row) { 96 | throw InternalException("MySQLResult::GetValueInternal called without row"); 97 | } 98 | if (col >= field_count) { 99 | throw InternalException("MySQLResult::GetValueInternal row out of range of field count"); 100 | } 101 | return mysql_row[col]; 102 | } 103 | }; 104 | 105 | } // namespace duckdb 106 | -------------------------------------------------------------------------------- /src/include/mysql_scanner.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_scanner.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb.hpp" 12 | #include "mysql_utils.hpp" 13 | #include "mysql_connection.hpp" 14 | 15 | namespace duckdb { 16 | class MySQLTableEntry; 17 | class MySQLTransaction; 18 | 19 | struct MySQLBindData : public FunctionData { 20 | explicit MySQLBindData(MySQLTableEntry &table) : table(table) { 21 | } 22 | 23 | MySQLTableEntry &table; 24 | vector mysql_types; 25 | vector names; 26 | vector types; 27 | string limit; 28 | 29 | public: 30 | unique_ptr Copy() const override { 31 | throw NotImplementedException("MySQLBindData copy not supported"); 32 | } 33 | bool Equals(const FunctionData &other_p) const override { 34 | return false; 35 | } 36 | }; 37 | 38 | class MySQLScanFunction : public TableFunction { 39 | public: 40 | MySQLScanFunction(); 41 | }; 42 | 43 | class MySQLQueryFunction : public TableFunction { 44 | public: 45 | MySQLQueryFunction(); 46 | }; 47 | 48 | class MySQLClearCacheFunction : public TableFunction { 49 | public: 50 | MySQLClearCacheFunction(); 51 | 52 | static void ClearCacheOnSetting(ClientContext &context, SetScope scope, Value ¶meter); 53 | }; 54 | 55 | class MySQLExecuteFunction : public TableFunction { 56 | public: 57 | MySQLExecuteFunction(); 58 | }; 59 | 60 | } // namespace duckdb 61 | -------------------------------------------------------------------------------- /src/include/mysql_scanner_extension.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUCKDB_BUILD_LOADABLE_EXTENSION 2 | #define DUCKDB_BUILD_LOADABLE_EXTENSION 3 | #endif 4 | #include "duckdb.hpp" 5 | 6 | #include "duckdb/catalog/catalog.hpp" 7 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 8 | 9 | using namespace duckdb; 10 | 11 | class MysqlScannerExtension : public Extension { 12 | public: 13 | std::string Name() override { 14 | return "mysql_scanner"; 15 | } 16 | void Load(DuckDB &db) override; 17 | }; 18 | 19 | extern "C" { 20 | DUCKDB_EXTENSION_API void mysql_scanner_init(duckdb::DatabaseInstance &db); 21 | DUCKDB_EXTENSION_API const char *mysql_scanner_version(); 22 | DUCKDB_EXTENSION_API void mysql_scanner_storage_init(DBConfig &config); 23 | } 24 | -------------------------------------------------------------------------------- /src/include/mysql_storage.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_storage.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/storage/storage_extension.hpp" 12 | 13 | namespace duckdb { 14 | 15 | class MySQLStorageExtension : public StorageExtension { 16 | public: 17 | MySQLStorageExtension(); 18 | }; 19 | 20 | } // namespace duckdb 21 | -------------------------------------------------------------------------------- /src/include/mysql_text_writer.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_text_writer.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb.hpp" 12 | #include "duckdb/common/types/interval.hpp" 13 | #include "duckdb/common/serializer/memory_stream.hpp" 14 | 15 | namespace duckdb { 16 | 17 | class MySQLTextWriter { 18 | public: 19 | void WriteNull() { 20 | stream.WriteData(const_data_ptr_cast("\b"), 1); 21 | } 22 | 23 | void WriteChar(char c) { 24 | switch (c) { 25 | case '\n': 26 | WriteChar('\\'); 27 | WriteChar('n'); 28 | break; 29 | case '\r': 30 | WriteChar('\\'); 31 | WriteChar('r'); 32 | break; 33 | case '\b': 34 | WriteChar('\\'); 35 | WriteChar('b'); 36 | break; 37 | case '\f': 38 | WriteChar('\\'); 39 | WriteChar('f'); 40 | break; 41 | case '\t': 42 | WriteChar('\\'); 43 | WriteChar('t'); 44 | break; 45 | case '\v': 46 | WriteChar('\\'); 47 | WriteChar('v'); 48 | break; 49 | default: 50 | stream.WriteData(const_data_ptr_cast(&c), 1); 51 | break; 52 | } 53 | } 54 | 55 | void WriteVarchar(string_t value) { 56 | auto size = value.GetSize(); 57 | auto data = value.GetData(); 58 | for (idx_t c = 0; c < size; c++) { 59 | WriteChar(data[c]); 60 | } 61 | } 62 | 63 | void WriteValue(Vector &col, idx_t r) { 64 | if (col.GetType().id() != LogicalTypeId::VARCHAR) { 65 | throw InternalException("Text format can only write VARCHAR columns"); 66 | } 67 | if (FlatVector::IsNull(col, r)) { 68 | WriteNull(); 69 | } else { 70 | WriteVarchar(FlatVector::GetData(col)[r]); 71 | } 72 | } 73 | 74 | void WriteSeparator() { 75 | stream.WriteData(const_data_ptr_cast("\t"), 1); 76 | } 77 | 78 | void FinishRow() { 79 | stream.WriteData(const_data_ptr_cast("\n"), 1); 80 | } 81 | 82 | void WriteFooter() { 83 | stream.WriteData(const_data_ptr_cast("\\.\n"), 3); 84 | } 85 | 86 | public: 87 | MemoryStream stream; 88 | }; 89 | 90 | } // namespace duckdb 91 | -------------------------------------------------------------------------------- /src/include/mysql_utils.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // mysql_utils.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb.hpp" 12 | #include "mysql.h" 13 | 14 | namespace duckdb { 15 | class MySQLSchemaEntry; 16 | class MySQLTransaction; 17 | 18 | struct MySQLTypeData { 19 | string type_name; 20 | string column_type; 21 | int64_t precision; 22 | int64_t scale; 23 | }; 24 | 25 | enum class MySQLTypeAnnotation { STANDARD, CAST_TO_VARCHAR, NUMERIC_AS_DOUBLE, CTID, JSONB, FIXED_LENGTH_CHAR }; 26 | 27 | struct MySQLType { 28 | idx_t oid = 0; 29 | MySQLTypeAnnotation info = MySQLTypeAnnotation::STANDARD; 30 | vector children; 31 | }; 32 | 33 | struct MySQLConnectionParameters { 34 | string host; 35 | string user; 36 | string passwd; 37 | string db; 38 | uint32_t port = 0; 39 | string unix_socket; 40 | idx_t client_flag = CLIENT_COMPRESS | CLIENT_IGNORE_SIGPIPE | CLIENT_MULTI_STATEMENTS; 41 | unsigned int ssl_mode = SSL_MODE_PREFERRED; 42 | string ssl_ca; 43 | string ssl_ca_path; 44 | string ssl_cert; 45 | string ssl_cipher; 46 | string ssl_crl; 47 | string ssl_crl_path; 48 | string ssl_key; 49 | }; 50 | 51 | class MySQLUtils { 52 | public: 53 | static std::tuple> ParseConnectionParameters(const string &dsn); 54 | static MYSQL *Connect(const string &dsn); 55 | 56 | static LogicalType ToMySQLType(const LogicalType &input); 57 | static LogicalType TypeToLogicalType(ClientContext &context, const MySQLTypeData &input); 58 | static LogicalType FieldToLogicalType(ClientContext &context, MYSQL_FIELD *field); 59 | static string TypeToString(const LogicalType &input); 60 | 61 | static string WriteIdentifier(const string &identifier); 62 | static string WriteLiteral(const string &identifier); 63 | static string EscapeQuotes(const string &text, char quote); 64 | static string WriteQuoted(const string &text, char quote); 65 | }; 66 | 67 | } // namespace duckdb 68 | -------------------------------------------------------------------------------- /src/include/mysql_version.h: -------------------------------------------------------------------------------- 1 | /* Copyright Abandoned 1996,1999 TCX DataKonsult AB & Monty Program KB 2 | & Detron HB, 1996, 1999-2004, 2007 MySQL AB. 3 | This file is public domain and comes with NO WARRANTY of any kind 4 | */ 5 | 6 | /* Version numbers for protocol & mysqld */ 7 | 8 | #ifndef _mysql_version_h 9 | #define _mysql_version_h 10 | 11 | #define PROTOCOL_VERSION 10 12 | #define MYSQL_SERVER_VERSION "8.1.0" 13 | #define MYSQL_BASE_VERSION "mysqld-8.1" 14 | #define MYSQL_SERVER_SUFFIX_DEF "" 15 | #define MYSQL_VERSION_ID 80100 16 | #define MYSQL_PORT 3306 17 | #define MYSQL_ADMIN_PORT 33062 18 | #define MYSQL_PORT_DEFAULT 0 19 | #define MYSQL_UNIX_ADDR "/tmp/mysql.sock" 20 | #define MYSQL_CONFIG_NAME "my" 21 | #define MYSQL_PERSIST_CONFIG_NAME "mysqld-auto" 22 | #define MYSQL_COMPILATION_COMMENT "Source distribution" 23 | #define MYSQL_COMPILATION_COMMENT_SERVER "Source distribution" 24 | #define LIBMYSQL_VERSION "8.1.0" 25 | #define LIBMYSQL_VERSION_ID 80100 26 | 27 | #ifndef LICENSE 28 | #define LICENSE GPL 29 | #endif /* LICENSE */ 30 | 31 | #endif /* _mysql_version_h */ 32 | -------------------------------------------------------------------------------- /src/include/storage/mysql_catalog.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_catalog.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/catalog/catalog.hpp" 12 | #include "duckdb/common/enums/access_mode.hpp" 13 | #include "mysql_connection.hpp" 14 | #include "storage/mysql_schema_set.hpp" 15 | 16 | namespace duckdb { 17 | class MySQLSchemaEntry; 18 | 19 | class MySQLCatalog : public Catalog { 20 | public: 21 | explicit MySQLCatalog(AttachedDatabase &db_p, string connection_string, string attach_path, AccessMode access_mode); 22 | ~MySQLCatalog(); 23 | 24 | string connection_string; 25 | string attach_path; 26 | AccessMode access_mode; 27 | 28 | public: 29 | void Initialize(bool load_builtin) override; 30 | string GetCatalogType() override { 31 | return "mysql"; 32 | } 33 | 34 | static string GetConnectionString(ClientContext &context, const string &attach_path, string secret_name); 35 | 36 | optional_ptr CreateSchema(CatalogTransaction transaction, CreateSchemaInfo &info) override; 37 | 38 | void ScanSchemas(ClientContext &context, std::function callback) override; 39 | 40 | optional_ptr LookupSchema(CatalogTransaction transaction, const EntryLookupInfo &schema_lookup, 41 | OnEntryNotFound if_not_found) override; 42 | 43 | PhysicalOperator &PlanCreateTableAs(ClientContext &context, PhysicalPlanGenerator &planner, LogicalCreateTable &op, 44 | PhysicalOperator &plan) override; 45 | PhysicalOperator &PlanInsert(ClientContext &context, PhysicalPlanGenerator &planner, LogicalInsert &op, 46 | optional_ptr plan) override; 47 | PhysicalOperator &PlanDelete(ClientContext &context, PhysicalPlanGenerator &planner, LogicalDelete &op, 48 | PhysicalOperator &plan) override; 49 | PhysicalOperator &PlanUpdate(ClientContext &context, PhysicalPlanGenerator &planner, LogicalUpdate &op, 50 | PhysicalOperator &plan) override; 51 | 52 | unique_ptr BindCreateIndex(Binder &binder, CreateStatement &stmt, TableCatalogEntry &table, 53 | unique_ptr plan) override; 54 | 55 | DatabaseSize GetDatabaseSize(ClientContext &context) override; 56 | 57 | //! Whether or not this is an in-memory MySQL database 58 | bool InMemory() override; 59 | string GetDBPath() override; 60 | 61 | void ClearCache(); 62 | 63 | private: 64 | void DropSchema(ClientContext &context, DropInfo &info) override; 65 | 66 | private: 67 | MySQLSchemaSet schemas; 68 | string default_schema; 69 | }; 70 | 71 | } // namespace duckdb 72 | -------------------------------------------------------------------------------- /src/include/storage/mysql_catalog_set.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_catalog_set.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/transaction/transaction.hpp" 12 | #include "duckdb/common/case_insensitive_map.hpp" 13 | #include "duckdb/common/mutex.hpp" 14 | 15 | namespace duckdb { 16 | struct DropInfo; 17 | class MySQLSchemaEntry; 18 | class MySQLTransaction; 19 | 20 | class MySQLCatalogSet { 21 | public: 22 | MySQLCatalogSet(Catalog &catalog); 23 | 24 | optional_ptr GetEntry(ClientContext &context, const string &name); 25 | virtual void DropEntry(ClientContext &context, DropInfo &info); 26 | void Scan(ClientContext &context, const std::function &callback); 27 | virtual optional_ptr CreateEntry(unique_ptr entry); 28 | void ClearEntries(); 29 | 30 | protected: 31 | virtual void LoadEntries(ClientContext &context) = 0; 32 | 33 | void EraseEntryInternal(const string &name); 34 | 35 | protected: 36 | Catalog &catalog; 37 | 38 | private: 39 | mutex entry_lock; 40 | case_insensitive_map_t> entries; 41 | bool is_loaded; 42 | }; 43 | 44 | class MySQLInSchemaSet : public MySQLCatalogSet { 45 | public: 46 | MySQLInSchemaSet(MySQLSchemaEntry &schema); 47 | 48 | optional_ptr CreateEntry(unique_ptr entry) override; 49 | 50 | protected: 51 | MySQLSchemaEntry &schema; 52 | }; 53 | 54 | } // namespace duckdb 55 | -------------------------------------------------------------------------------- /src/include/storage/mysql_execute_query.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_delete.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/execution/physical_operator.hpp" 12 | 13 | namespace duckdb { 14 | class TableCatalogEntry; 15 | 16 | class MySQLExecuteQuery : public PhysicalOperator { 17 | public: 18 | MySQLExecuteQuery(LogicalOperator &op, string op_name, TableCatalogEntry &table, string query); 19 | 20 | //! The table to delete from 21 | string op_name; 22 | TableCatalogEntry &table; 23 | string query; 24 | 25 | public: 26 | // Source interface 27 | SourceResultType GetData(ExecutionContext &context, DataChunk &chunk, OperatorSourceInput &input) const override; 28 | 29 | bool IsSource() const override { 30 | return true; 31 | } 32 | 33 | public: 34 | // Sink interface 35 | unique_ptr GetGlobalSinkState(ClientContext &context) const override; 36 | SinkResultType Sink(ExecutionContext &context, DataChunk &chunk, OperatorSinkInput &input) const override; 37 | SinkFinalizeType Finalize(Pipeline &pipeline, Event &event, ClientContext &context, 38 | OperatorSinkFinalizeInput &input) const override; 39 | 40 | bool IsSink() const override { 41 | return true; 42 | } 43 | 44 | bool ParallelSink() const override { 45 | return false; 46 | } 47 | 48 | string GetName() const override; 49 | InsertionOrderPreservingMap ParamsToString() const override; 50 | }; 51 | 52 | } // namespace duckdb 53 | -------------------------------------------------------------------------------- /src/include/storage/mysql_index.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_index.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/execution/physical_operator.hpp" 12 | #include "duckdb/parser/parsed_data/create_index_info.hpp" 13 | 14 | namespace duckdb { 15 | 16 | //! PhysicalCreateSequence represents a CREATE SEQUENCE command 17 | class MySQLCreateIndex : public PhysicalOperator { 18 | public: 19 | explicit MySQLCreateIndex(unique_ptr info, TableCatalogEntry &table); 20 | 21 | unique_ptr info; 22 | TableCatalogEntry &table; 23 | 24 | public: 25 | // Source interface 26 | SourceResultType GetData(ExecutionContext &context, DataChunk &chunk, OperatorSourceInput &input) const override; 27 | 28 | bool IsSource() const override { 29 | return true; 30 | } 31 | }; 32 | 33 | } // namespace duckdb 34 | -------------------------------------------------------------------------------- /src/include/storage/mysql_index_entry.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_index_entry.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/catalog/catalog_entry/index_catalog_entry.hpp" 12 | 13 | namespace duckdb { 14 | 15 | class MySQLIndexEntry : public IndexCatalogEntry { 16 | public: 17 | MySQLIndexEntry(Catalog &catalog, SchemaCatalogEntry &schema, CreateIndexInfo &info, string table_name); 18 | 19 | string table_name; 20 | 21 | public: 22 | string GetSchemaName() const override; 23 | string GetTableName() const override; 24 | }; 25 | 26 | } // namespace duckdb 27 | -------------------------------------------------------------------------------- /src/include/storage/mysql_index_set.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_index_set.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "storage/mysql_catalog_set.hpp" 12 | #include "storage/mysql_index_entry.hpp" 13 | 14 | namespace duckdb { 15 | class MySQLSchemaEntry; 16 | 17 | class MySQLIndexSet : public MySQLInSchemaSet { 18 | public: 19 | MySQLIndexSet(MySQLSchemaEntry &schema); 20 | 21 | void DropEntry(ClientContext &context, DropInfo &info) override; 22 | 23 | protected: 24 | void LoadEntries(ClientContext &context) override; 25 | }; 26 | 27 | } // namespace duckdb 28 | -------------------------------------------------------------------------------- /src/include/storage/mysql_insert.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_insert.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/execution/physical_operator.hpp" 12 | #include "duckdb/common/index_vector.hpp" 13 | 14 | namespace duckdb { 15 | 16 | class MySQLInsert : public PhysicalOperator { 17 | public: 18 | //! INSERT INTO 19 | MySQLInsert(LogicalOperator &op, TableCatalogEntry &table, physical_index_vector_t column_index_map); 20 | //! CREATE TABLE AS 21 | MySQLInsert(LogicalOperator &op, SchemaCatalogEntry &schema, unique_ptr info); 22 | 23 | //! The table to insert into 24 | optional_ptr table; 25 | //! Table schema, in case of CREATE TABLE AS 26 | optional_ptr schema; 27 | //! Create table info, in case of CREATE TABLE AS 28 | unique_ptr info; 29 | //! column_index_map 30 | physical_index_vector_t column_index_map; 31 | 32 | public: 33 | // Source interface 34 | SourceResultType GetData(ExecutionContext &context, DataChunk &chunk, OperatorSourceInput &input) const override; 35 | 36 | bool IsSource() const override { 37 | return true; 38 | } 39 | 40 | public: 41 | // Sink interface 42 | unique_ptr GetGlobalSinkState(ClientContext &context) const override; 43 | SinkResultType Sink(ExecutionContext &context, DataChunk &chunk, OperatorSinkInput &input) const override; 44 | SinkFinalizeType Finalize(Pipeline &pipeline, Event &event, ClientContext &context, 45 | OperatorSinkFinalizeInput &input) const override; 46 | 47 | bool IsSink() const override { 48 | return true; 49 | } 50 | 51 | bool ParallelSink() const override { 52 | return false; 53 | } 54 | 55 | string GetName() const override; 56 | InsertionOrderPreservingMap ParamsToString() const override; 57 | }; 58 | 59 | } // namespace duckdb 60 | -------------------------------------------------------------------------------- /src/include/storage/mysql_optimizer.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_optimizer.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/main/config.hpp" 12 | 13 | namespace duckdb { 14 | class MySQLOptimizer { 15 | public: 16 | static void Optimize(OptimizerExtensionInput &input, unique_ptr &plan); 17 | }; 18 | 19 | } // namespace duckdb 20 | -------------------------------------------------------------------------------- /src/include/storage/mysql_schema_entry.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_schema_entry.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/catalog/catalog_entry/schema_catalog_entry.hpp" 12 | #include "storage/mysql_table_set.hpp" 13 | #include "storage/mysql_index_set.hpp" 14 | 15 | namespace duckdb { 16 | class MySQLTransaction; 17 | 18 | class MySQLSchemaEntry : public SchemaCatalogEntry { 19 | public: 20 | MySQLSchemaEntry(Catalog &catalog, CreateSchemaInfo &info); 21 | 22 | public: 23 | optional_ptr CreateTable(CatalogTransaction transaction, BoundCreateTableInfo &info) override; 24 | optional_ptr CreateFunction(CatalogTransaction transaction, CreateFunctionInfo &info) override; 25 | optional_ptr CreateIndex(CatalogTransaction transaction, CreateIndexInfo &info, 26 | TableCatalogEntry &table) override; 27 | optional_ptr CreateView(CatalogTransaction transaction, CreateViewInfo &info) override; 28 | optional_ptr CreateSequence(CatalogTransaction transaction, CreateSequenceInfo &info) override; 29 | optional_ptr CreateTableFunction(CatalogTransaction transaction, 30 | CreateTableFunctionInfo &info) override; 31 | optional_ptr CreateCopyFunction(CatalogTransaction transaction, 32 | CreateCopyFunctionInfo &info) override; 33 | optional_ptr CreatePragmaFunction(CatalogTransaction transaction, 34 | CreatePragmaFunctionInfo &info) override; 35 | optional_ptr CreateCollation(CatalogTransaction transaction, CreateCollationInfo &info) override; 36 | optional_ptr CreateType(CatalogTransaction transaction, CreateTypeInfo &info) override; 37 | void Alter(CatalogTransaction transaction, AlterInfo &info) override; 38 | void Scan(ClientContext &context, CatalogType type, const std::function &callback) override; 39 | void Scan(CatalogType type, const std::function &callback) override; 40 | void DropEntry(ClientContext &context, DropInfo &info) override; 41 | optional_ptr LookupEntry(CatalogTransaction transaction, const EntryLookupInfo &lookup_info) override; 42 | 43 | private: 44 | void AlterTable(MySQLTransaction &transaction, RenameTableInfo &info); 45 | void AlterTable(MySQLTransaction &transaction, RenameColumnInfo &info); 46 | void AlterTable(MySQLTransaction &transaction, AddColumnInfo &info); 47 | void AlterTable(MySQLTransaction &transaction, RemoveColumnInfo &info); 48 | 49 | void TryDropEntry(ClientContext &context, CatalogType catalog_type, const string &name); 50 | 51 | MySQLCatalogSet &GetCatalogSet(CatalogType type); 52 | 53 | private: 54 | MySQLTableSet tables; 55 | MySQLIndexSet indexes; 56 | }; 57 | 58 | } // namespace duckdb 59 | -------------------------------------------------------------------------------- /src/include/storage/mysql_schema_set.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_schema_set.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "storage/mysql_catalog_set.hpp" 12 | #include "storage/mysql_schema_entry.hpp" 13 | 14 | namespace duckdb { 15 | struct CreateSchemaInfo; 16 | 17 | class MySQLSchemaSet : public MySQLCatalogSet { 18 | public: 19 | explicit MySQLSchemaSet(Catalog &catalog); 20 | 21 | public: 22 | optional_ptr CreateSchema(ClientContext &context, CreateSchemaInfo &info); 23 | 24 | protected: 25 | void LoadEntries(ClientContext &context) override; 26 | }; 27 | 28 | } // namespace duckdb 29 | -------------------------------------------------------------------------------- /src/include/storage/mysql_table_entry.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_table_entry.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp" 12 | #include "duckdb/parser/parsed_data/create_table_info.hpp" 13 | #include "mysql_utils.hpp" 14 | 15 | namespace duckdb { 16 | 17 | struct MySQLTableInfo { 18 | MySQLTableInfo() { 19 | create_info = make_uniq(); 20 | } 21 | MySQLTableInfo(const string &schema, const string &table) { 22 | create_info = make_uniq(string(), schema, table); 23 | } 24 | MySQLTableInfo(const SchemaCatalogEntry &schema, const string &table) { 25 | create_info = make_uniq((SchemaCatalogEntry &)schema, table); 26 | } 27 | 28 | const string &GetTableName() const { 29 | return create_info->table; 30 | } 31 | 32 | unique_ptr create_info; 33 | }; 34 | 35 | class MySQLTableEntry : public TableCatalogEntry { 36 | public: 37 | MySQLTableEntry(Catalog &catalog, SchemaCatalogEntry &schema, CreateTableInfo &info); 38 | MySQLTableEntry(Catalog &catalog, SchemaCatalogEntry &schema, MySQLTableInfo &info); 39 | 40 | public: 41 | unique_ptr GetStatistics(ClientContext &context, column_t column_id) override; 42 | 43 | TableFunction GetScanFunction(ClientContext &context, unique_ptr &bind_data) override; 44 | 45 | TableStorageInfo GetStorageInfo(ClientContext &context) override; 46 | 47 | void BindUpdateConstraints(Binder &binder, LogicalGet &get, LogicalProjection &proj, LogicalUpdate &update, 48 | ClientContext &context) override; 49 | }; 50 | 51 | } // namespace duckdb 52 | -------------------------------------------------------------------------------- /src/include/storage/mysql_table_set.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_table_set.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "storage/mysql_catalog_set.hpp" 12 | #include "storage/mysql_table_entry.hpp" 13 | 14 | namespace duckdb { 15 | struct CreateTableInfo; 16 | class MySQLConnection; 17 | class MySQLResult; 18 | class MySQLSchemaEntry; 19 | 20 | class MySQLTableSet : public MySQLInSchemaSet { 21 | public: 22 | explicit MySQLTableSet(MySQLSchemaEntry &schema); 23 | 24 | public: 25 | optional_ptr CreateTable(ClientContext &context, BoundCreateTableInfo &info); 26 | 27 | static unique_ptr GetTableInfo(ClientContext &context, MySQLSchemaEntry &schema, 28 | const string &table_name); 29 | optional_ptr RefreshTable(ClientContext &context, const string &table_name); 30 | 31 | void AlterTable(ClientContext &context, AlterTableInfo &info); 32 | 33 | protected: 34 | void LoadEntries(ClientContext &context) override; 35 | 36 | void AlterTable(ClientContext &context, RenameTableInfo &info); 37 | void AlterTable(ClientContext &context, RenameColumnInfo &info); 38 | void AlterTable(ClientContext &context, AddColumnInfo &info); 39 | void AlterTable(ClientContext &context, RemoveColumnInfo &info); 40 | 41 | static void AddColumn(ClientContext &context, MySQLResult &result, MySQLTableInfo &table_info, 42 | idx_t column_offset = 0); 43 | }; 44 | 45 | } // namespace duckdb 46 | -------------------------------------------------------------------------------- /src/include/storage/mysql_transaction.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_transaction.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/transaction/transaction.hpp" 12 | #include "mysql_connection.hpp" 13 | 14 | namespace duckdb { 15 | class MySQLCatalog; 16 | class MySQLSchemaEntry; 17 | class MySQLTableEntry; 18 | 19 | enum class MySQLTransactionState { TRANSACTION_NOT_YET_STARTED, TRANSACTION_STARTED, TRANSACTION_FINISHED }; 20 | 21 | class MySQLTransaction : public Transaction { 22 | public: 23 | MySQLTransaction(MySQLCatalog &mysql_catalog, TransactionManager &manager, ClientContext &context); 24 | ~MySQLTransaction() override; 25 | 26 | void Start(); 27 | void Commit(); 28 | void Rollback(); 29 | 30 | MySQLConnection &GetConnection(); 31 | unique_ptr Query(const string &query); 32 | static MySQLTransaction &Get(ClientContext &context, Catalog &catalog); 33 | AccessMode GetAccessMode() const { 34 | return access_mode; 35 | } 36 | 37 | private: 38 | MySQLConnection connection; 39 | MySQLTransactionState transaction_state; 40 | AccessMode access_mode; 41 | }; 42 | 43 | } // namespace duckdb 44 | -------------------------------------------------------------------------------- /src/include/storage/mysql_transaction_manager.hpp: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // DuckDB 3 | // 4 | // storage/mysql_transaction_manager.hpp 5 | // 6 | // 7 | //===----------------------------------------------------------------------===// 8 | 9 | #pragma once 10 | 11 | #include "duckdb/transaction/transaction_manager.hpp" 12 | #include "storage/mysql_catalog.hpp" 13 | #include "storage/mysql_transaction.hpp" 14 | 15 | namespace duckdb { 16 | 17 | class MySQLTransactionManager : public TransactionManager { 18 | public: 19 | MySQLTransactionManager(AttachedDatabase &db_p, MySQLCatalog &mysql_catalog); 20 | 21 | Transaction &StartTransaction(ClientContext &context) override; 22 | ErrorData CommitTransaction(ClientContext &context, Transaction &transaction) override; 23 | void RollbackTransaction(Transaction &transaction) override; 24 | 25 | void Checkpoint(ClientContext &context, bool force = false) override; 26 | 27 | private: 28 | MySQLCatalog &mysql_catalog; 29 | mutex transaction_lock; 30 | reference_map_t> transactions; 31 | }; 32 | 33 | } // namespace duckdb 34 | -------------------------------------------------------------------------------- /src/mysql_connection.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb/storage/table_storage_info.hpp" 2 | #include "duckdb/parser/column_list.hpp" 3 | #include "duckdb/parser/parser.hpp" 4 | #include "mysql_connection.hpp" 5 | #include "duckdb/common/types/uuid.hpp" 6 | 7 | namespace duckdb { 8 | 9 | static bool debug_mysql_print_queries = false; 10 | 11 | MySQLConnection::MySQLConnection(shared_ptr connection_p) : connection(std::move(connection_p)) { 12 | } 13 | 14 | MySQLConnection::~MySQLConnection() { 15 | Close(); 16 | } 17 | 18 | MySQLConnection::MySQLConnection(MySQLConnection &&other) noexcept { 19 | std::swap(connection, other.connection); 20 | std::swap(dsn, other.dsn); 21 | } 22 | 23 | MySQLConnection &MySQLConnection::operator=(MySQLConnection &&other) noexcept { 24 | std::swap(connection, other.connection); 25 | std::swap(dsn, other.dsn); 26 | return *this; 27 | } 28 | 29 | MySQLConnection MySQLConnection::Open(const string &connection_string) { 30 | MySQLConnection result; 31 | result.connection = make_shared_ptr(MySQLUtils::Connect(connection_string)); 32 | result.dsn = connection_string; 33 | return result; 34 | } 35 | 36 | MYSQL_RES *MySQLConnection::MySQLExecute(const string &query) { 37 | if (MySQLConnection::DebugPrintQueries()) { 38 | Printer::Print(query + "\n"); 39 | } 40 | auto con = GetConn(); 41 | lock_guard l(query_lock); 42 | int res = mysql_real_query(con, query.c_str(), query.size()); 43 | if (res != 0) { 44 | throw IOException("Failed to run query \"%s\": %s\n", query.c_str(), mysql_error(con)); 45 | } 46 | return mysql_store_result(con); 47 | } 48 | 49 | unique_ptr MySQLConnection::Query(const string &query, optional_ptr context) { 50 | auto con = GetConn(); 51 | auto result = MySQLExecute(query); 52 | auto field_count = mysql_field_count(con); 53 | if (!result) { 54 | // no result set 55 | // this can happen in case of a statement like CREATE TABLE, INSERT, etc 56 | // check if this is the case with mysql_field_count 57 | if (field_count != 0) { 58 | // no result but we expected a result 59 | throw IOException("Failed to fetch result for query \"%s\": %s\n", query.c_str(), mysql_error(con)); 60 | } 61 | // get the affected rows 62 | return make_uniq(mysql_affected_rows(con)); 63 | } else { 64 | // result set 65 | if (!context) { 66 | return make_uniq(result, field_count); 67 | } 68 | vector fields; 69 | for (idx_t i = 0; i < field_count; i++) { 70 | auto field = mysql_fetch_field_direct(result, i); 71 | MySQLField mysql_field; 72 | if (field->name && field->name_length > 0) { 73 | mysql_field.name = string(field->name, field->name_length); 74 | } 75 | mysql_field.type = MySQLUtils::FieldToLogicalType(*context, field); 76 | fields.push_back(std::move(mysql_field)); 77 | } 78 | 79 | return make_uniq(result, std::move(fields)); 80 | } 81 | } 82 | 83 | void MySQLConnection::Execute(const string &query) { 84 | Query(query); 85 | } 86 | 87 | bool MySQLConnection::IsOpen() { 88 | return connection.get(); 89 | } 90 | 91 | void MySQLConnection::Close() { 92 | if (!IsOpen()) { 93 | return; 94 | } 95 | connection = nullptr; 96 | } 97 | 98 | vector MySQLConnection::GetIndexInfo(const string &table_name) { 99 | return vector(); 100 | } 101 | 102 | void MySQLConnection::DebugSetPrintQueries(bool print) { 103 | debug_mysql_print_queries = print; 104 | } 105 | 106 | bool MySQLConnection::DebugPrintQueries() { 107 | return debug_mysql_print_queries; 108 | } 109 | 110 | } // namespace duckdb 111 | -------------------------------------------------------------------------------- /src/mysql_execute.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | 3 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 4 | #include "mysql_scanner.hpp" 5 | #include "duckdb/main/database_manager.hpp" 6 | #include "duckdb/main/attached_database.hpp" 7 | #include "storage/mysql_catalog.hpp" 8 | #include "storage/mysql_transaction.hpp" 9 | #include "mysql_connection.hpp" 10 | 11 | namespace duckdb { 12 | 13 | struct MySQLExecuteBindData : public TableFunctionData { 14 | explicit MySQLExecuteBindData(MySQLCatalog &mysql_catalog, string query_p) 15 | : mysql_catalog(mysql_catalog), query(std::move(query_p)) { 16 | } 17 | 18 | bool finished = false; 19 | MySQLCatalog &mysql_catalog; 20 | string query; 21 | }; 22 | 23 | static duckdb::unique_ptr MySQLExecuteBind(ClientContext &context, TableFunctionBindInput &input, 24 | vector &return_types, vector &names) { 25 | return_types.emplace_back(LogicalType::BOOLEAN); 26 | names.emplace_back("Success"); 27 | 28 | // look up the database to query 29 | auto db_name = input.inputs[0].GetValue(); 30 | auto &db_manager = DatabaseManager::Get(context); 31 | auto db = db_manager.GetDatabase(context, db_name); 32 | if (!db) { 33 | throw BinderException("Failed to find attached database \"%s\" referenced in mysql_query", db_name); 34 | } 35 | auto &catalog = db->GetCatalog(); 36 | if (catalog.GetCatalogType() != "mysql") { 37 | throw BinderException("Attached database \"%s\" does not refer to a MySQL database", db_name); 38 | } 39 | auto &mysql_catalog = catalog.Cast(); 40 | return make_uniq(mysql_catalog, input.inputs[1].GetValue()); 41 | } 42 | 43 | static void MySQLExecuteFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { 44 | auto &data = data_p.bind_data->CastNoConst(); 45 | if (data.finished) { 46 | return; 47 | } 48 | auto &transaction = Transaction::Get(context, data.mysql_catalog).Cast(); 49 | if (transaction.GetAccessMode() == AccessMode::READ_ONLY) { 50 | throw PermissionException("mysql_execute cannot be run in a read-only connection"); 51 | } 52 | transaction.GetConnection().Execute(data.query); 53 | data.finished = true; 54 | } 55 | 56 | MySQLExecuteFunction::MySQLExecuteFunction() 57 | : TableFunction("mysql_execute", {LogicalType::VARCHAR, LogicalType::VARCHAR}, MySQLExecuteFunc, MySQLExecuteBind) { 58 | } 59 | 60 | } // namespace duckdb 61 | -------------------------------------------------------------------------------- /src/mysql_extension.cpp: -------------------------------------------------------------------------------- 1 | #define DUCKDB_BUILD_LOADABLE_EXTENSION 2 | #include "duckdb.hpp" 3 | 4 | #include "mysql_scanner.hpp" 5 | #include "mysql_storage.hpp" 6 | #include "mysql_scanner_extension.hpp" 7 | 8 | #include "duckdb/catalog/catalog.hpp" 9 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 10 | #include "duckdb/main/extension_util.hpp" 11 | #include "duckdb/main/database_manager.hpp" 12 | #include "duckdb/main/attached_database.hpp" 13 | #include "storage/mysql_catalog.hpp" 14 | #include "storage/mysql_optimizer.hpp" 15 | 16 | using namespace duckdb; 17 | 18 | static void SetMySQLDebugQueryPrint(ClientContext &context, SetScope scope, Value ¶meter) { 19 | MySQLConnection::DebugSetPrintQueries(BooleanValue::Get(parameter)); 20 | } 21 | 22 | unique_ptr CreateMySQLSecretFunction(ClientContext &, CreateSecretInput &input) { 23 | // apply any overridden settings 24 | vector prefix_paths; 25 | auto result = make_uniq(prefix_paths, "mysql", "config", input.name); 26 | for (const auto &named_param : input.options) { 27 | auto lower_name = StringUtil::Lower(named_param.first); 28 | 29 | if (lower_name == "host") { 30 | result->secret_map["host"] = named_param.second.ToString(); 31 | } else if (lower_name == "user") { 32 | result->secret_map["user"] = named_param.second.ToString(); 33 | } else if (lower_name == "database") { 34 | result->secret_map["database"] = named_param.second.ToString(); 35 | } else if (lower_name == "password") { 36 | result->secret_map["password"] = named_param.second.ToString(); 37 | } else if (lower_name == "port") { 38 | result->secret_map["port"] = named_param.second.ToString(); 39 | } else if (lower_name == "socket") { 40 | result->secret_map["socket"] = named_param.second.ToString(); 41 | } else if (lower_name == "ssl_mode") { 42 | result->secret_map["ssl_mode"] = named_param.second.ToString(); 43 | } else if (lower_name == "ssl_ca") { 44 | result->secret_map["ssl_ca"] = named_param.second.ToString(); 45 | } else if (lower_name == "ssl_capath") { 46 | result->secret_map["ssl_capath"] = named_param.second.ToString(); 47 | } else if (lower_name == "ssl_capath") { 48 | result->secret_map["ssl_capath"] = named_param.second.ToString(); 49 | } else if (lower_name == "ssl_cert") { 50 | result->secret_map["ssl_cert"] = named_param.second.ToString(); 51 | } else if (lower_name == "ssl_cipher") { 52 | result->secret_map["ssl_cipher"] = named_param.second.ToString(); 53 | } else if (lower_name == "ssl_crl") { 54 | result->secret_map["ssl_crl"] = named_param.second.ToString(); 55 | } else if (lower_name == "ssl_crlpath") { 56 | result->secret_map["ssl_crlpath"] = named_param.second.ToString(); 57 | } else if (lower_name == "ssl_key") { 58 | result->secret_map["ssl_key"] = named_param.second.ToString(); 59 | } else { 60 | throw InternalException("Unknown named parameter passed to CreateMySQLSecretFunction: " + lower_name); 61 | } 62 | } 63 | 64 | //! Set redact keys 65 | result->redact_keys = {"password"}; 66 | return std::move(result); 67 | } 68 | 69 | void SetMySQLSecretParameters(CreateSecretFunction &function) { 70 | function.named_parameters["host"] = LogicalType::VARCHAR; 71 | function.named_parameters["port"] = LogicalType::VARCHAR; 72 | function.named_parameters["password"] = LogicalType::VARCHAR; 73 | function.named_parameters["user"] = LogicalType::VARCHAR; 74 | function.named_parameters["database"] = LogicalType::VARCHAR; 75 | function.named_parameters["socket"] = LogicalType::VARCHAR; 76 | function.named_parameters["ssl_mode"] = LogicalType::VARCHAR; 77 | function.named_parameters["ssl_ca"] = LogicalType::VARCHAR; 78 | function.named_parameters["ssl_capath"] = LogicalType::VARCHAR; 79 | function.named_parameters["ssl_cert"] = LogicalType::VARCHAR; 80 | function.named_parameters["ssl_cipher"] = LogicalType::VARCHAR; 81 | function.named_parameters["ssl_crl"] = LogicalType::VARCHAR; 82 | function.named_parameters["ssl_crlpath"] = LogicalType::VARCHAR; 83 | function.named_parameters["ssl_key"] = LogicalType::VARCHAR; 84 | } 85 | 86 | static void LoadInternal(DatabaseInstance &db) { 87 | mysql_library_init(0, NULL, NULL); 88 | MySQLClearCacheFunction clear_cache_func; 89 | ExtensionUtil::RegisterFunction(db, clear_cache_func); 90 | 91 | MySQLExecuteFunction execute_function; 92 | ExtensionUtil::RegisterFunction(db, execute_function); 93 | 94 | MySQLQueryFunction query_function; 95 | ExtensionUtil::RegisterFunction(db, query_function); 96 | 97 | SecretType secret_type; 98 | secret_type.name = "mysql"; 99 | secret_type.deserializer = KeyValueSecret::Deserialize; 100 | secret_type.default_provider = "config"; 101 | 102 | ExtensionUtil::RegisterSecretType(db, secret_type); 103 | 104 | CreateSecretFunction mysql_secret_function = {"mysql", "config", CreateMySQLSecretFunction}; 105 | SetMySQLSecretParameters(mysql_secret_function); 106 | ExtensionUtil::RegisterFunction(db, mysql_secret_function); 107 | 108 | auto &config = DBConfig::GetConfig(db); 109 | config.storage_extensions["mysql_scanner"] = make_uniq(); 110 | 111 | config.AddExtensionOption("mysql_experimental_filter_pushdown", 112 | "Whether or not to use filter pushdown (currently experimental)", LogicalType::BOOLEAN, 113 | Value::BOOLEAN(true)); 114 | config.AddExtensionOption("mysql_debug_show_queries", "DEBUG SETTING: print all queries sent to MySQL to stdout", 115 | LogicalType::BOOLEAN, Value::BOOLEAN(false), SetMySQLDebugQueryPrint); 116 | config.AddExtensionOption("mysql_tinyint1_as_boolean", "Whether or not to convert TINYINT(1) columns to BOOLEAN", 117 | LogicalType::BOOLEAN, Value::BOOLEAN(true), MySQLClearCacheFunction::ClearCacheOnSetting); 118 | config.AddExtensionOption("mysql_bit1_as_boolean", "Whether or not to convert BIT(1) columns to BOOLEAN", 119 | LogicalType::BOOLEAN, Value::BOOLEAN(true), MySQLClearCacheFunction::ClearCacheOnSetting); 120 | 121 | OptimizerExtension mysql_optimizer; 122 | mysql_optimizer.optimize_function = MySQLOptimizer::Optimize; 123 | config.optimizer_extensions.push_back(std::move(mysql_optimizer)); 124 | } 125 | 126 | void MysqlScannerExtension::Load(DuckDB &db) { 127 | LoadInternal(*db.instance); 128 | } 129 | 130 | extern "C" { 131 | 132 | DUCKDB_EXTENSION_API void mysql_scanner_init(duckdb::DatabaseInstance &db) { 133 | LoadInternal(db); 134 | } 135 | 136 | DUCKDB_EXTENSION_API const char *mysql_scanner_version() { 137 | return DuckDB::LibraryVersion(); 138 | } 139 | 140 | DUCKDB_EXTENSION_API void mysql_scanner_storage_init(DBConfig &config) { 141 | mysql_library_init(0, NULL, NULL); 142 | config.storage_extensions["mysql_scanner"] = make_uniq(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/mysql_filter_pushdown.cpp: -------------------------------------------------------------------------------- 1 | #include "mysql_filter_pushdown.hpp" 2 | #include "mysql_utils.hpp" 3 | #include "duckdb/planner/filter/optional_filter.hpp" 4 | #include "duckdb/planner/filter/in_filter.hpp" 5 | 6 | namespace duckdb { 7 | 8 | string MySQLFilterPushdown::CreateExpression(string &column_name, vector> &filters, string op) { 9 | vector filter_entries; 10 | for (auto &filter : filters) { 11 | filter_entries.push_back(TransformFilter(column_name, *filter)); 12 | } 13 | return "(" + StringUtil::Join(filter_entries, " " + op + " ") + ")"; 14 | } 15 | 16 | string MySQLFilterPushdown::TransformComparison(ExpressionType type) { 17 | switch (type) { 18 | case ExpressionType::COMPARE_EQUAL: 19 | return "="; 20 | case ExpressionType::COMPARE_NOTEQUAL: 21 | return "!="; 22 | case ExpressionType::COMPARE_LESSTHAN: 23 | return "<"; 24 | case ExpressionType::COMPARE_GREATERTHAN: 25 | return ">"; 26 | case ExpressionType::COMPARE_LESSTHANOREQUALTO: 27 | return "<="; 28 | case ExpressionType::COMPARE_GREATERTHANOREQUALTO: 29 | return ">="; 30 | default: 31 | throw NotImplementedException("Unsupported expression type"); 32 | } 33 | } 34 | 35 | static string TransformBlobToMySQL(const string &val) { 36 | char const HEX_DIGITS[] = "0123456789ABCDEF"; 37 | 38 | string result = "x'"; 39 | for (idx_t i = 0; i < val.size(); i++) { 40 | uint8_t byte_val = static_cast(val[i]); 41 | result += HEX_DIGITS[(byte_val >> 4) & 0xf]; 42 | result += HEX_DIGITS[byte_val & 0xf]; 43 | } 44 | result += "'"; 45 | return result; 46 | } 47 | 48 | string MySQLFilterPushdown::TransformConstant(const Value &val) { 49 | if (val.type().IsNumeric()) { 50 | return val.ToSQLString(); 51 | } 52 | if (val.type().id() == LogicalTypeId::BLOB) { 53 | return TransformBlobToMySQL(StringValue::Get(val)); 54 | } 55 | if (val.type().id() == LogicalTypeId::TIMESTAMP_TZ) { 56 | return val.DefaultCastAs(LogicalType::TIMESTAMP).DefaultCastAs(LogicalType::VARCHAR).ToSQLString(); 57 | } 58 | return val.DefaultCastAs(LogicalType::VARCHAR).ToSQLString(); 59 | } 60 | 61 | string MySQLFilterPushdown::TransformFilter(string &column_name, TableFilter &filter) { 62 | switch (filter.filter_type) { 63 | case TableFilterType::IS_NULL: 64 | return column_name + " IS NULL"; 65 | case TableFilterType::IS_NOT_NULL: 66 | return column_name + " IS NOT NULL"; 67 | case TableFilterType::CONJUNCTION_AND: { 68 | auto &conjunction_filter = filter.Cast(); 69 | return CreateExpression(column_name, conjunction_filter.child_filters, "AND"); 70 | } 71 | case TableFilterType::CONJUNCTION_OR: { 72 | auto &conjunction_filter = filter.Cast(); 73 | return CreateExpression(column_name, conjunction_filter.child_filters, "OR"); 74 | } 75 | case TableFilterType::CONSTANT_COMPARISON: { 76 | auto &constant_filter = filter.Cast(); 77 | auto constant_string = TransformConstant(constant_filter.constant); 78 | auto operator_string = TransformComparison(constant_filter.comparison_type); 79 | return StringUtil::Format("%s %s %s", column_name, operator_string, constant_string); 80 | } 81 | case TableFilterType::OPTIONAL_FILTER: { 82 | auto &optional_filter = filter.Cast(); 83 | return TransformFilter(column_name, *optional_filter.child_filter); 84 | } 85 | case TableFilterType::DYNAMIC_FILTER: { 86 | return string(); 87 | } 88 | case TableFilterType::IN_FILTER: { 89 | auto &in_filter = filter.Cast(); 90 | string in_list; 91 | for (auto &val : in_filter.values) { 92 | if (!in_list.empty()) { 93 | in_list += ", "; 94 | } 95 | in_list += TransformConstant(val); 96 | } 97 | return column_name + " IN (" + in_list + ")"; 98 | } 99 | default: 100 | throw InternalException("Unsupported table filter type"); 101 | } 102 | } 103 | 104 | string MySQLFilterPushdown::TransformFilters(const vector &column_ids, optional_ptr filters, 105 | const vector &names) { 106 | if (!filters || filters->filters.empty()) { 107 | // no filters 108 | return string(); 109 | } 110 | string result; 111 | for (auto &entry : filters->filters) { 112 | if (!result.empty()) { 113 | result += " AND "; 114 | } 115 | auto column_name = MySQLUtils::WriteIdentifier(names[column_ids[entry.first]]); 116 | auto &filter = *entry.second; 117 | result += TransformFilter(column_name, filter); 118 | } 119 | return result; 120 | } 121 | 122 | } // namespace duckdb 123 | -------------------------------------------------------------------------------- /src/mysql_scanner.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | 3 | #include "duckdb/main/extension_util.hpp" 4 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 5 | #include "mysql_scanner.hpp" 6 | #include "mysql_result.hpp" 7 | #include "storage/mysql_transaction.hpp" 8 | #include "storage/mysql_table_set.hpp" 9 | #include "mysql_filter_pushdown.hpp" 10 | #include "duckdb/main/database_manager.hpp" 11 | #include "duckdb/main/attached_database.hpp" 12 | 13 | namespace duckdb { 14 | 15 | struct MySQLGlobalState; 16 | 17 | struct MySQLLocalState : public LocalTableFunctionState {}; 18 | 19 | struct MySQLGlobalState : public GlobalTableFunctionState { 20 | explicit MySQLGlobalState(unique_ptr result_p) : result(std::move(result_p)) { 21 | } 22 | 23 | unique_ptr result; 24 | DataChunk varchar_chunk; 25 | 26 | idx_t MaxThreads() const override { 27 | return 1; 28 | } 29 | }; 30 | 31 | static unique_ptr MySQLBind(ClientContext &context, TableFunctionBindInput &input, 32 | vector &return_types, vector &names) { 33 | throw InternalException("MySQLBind"); 34 | } 35 | 36 | static unique_ptr MySQLInitGlobalState(ClientContext &context, 37 | TableFunctionInitInput &input) { 38 | auto &bind_data = input.bind_data->Cast(); 39 | // generate the SELECT statement 40 | string select; 41 | select += "SELECT "; 42 | for (idx_t c = 0; c < input.column_ids.size(); c++) { 43 | if (c > 0) { 44 | select += ", "; 45 | } 46 | if (input.column_ids[c] == COLUMN_IDENTIFIER_ROW_ID) { 47 | select += "NULL"; 48 | } else { 49 | auto &col = bind_data.table.GetColumn(LogicalIndex(input.column_ids[c])); 50 | auto col_name = col.GetName(); 51 | select += MySQLUtils::WriteIdentifier(col_name); 52 | } 53 | } 54 | select += " FROM "; 55 | select += MySQLUtils::WriteIdentifier(bind_data.table.schema.name); 56 | select += "."; 57 | select += MySQLUtils::WriteIdentifier(bind_data.table.name); 58 | string filter_string = MySQLFilterPushdown::TransformFilters(input.column_ids, input.filters, bind_data.names); 59 | if (!filter_string.empty()) { 60 | select += " WHERE " + filter_string; 61 | } 62 | if (!bind_data.limit.empty()) { 63 | select += bind_data.limit; 64 | } 65 | // run the query 66 | auto &transaction = MySQLTransaction::Get(context, bind_data.table.catalog); 67 | auto &con = transaction.GetConnection(); 68 | auto query_result = con.Query(select); 69 | auto result = make_uniq(std::move(query_result)); 70 | 71 | // generate the varchar chunk 72 | vector varchar_types; 73 | for (idx_t c = 0; c < input.column_ids.size(); c++) { 74 | varchar_types.push_back(LogicalType::VARCHAR); 75 | } 76 | result->varchar_chunk.Initialize(Allocator::DefaultAllocator(), varchar_types); 77 | return std::move(result); 78 | } 79 | 80 | static unique_ptr MySQLInitLocalState(ExecutionContext &context, TableFunctionInitInput &input, 81 | GlobalTableFunctionState *global_state) { 82 | return make_uniq(); 83 | } 84 | 85 | void CastBoolFromMySQL(ClientContext &context, Vector &input, Vector &result, idx_t size) { 86 | auto input_data = FlatVector::GetData(input); 87 | auto result_data = FlatVector::GetData(result); 88 | for (idx_t r = 0; r < size; r++) { 89 | if (FlatVector::IsNull(input, r)) { 90 | FlatVector::SetNull(result, r, true); 91 | continue; 92 | } 93 | auto str_data = input_data[r].GetData(); 94 | auto str_size = input_data[r].GetSize(); 95 | if (str_size == 0) { 96 | throw BinderException( 97 | "Failed to cast MySQL boolean - expected 1 byte element but got element of size %d\n* SET " 98 | "mysql_tinyint1_as_boolean=false to disable loading TINYINT(1) columns as booleans\n* SET " 99 | "mysql_bit1_as_boolean=false to disable loading BIT(1) columns as booleans", 100 | str_size); 101 | } 102 | // booleans are EITHER binary "1" or "0" (BIT(1)) 103 | // OR a number 104 | // in both cases we can figure out what value it is from the first character: 105 | // \0 -> zero byte, false 106 | // - -> negative number, false 107 | // 0 -> zero number, false 108 | if (*str_data == '\0' || *str_data == '0' || *str_data == '-') { 109 | result_data[r] = false; 110 | } else { 111 | result_data[r] = true; 112 | } 113 | } 114 | } 115 | 116 | static void MySQLScan(ClientContext &context, TableFunctionInput &data, DataChunk &output) { 117 | auto &gstate = data.global_state->Cast(); 118 | idx_t r; 119 | gstate.varchar_chunk.Reset(); 120 | for (r = 0; r < STANDARD_VECTOR_SIZE; r++) { 121 | if (!gstate.result->Next()) { 122 | // exhausted result 123 | break; 124 | } 125 | for (idx_t c = 0; c < output.ColumnCount(); c++) { 126 | auto &vec = gstate.varchar_chunk.data[c]; 127 | if (gstate.result->IsNull(c)) { 128 | FlatVector::SetNull(vec, r, true); 129 | } else { 130 | auto string_data = FlatVector::GetData(vec); 131 | string_data[r] = StringVector::AddStringOrBlob(vec, gstate.result->GetStringT(c)); 132 | } 133 | } 134 | } 135 | if (r == 0) { 136 | // done 137 | return; 138 | } 139 | D_ASSERT(output.ColumnCount() == gstate.varchar_chunk.ColumnCount()); 140 | for (idx_t c = 0; c < output.ColumnCount(); c++) { 141 | switch (output.data[c].GetType().id()) { 142 | case LogicalTypeId::BLOB: 143 | // blobs are sent over the wire as-is 144 | output.data[c].Reinterpret(gstate.varchar_chunk.data[c]); 145 | break; 146 | case LogicalTypeId::BOOLEAN: 147 | // booleans can be sent either as numbers ('0' or '1') or as bits ('\0' or 148 | // '\1') 149 | CastBoolFromMySQL(context, gstate.varchar_chunk.data[c], output.data[c], r); 150 | break; 151 | case LogicalTypeId::TIMESTAMP_TZ: { 152 | string error; 153 | VectorOperations::DefaultTryCast(gstate.varchar_chunk.data[c], output.data[c], r, &error); 154 | break; 155 | } 156 | default: { 157 | string error; 158 | VectorOperations::TryCast(context, gstate.varchar_chunk.data[c], output.data[c], r, &error); 159 | break; 160 | } 161 | } 162 | } 163 | output.SetCardinality(r); 164 | } 165 | 166 | static InsertionOrderPreservingMap MySQLScanToString(TableFunctionToStringInput &input) { 167 | InsertionOrderPreservingMap result; 168 | auto &bind_data = input.bind_data->Cast(); 169 | result["Table"] = bind_data.table.name; 170 | return result; 171 | } 172 | 173 | static void MySQLScanSerialize(Serializer &serializer, const optional_ptr bind_data_p, 174 | const TableFunction &function) { 175 | throw NotImplementedException("MySQLScanSerialize"); 176 | } 177 | 178 | static unique_ptr MySQLScanDeserialize(Deserializer &deserializer, TableFunction &function) { 179 | throw NotImplementedException("MySQLScanDeserialize"); 180 | } 181 | 182 | static BindInfo MySQLGetBindInfo(const optional_ptr bind_data_p) { 183 | auto &bind_data = bind_data_p->Cast(); 184 | BindInfo info(ScanType::EXTERNAL); 185 | info.table = bind_data.table; 186 | return info; 187 | } 188 | 189 | MySQLScanFunction::MySQLScanFunction() 190 | : TableFunction("mysql_scan", {LogicalType::VARCHAR, LogicalType::VARCHAR, LogicalType::VARCHAR}, MySQLScan, 191 | MySQLBind, MySQLInitGlobalState, MySQLInitLocalState) { 192 | to_string = MySQLScanToString; 193 | serialize = MySQLScanSerialize; 194 | deserialize = MySQLScanDeserialize; 195 | get_bind_info = MySQLGetBindInfo; 196 | projection_pushdown = true; 197 | } 198 | 199 | //===--------------------------------------------------------------------===// 200 | // MySQL Query 201 | //===--------------------------------------------------------------------===// 202 | struct MySQLQueryBindData : public FunctionData { 203 | MySQLQueryBindData(Catalog &catalog, unique_ptr result_p, string query_p) 204 | : catalog(catalog), result(std::move(result_p)), query(std::move(query_p)) { 205 | } 206 | 207 | Catalog &catalog; 208 | unique_ptr result; 209 | string query; 210 | 211 | public: 212 | unique_ptr Copy() const override { 213 | throw NotImplementedException("MySQLBindData copy not supported"); 214 | } 215 | bool Equals(const FunctionData &other_p) const override { 216 | return false; 217 | } 218 | }; 219 | 220 | static unique_ptr MySQLQueryBind(ClientContext &context, TableFunctionBindInput &input, 221 | vector &return_types, vector &names) { 222 | if (input.inputs[0].IsNull() || input.inputs[1].IsNull()) { 223 | throw BinderException("Parameters to mysql_query cannot be NULL"); 224 | } 225 | 226 | // look up the database to query 227 | auto db_name = input.inputs[0].GetValue(); 228 | auto &db_manager = DatabaseManager::Get(context); 229 | auto db = db_manager.GetDatabase(context, db_name); 230 | if (!db) { 231 | throw BinderException("Failed to find attached database \"%s\" referenced in mysql_query", db_name); 232 | } 233 | auto &catalog = db->GetCatalog(); 234 | if (catalog.GetCatalogType() != "mysql") { 235 | throw BinderException("Attached database \"%s\" does not refer to a MySQL database", db_name); 236 | } 237 | auto &transaction = MySQLTransaction::Get(context, catalog); 238 | auto sql = input.inputs[1].GetValue(); 239 | auto result = transaction.GetConnection().Query(sql, &context); 240 | for (auto &field : result->Fields()) { 241 | names.push_back(field.name); 242 | return_types.push_back(field.type); 243 | } 244 | return make_uniq(catalog, std::move(result), std::move(sql)); 245 | } 246 | 247 | static unique_ptr MySQLQueryInitGlobalState(ClientContext &context, 248 | TableFunctionInitInput &input) { 249 | auto &bind_data = input.bind_data->CastNoConst(); 250 | unique_ptr mysql_result; 251 | if (bind_data.result) { 252 | mysql_result = std::move(bind_data.result); 253 | } else { 254 | auto &transaction = MySQLTransaction::Get(context, bind_data.catalog); 255 | mysql_result = transaction.GetConnection().Query(bind_data.query, &context); 256 | } 257 | auto column_count = mysql_result->ColumnCount(); 258 | 259 | auto result = make_uniq(std::move(mysql_result)); 260 | 261 | // generate the varchar chunk 262 | vector varchar_types; 263 | for (idx_t c = 0; c < column_count; c++) { 264 | varchar_types.push_back(LogicalType::VARCHAR); 265 | } 266 | result->varchar_chunk.Initialize(Allocator::DefaultAllocator(), varchar_types); 267 | return std::move(result); 268 | } 269 | 270 | MySQLQueryFunction::MySQLQueryFunction() 271 | : TableFunction("mysql_query", {LogicalType::VARCHAR, LogicalType::VARCHAR}, MySQLScan, MySQLQueryBind, 272 | MySQLQueryInitGlobalState, MySQLInitLocalState) { 273 | serialize = MySQLScanSerialize; 274 | deserialize = MySQLScanDeserialize; 275 | } 276 | 277 | } // namespace duckdb 278 | -------------------------------------------------------------------------------- /src/mysql_storage.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | 3 | #include "mysql_storage.hpp" 4 | #include "storage/mysql_catalog.hpp" 5 | #include "duckdb/parser/parsed_data/attach_info.hpp" 6 | #include "storage/mysql_transaction_manager.hpp" 7 | 8 | namespace duckdb { 9 | 10 | static unique_ptr MySQLAttach(StorageExtensionInfo *storage_info, ClientContext &context, AttachedDatabase &db, 11 | const string &name, AttachInfo &info, AccessMode access_mode) { 12 | auto &config = DBConfig::GetConfig(context); 13 | if (!config.options.enable_external_access) { 14 | throw PermissionException("Attaching MySQL databases is disabled through configuration"); 15 | } 16 | // check if we have a secret provided 17 | string secret_name; 18 | for (auto &entry : info.options) { 19 | auto lower_name = StringUtil::Lower(entry.first); 20 | if (lower_name == "type" || lower_name == "read_only") { 21 | // already handled 22 | } else if (lower_name == "secret") { 23 | secret_name = entry.second.ToString(); 24 | } else { 25 | throw BinderException("Unrecognized option for MySQL attach: %s", entry.first); 26 | } 27 | } 28 | 29 | string attach_path = info.path; 30 | auto connection_string = MySQLCatalog::GetConnectionString(context, attach_path, secret_name); 31 | return make_uniq(db, std::move(connection_string), std::move(attach_path), access_mode); 32 | } 33 | 34 | static unique_ptr MySQLCreateTransactionManager(StorageExtensionInfo *storage_info, 35 | AttachedDatabase &db, Catalog &catalog) { 36 | auto &mysql_catalog = catalog.Cast(); 37 | return make_uniq(db, mysql_catalog); 38 | } 39 | 40 | MySQLStorageExtension::MySQLStorageExtension() { 41 | attach = MySQLAttach; 42 | create_transaction_manager = MySQLCreateTransactionManager; 43 | } 44 | 45 | } // namespace duckdb 46 | -------------------------------------------------------------------------------- /src/storage/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library( 2 | mysql_ext_storage OBJECT 3 | mysql_catalog.cpp 4 | mysql_catalog_set.cpp 5 | mysql_clear_cache.cpp 6 | mysql_execute_query.cpp 7 | mysql_index.cpp 8 | mysql_index_entry.cpp 9 | mysql_index_set.cpp 10 | mysql_insert.cpp 11 | mysql_optimizer.cpp 12 | mysql_schema_entry.cpp 13 | mysql_schema_set.cpp 14 | mysql_table_entry.cpp 15 | mysql_table_set.cpp 16 | mysql_transaction.cpp 17 | mysql_transaction_manager.cpp) 18 | set(ALL_OBJECT_FILES 19 | ${ALL_OBJECT_FILES} $ 20 | PARENT_SCOPE) 21 | -------------------------------------------------------------------------------- /src/storage/mysql_catalog_set.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_catalog_set.hpp" 2 | #include "storage/mysql_transaction.hpp" 3 | #include "duckdb/parser/parsed_data/drop_info.hpp" 4 | #include "storage/mysql_schema_entry.hpp" 5 | 6 | namespace duckdb { 7 | 8 | MySQLCatalogSet::MySQLCatalogSet(Catalog &catalog) : catalog(catalog), is_loaded(false) { 9 | } 10 | 11 | optional_ptr MySQLCatalogSet::GetEntry(ClientContext &context, const string &name) { 12 | if (!is_loaded) { 13 | is_loaded = true; 14 | LoadEntries(context); 15 | } 16 | lock_guard l(entry_lock); 17 | auto entry = entries.find(name); 18 | if (entry == entries.end()) { 19 | return nullptr; 20 | } 21 | return entry->second.get(); 22 | } 23 | 24 | void MySQLCatalogSet::DropEntry(ClientContext &context, DropInfo &info) { 25 | string drop_query = "DROP "; 26 | drop_query += CatalogTypeToString(info.type) + " "; 27 | if (info.if_not_found == OnEntryNotFound::RETURN_NULL) { 28 | drop_query += " IF EXISTS "; 29 | } 30 | drop_query += MySQLUtils::WriteIdentifier(info.name); 31 | if (info.type != CatalogType::SCHEMA_ENTRY) { 32 | if (info.cascade) { 33 | drop_query += " CASCADE"; 34 | } 35 | } 36 | auto &transaction = MySQLTransaction::Get(context, catalog); 37 | transaction.Query(drop_query); 38 | 39 | // erase the entry from the catalog set 40 | EraseEntryInternal(info.name); 41 | } 42 | 43 | void MySQLCatalogSet::EraseEntryInternal(const string &name) { 44 | lock_guard l(entry_lock); 45 | entries.erase(name); 46 | } 47 | 48 | void MySQLCatalogSet::Scan(ClientContext &context, const std::function &callback) { 49 | if (!is_loaded) { 50 | is_loaded = true; 51 | LoadEntries(context); 52 | } 53 | lock_guard l(entry_lock); 54 | for (auto &entry : entries) { 55 | callback(*entry.second); 56 | } 57 | } 58 | 59 | optional_ptr MySQLCatalogSet::CreateEntry(unique_ptr entry) { 60 | lock_guard l(entry_lock); 61 | auto result = entry.get(); 62 | if (result->name.empty()) { 63 | throw InternalException("MySQLCatalogSet::CreateEntry called with empty name"); 64 | } 65 | entries.insert(make_pair(result->name, std::move(entry))); 66 | return result; 67 | } 68 | 69 | void MySQLCatalogSet::ClearEntries() { 70 | entries.clear(); 71 | is_loaded = false; 72 | } 73 | 74 | MySQLInSchemaSet::MySQLInSchemaSet(MySQLSchemaEntry &schema) : MySQLCatalogSet(schema.ParentCatalog()), schema(schema) { 75 | } 76 | 77 | optional_ptr MySQLInSchemaSet::CreateEntry(unique_ptr entry) { 78 | if (!entry->internal) { 79 | entry->internal = schema.internal; 80 | } 81 | return MySQLCatalogSet::CreateEntry(std::move(entry)); 82 | } 83 | 84 | } // namespace duckdb 85 | -------------------------------------------------------------------------------- /src/storage/mysql_clear_cache.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | 3 | #include "duckdb/parser/parsed_data/create_table_function_info.hpp" 4 | #include "mysql_scanner.hpp" 5 | #include "duckdb/main/database_manager.hpp" 6 | #include "duckdb/main/attached_database.hpp" 7 | #include "storage/mysql_catalog.hpp" 8 | 9 | namespace duckdb { 10 | 11 | struct ClearCacheFunctionData : public TableFunctionData { 12 | bool finished = false; 13 | }; 14 | 15 | static unique_ptr ClearCacheBind(ClientContext &context, TableFunctionBindInput &input, 16 | vector &return_types, vector &names) { 17 | 18 | auto result = make_uniq(); 19 | return_types.push_back(LogicalType::BOOLEAN); 20 | names.emplace_back("Success"); 21 | return std::move(result); 22 | } 23 | 24 | static void ClearMySQLCaches(ClientContext &context) { 25 | auto databases = DatabaseManager::Get(context).GetDatabases(context); 26 | for (auto &db_ref : databases) { 27 | auto &db = db_ref.get(); 28 | auto &catalog = db.GetCatalog(); 29 | if (catalog.GetCatalogType() != "mysql") { 30 | continue; 31 | } 32 | catalog.Cast().ClearCache(); 33 | } 34 | } 35 | 36 | static void ClearCacheFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { 37 | auto &data = data_p.bind_data->CastNoConst(); 38 | if (data.finished) { 39 | return; 40 | } 41 | ClearMySQLCaches(context); 42 | data.finished = true; 43 | } 44 | 45 | void MySQLClearCacheFunction::ClearCacheOnSetting(ClientContext &context, SetScope scope, Value ¶meter) { 46 | ClearMySQLCaches(context); 47 | } 48 | 49 | MySQLClearCacheFunction::MySQLClearCacheFunction() 50 | : TableFunction("mysql_clear_cache", {}, ClearCacheFunction, ClearCacheBind) { 51 | } 52 | } // namespace duckdb 53 | -------------------------------------------------------------------------------- /src/storage/mysql_execute_query.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_execute_query.hpp" 2 | #include "storage/mysql_table_entry.hpp" 3 | #include "duckdb/planner/operator/logical_delete.hpp" 4 | #include "storage/mysql_catalog.hpp" 5 | #include "storage/mysql_transaction.hpp" 6 | #include "mysql_connection.hpp" 7 | #include "duckdb/planner/operator/logical_update.hpp" 8 | #include "duckdb/execution/operator/filter/physical_filter.hpp" 9 | #include "duckdb/execution/operator/scan/physical_table_scan.hpp" 10 | #include "duckdb/planner/expression/bound_reference_expression.hpp" 11 | #include "duckdb/execution/operator/projection/physical_projection.hpp" 12 | 13 | namespace duckdb { 14 | 15 | MySQLExecuteQuery::MySQLExecuteQuery(LogicalOperator &op, string op_name_p, TableCatalogEntry &table, string query_p) 16 | : PhysicalOperator(PhysicalOperatorType::EXTENSION, op.types, 1), op_name(std::move(op_name_p)), table(table), 17 | query(std::move(query_p)) { 18 | } 19 | 20 | //===--------------------------------------------------------------------===// 21 | // States 22 | //===--------------------------------------------------------------------===// 23 | class MySQLExecuteQueryGlobalState : public GlobalSinkState { 24 | public: 25 | explicit MySQLExecuteQueryGlobalState() : affected_rows(0) { 26 | } 27 | 28 | idx_t affected_rows; 29 | }; 30 | 31 | unique_ptr MySQLExecuteQuery::GetGlobalSinkState(ClientContext &context) const { 32 | return make_uniq(); 33 | } 34 | 35 | //===--------------------------------------------------------------------===// 36 | // Sink 37 | //===--------------------------------------------------------------------===// 38 | SinkResultType MySQLExecuteQuery::Sink(ExecutionContext &context, DataChunk &chunk, OperatorSinkInput &input) const { 39 | return SinkResultType::FINISHED; 40 | } 41 | 42 | //===--------------------------------------------------------------------===// 43 | // Finalize 44 | //===--------------------------------------------------------------------===// 45 | SinkFinalizeType MySQLExecuteQuery::Finalize(Pipeline &pipeline, Event &event, ClientContext &context, 46 | OperatorSinkFinalizeInput &input) const { 47 | auto &gstate = input.global_state.Cast(); 48 | auto &transaction = MySQLTransaction::Get(context, table.catalog); 49 | auto &connection = transaction.GetConnection(); 50 | auto result = connection.Query(query); 51 | gstate.affected_rows = result->AffectedRows(); 52 | return SinkFinalizeType::READY; 53 | } 54 | 55 | //===--------------------------------------------------------------------===// 56 | // GetData 57 | //===--------------------------------------------------------------------===// 58 | SourceResultType MySQLExecuteQuery::GetData(ExecutionContext &context, DataChunk &chunk, 59 | OperatorSourceInput &input) const { 60 | auto &insert_gstate = sink_state->Cast(); 61 | chunk.SetCardinality(1); 62 | chunk.SetValue(0, 0, Value::BIGINT(insert_gstate.affected_rows)); 63 | 64 | return SourceResultType::FINISHED; 65 | } 66 | 67 | //===--------------------------------------------------------------------===// 68 | // Helpers 69 | //===--------------------------------------------------------------------===// 70 | string MySQLExecuteQuery::GetName() const { 71 | return op_name; 72 | } 73 | 74 | InsertionOrderPreservingMap MySQLExecuteQuery::ParamsToString() const { 75 | InsertionOrderPreservingMap result; 76 | result["Table Name"] = table.name; 77 | return result; 78 | } 79 | 80 | //===--------------------------------------------------------------------===// 81 | // Plan 82 | //===--------------------------------------------------------------------===// 83 | string ExtractFilters(PhysicalOperator &child, const string &statement) { 84 | // FIXME - all of this is pretty gnarly, we should provide a hook earlier on 85 | // in the planning process to convert this into a SQL statement 86 | if (child.type == PhysicalOperatorType::FILTER) { 87 | auto &filter = child.Cast(); 88 | auto result = ExtractFilters(child.children[0], statement); 89 | auto filter_str = filter.expression->ToString(); 90 | if (result.empty()) { 91 | return filter_str; 92 | } else { 93 | return result + " AND " + filter_str; 94 | } 95 | } else if (child.type == PhysicalOperatorType::PROJECTION) { 96 | auto &proj = child.Cast(); 97 | for (auto &expr : proj.select_list) { 98 | switch (expr->type) { 99 | case ExpressionType::BOUND_REF: 100 | case ExpressionType::BOUND_COLUMN_REF: 101 | case ExpressionType::VALUE_CONSTANT: 102 | break; 103 | default: 104 | throw NotImplementedException("Unsupported expression type in projection - only simple deletes/updates " 105 | "are supported in the MySQL connector"); 106 | } 107 | } 108 | return ExtractFilters(child.children[0], statement); 109 | } else if (child.type == PhysicalOperatorType::TABLE_SCAN) { 110 | auto &table_scan = child.Cast(); 111 | if (!table_scan.table_filters) { 112 | return string(); 113 | } 114 | string result; 115 | for (auto &entry : table_scan.table_filters->filters) { 116 | auto column_index = entry.first; 117 | auto &filter = entry.second; 118 | string column_name; 119 | if (column_index < table_scan.names.size()) { 120 | const auto col_id = table_scan.column_ids[column_index].GetPrimaryIndex(); 121 | if (col_id == COLUMN_IDENTIFIER_ROW_ID) { 122 | column_name = "rowid"; 123 | } else { 124 | column_name = table_scan.names[col_id]; 125 | } 126 | } 127 | BoundReferenceExpression bound_ref(std::move(column_name), LogicalTypeId::INVALID, 0); 128 | auto filter_expr = filter->ToExpression(bound_ref); 129 | auto filter_str = filter_expr->ToString(); 130 | if (result.empty()) { 131 | result = std::move(filter_str); 132 | } else { 133 | result += " AND " + filter_str; 134 | } 135 | } 136 | return result; 137 | } else { 138 | throw NotImplementedException("Unsupported operator type %s in %s statement - only simple deletes " 139 | "(e.g. %s " 140 | "FROM tbl WHERE x=y) are supported in the MySQL connector", 141 | PhysicalOperatorToString(child.type), statement, statement); 142 | } 143 | } 144 | 145 | string ConstructDeleteStatement(LogicalDelete &op, PhysicalOperator &child) { 146 | string result = "DELETE FROM "; 147 | result += MySQLUtils::WriteIdentifier(op.table.schema.name); 148 | result += "."; 149 | result += MySQLUtils::WriteIdentifier(op.table.name); 150 | auto filters = ExtractFilters(child, "DELETE"); 151 | if (!filters.empty()) { 152 | result += " WHERE " + filters; 153 | } 154 | return result; 155 | } 156 | 157 | PhysicalOperator &MySQLCatalog::PlanDelete(ClientContext &context, PhysicalPlanGenerator &planner, LogicalDelete &op, 158 | PhysicalOperator &plan) { 159 | if (op.return_chunk) { 160 | throw BinderException("RETURNING clause not yet supported for deletion of a MySQL table"); 161 | } 162 | 163 | auto &execute = planner.Make(op, "DELETE", op.table, ConstructDeleteStatement(op, plan)); 164 | execute.children.push_back(plan); 165 | return execute; 166 | } 167 | 168 | string ConstructUpdateStatement(LogicalUpdate &op, PhysicalOperator &child) { 169 | // FIXME - all of this is pretty gnarly, we should provide a hook earlier on 170 | // in the planning process to convert this into a SQL statement 171 | string result = "UPDATE"; 172 | result += MySQLUtils::WriteIdentifier(op.table.schema.name); 173 | result += "."; 174 | result += MySQLUtils::WriteIdentifier(op.table.name); 175 | result += " SET "; 176 | if (child.type != PhysicalOperatorType::PROJECTION) { 177 | throw NotImplementedException("MySQL Update not supported - Expected the " 178 | "child of an update to be a projection"); 179 | } 180 | auto &proj = child.Cast(); 181 | for (idx_t c = 0; c < op.columns.size(); c++) { 182 | if (c > 0) { 183 | result += ", "; 184 | } 185 | auto &col = op.table.GetColumn(op.table.GetColumns().PhysicalToLogical(op.columns[c])); 186 | result += MySQLUtils::WriteIdentifier(col.GetName()); 187 | result += " = "; 188 | if (op.expressions[c]->type == ExpressionType::VALUE_DEFAULT) { 189 | result += "DEFAULT"; 190 | continue; 191 | } 192 | if (op.expressions[c]->type != ExpressionType::BOUND_REF) { 193 | throw NotImplementedException("MySQL Update not supported - Expected a bound reference expression"); 194 | } 195 | auto &ref = op.expressions[c]->Cast(); 196 | result += proj.select_list[ref.index]->ToString(); 197 | } 198 | result += " "; 199 | auto filters = ExtractFilters(child.children[0], "UPDATE"); 200 | if (!filters.empty()) { 201 | result += " WHERE " + filters; 202 | } 203 | return result; 204 | } 205 | 206 | PhysicalOperator &MySQLCatalog::PlanUpdate(ClientContext &context, PhysicalPlanGenerator &planner, LogicalUpdate &op, 207 | PhysicalOperator &plan) { 208 | if (op.return_chunk) { 209 | throw BinderException("RETURNING clause not yet supported for updates of a MySQL table"); 210 | } 211 | 212 | auto &execute = planner.Make(op, "UPDATE", op.table, ConstructUpdateStatement(op, plan)); 213 | execute.children.push_back(plan); 214 | return execute; 215 | } 216 | 217 | } // namespace duckdb 218 | -------------------------------------------------------------------------------- /src/storage/mysql_index.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_catalog.hpp" 2 | #include "storage/mysql_index.hpp" 3 | #include "duckdb/parser/statement/create_statement.hpp" 4 | #include "duckdb/planner/operator/logical_extension_operator.hpp" 5 | #include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp" 6 | 7 | namespace duckdb { 8 | 9 | MySQLCreateIndex::MySQLCreateIndex(unique_ptr info, TableCatalogEntry &table) 10 | : PhysicalOperator(PhysicalOperatorType::EXTENSION, {LogicalType::BIGINT}, 1), info(std::move(info)), table(table) { 11 | } 12 | 13 | //===--------------------------------------------------------------------===// 14 | // Source 15 | //===--------------------------------------------------------------------===// 16 | SourceResultType MySQLCreateIndex::GetData(ExecutionContext &context, DataChunk &chunk, 17 | OperatorSourceInput &input) const { 18 | auto &catalog = table.catalog; 19 | if (info->catalog == INVALID_CATALOG && info->schema == catalog.GetName()) { 20 | info->schema = DEFAULT_SCHEMA; 21 | } 22 | auto &schema = catalog.GetSchema(context.client, info->schema); 23 | schema.CreateIndex(context.client, *info, table); 24 | 25 | return SourceResultType::FINISHED; 26 | } 27 | 28 | //===--------------------------------------------------------------------===// 29 | // Logical Operator 30 | //===--------------------------------------------------------------------===// 31 | class LogicalMySQLCreateIndex : public LogicalExtensionOperator { 32 | public: 33 | LogicalMySQLCreateIndex(unique_ptr info_p, TableCatalogEntry &table) 34 | : info(std::move(info_p)), table(table) { 35 | } 36 | 37 | unique_ptr info; 38 | TableCatalogEntry &table; 39 | 40 | PhysicalOperator &CreatePlan(ClientContext &context, PhysicalPlanGenerator &planner) override { 41 | return planner.Make(std::move(info), table); 42 | } 43 | 44 | void Serialize(Serializer &serializer) const override { 45 | throw InternalException("Cannot serialize MySQL Create index"); 46 | } 47 | 48 | void ResolveTypes() override { 49 | types = {LogicalType::BIGINT}; 50 | } 51 | }; 52 | 53 | unique_ptr MySQLCatalog::BindCreateIndex(Binder &binder, CreateStatement &stmt, 54 | TableCatalogEntry &table, unique_ptr plan) { 55 | return make_uniq(unique_ptr_cast(std::move(stmt.info)), 56 | table); 57 | } 58 | 59 | } // namespace duckdb 60 | -------------------------------------------------------------------------------- /src/storage/mysql_index_entry.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_index_entry.hpp" 2 | #include "duckdb/catalog/catalog_entry/schema_catalog_entry.hpp" 3 | 4 | namespace duckdb { 5 | 6 | MySQLIndexEntry::MySQLIndexEntry(Catalog &catalog, SchemaCatalogEntry &schema, CreateIndexInfo &info, 7 | string table_name_p) 8 | : IndexCatalogEntry(catalog, schema, info), table_name(std::move(table_name_p)) { 9 | } 10 | 11 | string MySQLIndexEntry::GetSchemaName() const { 12 | return schema.name; 13 | } 14 | 15 | string MySQLIndexEntry::GetTableName() const { 16 | return table_name; 17 | } 18 | 19 | } // namespace duckdb 20 | -------------------------------------------------------------------------------- /src/storage/mysql_index_set.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_index_set.hpp" 2 | #include "storage/mysql_schema_entry.hpp" 3 | #include "storage/mysql_transaction.hpp" 4 | #include "duckdb/parser/parsed_data/create_schema_info.hpp" 5 | #include "storage/mysql_index_entry.hpp" 6 | #include "duckdb/parser/parsed_data/drop_info.hpp" 7 | 8 | namespace duckdb { 9 | 10 | MySQLIndexSet::MySQLIndexSet(MySQLSchemaEntry &schema) : MySQLInSchemaSet(schema) { 11 | } 12 | 13 | void MySQLIndexSet::DropEntry(ClientContext &context, DropInfo &info) { 14 | auto entry = GetEntry(context, info.name); 15 | if (!entry) { 16 | if (info.if_not_found == OnEntryNotFound::RETURN_NULL) { 17 | return; 18 | } 19 | throw CatalogException("Failed to DROP INDEX \"%s\": entry not found", info.name); 20 | } 21 | auto &mysql_index = entry->Cast(); 22 | string drop_query = "DROP INDEX "; 23 | drop_query += MySQLUtils::WriteIdentifier(info.name); 24 | drop_query += " ON "; 25 | drop_query += MySQLUtils::WriteIdentifier(mysql_index.table_name); 26 | auto &transaction = MySQLTransaction::Get(context, catalog); 27 | transaction.Query(drop_query); 28 | 29 | EraseEntryInternal(info.name); 30 | } 31 | 32 | void MySQLIndexSet::LoadEntries(ClientContext &context) { 33 | auto query = StringUtil::Replace(R"( 34 | SELECT DISTINCT TABLE_NAME, INDEX_NAME 35 | FROM INFORMATION_SCHEMA.STATISTICS 36 | WHERE TABLE_SCHEMA = 'mysqlscanner'; 37 | )", 38 | "${SCHEMA_NAME}", MySQLUtils::WriteLiteral(schema.name)); 39 | 40 | auto &transaction = MySQLTransaction::Get(context, catalog); 41 | auto result = transaction.Query(query); 42 | while (result->Next()) { 43 | auto table_name = result->GetString(0); 44 | auto index_name = result->GetString(1); 45 | CreateIndexInfo info; 46 | info.schema = schema.name; 47 | info.table = table_name; 48 | info.index_name = index_name; 49 | auto index_entry = make_uniq(catalog, schema, info, table_name); 50 | CreateEntry(std::move(index_entry)); 51 | } 52 | } 53 | 54 | } // namespace duckdb 55 | -------------------------------------------------------------------------------- /src/storage/mysql_optimizer.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_optimizer.hpp" 2 | #include "duckdb/planner/operator/logical_get.hpp" 3 | #include "duckdb/planner/operator/logical_limit.hpp" 4 | #include "mysql_scanner.hpp" 5 | 6 | namespace duckdb { 7 | 8 | static bool IsMySQLScan(const string &function_name) { 9 | return function_name == "mysql_scan"; 10 | } 11 | 12 | void OptimizeMySQLScan(unique_ptr &op) { 13 | if (op->type == LogicalOperatorType::LOGICAL_LIMIT) { 14 | auto &limit = op->Cast(); 15 | reference child = *op->children[0]; 16 | while (child.get().type == LogicalOperatorType::LOGICAL_PROJECTION) { 17 | child = *child.get().children[0]; 18 | } 19 | if (child.get().type != LogicalOperatorType::LOGICAL_GET) { 20 | return; 21 | } 22 | auto &get = child.get().Cast(); 23 | if (!IsMySQLScan(get.function.name)) { 24 | return; 25 | } 26 | switch (limit.limit_val.Type()) { 27 | case LimitNodeType::CONSTANT_VALUE: 28 | case LimitNodeType::UNSET: 29 | break; 30 | default: 31 | // not a constant or unset limit 32 | return; 33 | } 34 | switch (limit.offset_val.Type()) { 35 | case LimitNodeType::CONSTANT_VALUE: 36 | case LimitNodeType::UNSET: 37 | break; 38 | default: 39 | // not a constant or unset offset 40 | return; 41 | } 42 | auto &bind_data = get.bind_data->Cast(); 43 | if (limit.limit_val.Type() != LimitNodeType::UNSET) { 44 | bind_data.limit += " LIMIT " + to_string(limit.limit_val.GetConstantValue()); 45 | } 46 | if (limit.offset_val.Type() != LimitNodeType::UNSET) { 47 | bind_data.limit += " OFFSET " + to_string(limit.offset_val.GetConstantValue()); 48 | } 49 | // remove the limit 50 | op = std::move(op->children[0]); 51 | return; 52 | } 53 | // recurse into children 54 | for (auto &child : op->children) { 55 | OptimizeMySQLScan(child); 56 | } 57 | } 58 | 59 | void MySQLOptimizer::Optimize(OptimizerExtensionInput &input, unique_ptr &plan) { 60 | OptimizeMySQLScan(plan); 61 | } 62 | 63 | } // namespace duckdb 64 | -------------------------------------------------------------------------------- /src/storage/mysql_schema_entry.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_schema_entry.hpp" 2 | #include "storage/mysql_table_entry.hpp" 3 | #include "storage/mysql_transaction.hpp" 4 | #include "duckdb/parser/parsed_data/create_view_info.hpp" 5 | #include "duckdb/parser/parsed_data/create_index_info.hpp" 6 | #include "duckdb/planner/parsed_data/bound_create_table_info.hpp" 7 | #include "duckdb/parser/parsed_data/drop_info.hpp" 8 | #include "duckdb/parser/constraints/list.hpp" 9 | #include "duckdb/common/unordered_set.hpp" 10 | #include "duckdb/parser/parsed_data/alter_info.hpp" 11 | #include "duckdb/parser/parsed_data/alter_table_info.hpp" 12 | #include "duckdb/parser/parsed_expression_iterator.hpp" 13 | 14 | namespace duckdb { 15 | 16 | MySQLSchemaEntry::MySQLSchemaEntry(Catalog &catalog, CreateSchemaInfo &info) 17 | : SchemaCatalogEntry(catalog, info), tables(*this), indexes(*this) { 18 | } 19 | 20 | MySQLTransaction &GetMySQLTransaction(CatalogTransaction transaction) { 21 | if (!transaction.transaction) { 22 | throw InternalException("No transaction!?"); 23 | } 24 | return transaction.transaction->Cast(); 25 | } 26 | 27 | void MySQLSchemaEntry::TryDropEntry(ClientContext &context, CatalogType catalog_type, const string &name) { 28 | DropInfo info; 29 | info.type = catalog_type; 30 | info.name = name; 31 | info.cascade = false; 32 | info.if_not_found = OnEntryNotFound::RETURN_NULL; 33 | DropEntry(context, info); 34 | } 35 | 36 | optional_ptr MySQLSchemaEntry::CreateTable(CatalogTransaction transaction, BoundCreateTableInfo &info) { 37 | auto &base_info = info.Base(); 38 | auto table_name = base_info.table; 39 | if (base_info.on_conflict == OnCreateConflict::REPLACE_ON_CONFLICT) { 40 | // CREATE OR REPLACE - drop any existing entries first (if any) 41 | TryDropEntry(transaction.GetContext(), CatalogType::TABLE_ENTRY, table_name); 42 | } 43 | return tables.CreateTable(transaction.GetContext(), info); 44 | } 45 | 46 | optional_ptr MySQLSchemaEntry::CreateFunction(CatalogTransaction transaction, CreateFunctionInfo &info) { 47 | throw BinderException("MySQL databases do not support creating functions"); 48 | } 49 | 50 | void MySQLUnqualifyColumnRef(ParsedExpression &expr) { 51 | if (expr.type == ExpressionType::COLUMN_REF) { 52 | auto &colref = expr.Cast(); 53 | auto name = std::move(colref.column_names.back()); 54 | colref.column_names = {std::move(name)}; 55 | return; 56 | } 57 | ParsedExpressionIterator::EnumerateChildren(expr, MySQLUnqualifyColumnRef); 58 | } 59 | 60 | string GetMySQLCreateIndex(CreateIndexInfo &info, TableCatalogEntry &tbl) { 61 | string sql; 62 | sql = "CREATE"; 63 | if (info.constraint_type == IndexConstraintType::UNIQUE) { 64 | sql += " UNIQUE"; 65 | } 66 | sql += " INDEX "; 67 | sql += MySQLUtils::WriteIdentifier(info.index_name); 68 | sql += " ON "; 69 | sql += MySQLUtils::WriteIdentifier(tbl.name); 70 | sql += "("; 71 | for (idx_t i = 0; i < info.parsed_expressions.size(); i++) { 72 | if (i > 0) { 73 | sql += ", "; 74 | } 75 | MySQLUnqualifyColumnRef(*info.parsed_expressions[i]); 76 | if (info.parsed_expressions[i]->type == ExpressionType::COLUMN_REF) { 77 | // index on column 78 | sql += info.parsed_expressions[i]->ToString(); 79 | } else { 80 | // index on expression 81 | // expressions need to be wrapped in brackets 82 | sql += "(" + info.parsed_expressions[i]->ToString() + ")"; 83 | } 84 | } 85 | sql += ")"; 86 | return sql; 87 | } 88 | 89 | optional_ptr MySQLSchemaEntry::CreateIndex(CatalogTransaction transaction, CreateIndexInfo &info, 90 | TableCatalogEntry &table) { 91 | auto &mysql_transaction = MySQLTransaction::Get(transaction.GetContext(), table.catalog); 92 | mysql_transaction.Query(GetMySQLCreateIndex(info, table)); 93 | return nullptr; 94 | } 95 | 96 | string GetMySQLCreateView(CreateViewInfo &info) { 97 | string sql; 98 | sql = "CREATE VIEW "; 99 | sql += MySQLUtils::WriteIdentifier(info.view_name); 100 | sql += " "; 101 | if (!info.aliases.empty()) { 102 | sql += "("; 103 | for (idx_t i = 0; i < info.aliases.size(); i++) { 104 | if (i > 0) { 105 | sql += ", "; 106 | } 107 | auto &alias = info.aliases[i]; 108 | sql += MySQLUtils::WriteIdentifier(alias); 109 | } 110 | sql += ") "; 111 | } 112 | sql += "AS "; 113 | sql += info.query->ToString(); 114 | return sql; 115 | } 116 | 117 | optional_ptr MySQLSchemaEntry::CreateView(CatalogTransaction transaction, CreateViewInfo &info) { 118 | if (info.sql.empty()) { 119 | throw BinderException("Cannot create view in MySQL that originated from an " 120 | "empty SQL statement"); 121 | } 122 | if (info.on_conflict == OnCreateConflict::REPLACE_ON_CONFLICT || 123 | info.on_conflict == OnCreateConflict::IGNORE_ON_CONFLICT) { 124 | auto current_entry = GetEntry(transaction, CatalogType::VIEW_ENTRY, info.view_name); 125 | if (current_entry) { 126 | if (info.on_conflict == OnCreateConflict::IGNORE_ON_CONFLICT) { 127 | return current_entry; 128 | } 129 | // CREATE OR REPLACE - drop any existing entries first (if any) 130 | TryDropEntry(transaction.GetContext(), CatalogType::VIEW_ENTRY, info.view_name); 131 | } 132 | } 133 | auto &mysql_transaction = GetMySQLTransaction(transaction); 134 | mysql_transaction.Query(GetMySQLCreateView(info)); 135 | return tables.RefreshTable(transaction.GetContext(), info.view_name); 136 | } 137 | 138 | optional_ptr MySQLSchemaEntry::CreateType(CatalogTransaction transaction, CreateTypeInfo &info) { 139 | throw BinderException("MySQL databases do not support creating types"); 140 | } 141 | 142 | optional_ptr MySQLSchemaEntry::CreateSequence(CatalogTransaction transaction, CreateSequenceInfo &info) { 143 | throw BinderException("MySQL databases do not support creating sequences"); 144 | } 145 | 146 | optional_ptr MySQLSchemaEntry::CreateTableFunction(CatalogTransaction transaction, 147 | CreateTableFunctionInfo &info) { 148 | throw BinderException("MySQL databases do not support creating table functions"); 149 | } 150 | 151 | optional_ptr MySQLSchemaEntry::CreateCopyFunction(CatalogTransaction transaction, 152 | CreateCopyFunctionInfo &info) { 153 | throw BinderException("MySQL databases do not support creating copy functions"); 154 | } 155 | 156 | optional_ptr MySQLSchemaEntry::CreatePragmaFunction(CatalogTransaction transaction, 157 | CreatePragmaFunctionInfo &info) { 158 | throw BinderException("MySQL databases do not support creating pragma functions"); 159 | } 160 | 161 | optional_ptr MySQLSchemaEntry::CreateCollation(CatalogTransaction transaction, 162 | CreateCollationInfo &info) { 163 | throw BinderException("MySQL databases do not support creating collations"); 164 | } 165 | 166 | void MySQLSchemaEntry::Alter(CatalogTransaction transaction, AlterInfo &info) { 167 | if (info.type != AlterType::ALTER_TABLE) { 168 | throw BinderException("Only altering tables is supported for now"); 169 | } 170 | auto &alter = info.Cast(); 171 | tables.AlterTable(transaction.GetContext(), alter); 172 | } 173 | 174 | bool CatalogTypeIsSupported(CatalogType type) { 175 | switch (type) { 176 | case CatalogType::INDEX_ENTRY: 177 | case CatalogType::TABLE_ENTRY: 178 | case CatalogType::VIEW_ENTRY: 179 | return true; 180 | default: 181 | return false; 182 | } 183 | } 184 | 185 | void MySQLSchemaEntry::Scan(ClientContext &context, CatalogType type, 186 | const std::function &callback) { 187 | if (!CatalogTypeIsSupported(type)) { 188 | return; 189 | } 190 | GetCatalogSet(type).Scan(context, callback); 191 | } 192 | void MySQLSchemaEntry::Scan(CatalogType type, const std::function &callback) { 193 | throw NotImplementedException("Scan without context not supported"); 194 | } 195 | 196 | void MySQLSchemaEntry::DropEntry(ClientContext &context, DropInfo &info) { 197 | GetCatalogSet(info.type).DropEntry(context, info); 198 | } 199 | 200 | optional_ptr MySQLSchemaEntry::LookupEntry(CatalogTransaction transaction, 201 | const EntryLookupInfo &lookup_info) { 202 | auto lookup_type = lookup_info.GetCatalogType(); 203 | if (!CatalogTypeIsSupported(lookup_type)) { 204 | return nullptr; 205 | } 206 | return GetCatalogSet(lookup_type).GetEntry(transaction.GetContext(), lookup_info.GetEntryName()); 207 | } 208 | 209 | MySQLCatalogSet &MySQLSchemaEntry::GetCatalogSet(CatalogType type) { 210 | switch (type) { 211 | case CatalogType::TABLE_ENTRY: 212 | case CatalogType::VIEW_ENTRY: 213 | return tables; 214 | case CatalogType::INDEX_ENTRY: 215 | return indexes; 216 | default: 217 | throw InternalException("Type not supported for GetCatalogSet"); 218 | } 219 | } 220 | 221 | } // namespace duckdb -------------------------------------------------------------------------------- /src/storage/mysql_schema_set.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_schema_set.hpp" 2 | #include "storage/mysql_transaction.hpp" 3 | #include "duckdb/parser/parsed_data/create_schema_info.hpp" 4 | 5 | namespace duckdb { 6 | 7 | static bool MySQLSchemaIsInternal(const string &name) { 8 | if (name == "information_schema" || name == "performance_schema" || name == "sys") { 9 | return true; 10 | } 11 | return false; 12 | } 13 | 14 | MySQLSchemaSet::MySQLSchemaSet(Catalog &catalog) : MySQLCatalogSet(catalog) { 15 | } 16 | 17 | void MySQLSchemaSet::LoadEntries(ClientContext &context) { 18 | auto query = R"( 19 | SELECT schema_name 20 | FROM information_schema.schemata; 21 | )"; 22 | 23 | auto &transaction = MySQLTransaction::Get(context, catalog); 24 | auto result = transaction.Query(query); 25 | while (result->Next()) { 26 | CreateSchemaInfo info; 27 | info.schema = result->GetString(0); 28 | info.internal = MySQLSchemaIsInternal(info.schema); 29 | auto schema = make_uniq(catalog, info); 30 | CreateEntry(std::move(schema)); 31 | } 32 | } 33 | 34 | optional_ptr MySQLSchemaSet::CreateSchema(ClientContext &context, CreateSchemaInfo &info) { 35 | auto &transaction = MySQLTransaction::Get(context, catalog); 36 | 37 | string create_sql = "CREATE SCHEMA " + MySQLUtils::WriteIdentifier(info.schema); 38 | transaction.Query(create_sql); 39 | auto schema_entry = make_uniq(catalog, info); 40 | return CreateEntry(std::move(schema_entry)); 41 | } 42 | 43 | } // namespace duckdb 44 | -------------------------------------------------------------------------------- /src/storage/mysql_table_entry.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_catalog.hpp" 2 | #include "storage/mysql_schema_entry.hpp" 3 | #include "storage/mysql_table_entry.hpp" 4 | #include "storage/mysql_transaction.hpp" 5 | #include "duckdb/storage/statistics/base_statistics.hpp" 6 | #include "duckdb/storage/table_storage_info.hpp" 7 | #include "mysql_scanner.hpp" 8 | 9 | namespace duckdb { 10 | 11 | static bool TableIsInternal(const SchemaCatalogEntry &schema, const string &name) { 12 | if (schema.name != "mysql") { 13 | return false; 14 | } 15 | // this is a list of all system tables 16 | // https://dev.mysql.com/doc/refman/8.0/en/system-schema.html#system-schema-data-dictionary-tables 17 | unordered_set system_tables {"audit_log_filter", 18 | "audit_log_user", 19 | "columns_priv", 20 | "component", 21 | "db", 22 | "default_roles", 23 | "engine_cost", 24 | "event", 25 | "events", 26 | "firewall_group_allowlist", 27 | "firewall_groups", 28 | "firewall_memebership", 29 | "firewall_users", 30 | "firewall_whitelist", 31 | "func", 32 | "general_log", 33 | "global_grants", 34 | "gtid_executed", 35 | "help_category", 36 | "help_keyword", 37 | "help_relation", 38 | "help_topic", 39 | "innodb_index_stats", 40 | "innodb_table_stats", 41 | "innodb_dynamic_metadata", 42 | "ndb_binlog_index", 43 | "password_history", 44 | "parameters", 45 | "plugin", 46 | "procs_priv", 47 | "proxies_priv", 48 | "replication_asynchronous_connection_failover", 49 | "replication_asynchronous_connection_failover_managed", 50 | "replication_group_configuration_version", 51 | "replication_group_member_actions", 52 | "role_edges", 53 | "routines", 54 | "server_cost", 55 | "servers", 56 | "slave_master_info", 57 | "slave_relay_log_info", 58 | "slave_worker_info", 59 | "slow_log", 60 | "tables_priv", 61 | "time_zone", 62 | "time_zone_leap_second", 63 | "time_zone_name", 64 | "time_zone_transition", 65 | "time_zone_transition_type", 66 | "user"}; 67 | return system_tables.find(name) != system_tables.end(); 68 | } 69 | 70 | MySQLTableEntry::MySQLTableEntry(Catalog &catalog, SchemaCatalogEntry &schema, CreateTableInfo &info) 71 | : TableCatalogEntry(catalog, schema, info) { 72 | this->internal = TableIsInternal(schema, name); 73 | } 74 | 75 | MySQLTableEntry::MySQLTableEntry(Catalog &catalog, SchemaCatalogEntry &schema, MySQLTableInfo &info) 76 | : TableCatalogEntry(catalog, schema, *info.create_info) { 77 | this->internal = TableIsInternal(schema, name); 78 | } 79 | 80 | unique_ptr MySQLTableEntry::GetStatistics(ClientContext &context, column_t column_id) { 81 | return nullptr; 82 | } 83 | 84 | void MySQLTableEntry::BindUpdateConstraints(Binder &binder, LogicalGet &, LogicalProjection &, LogicalUpdate &, 85 | ClientContext &) { 86 | } 87 | 88 | TableFunction MySQLTableEntry::GetScanFunction(ClientContext &context, unique_ptr &bind_data) { 89 | auto result = make_uniq(*this); 90 | for (auto &col : columns.Logical()) { 91 | result->types.push_back(col.GetType()); 92 | result->names.push_back(col.GetName()); 93 | } 94 | 95 | bind_data = std::move(result); 96 | 97 | auto function = MySQLScanFunction(); 98 | Value filter_pushdown; 99 | if (context.TryGetCurrentSetting("mysql_experimental_filter_pushdown", filter_pushdown)) { 100 | function.filter_pushdown = BooleanValue::Get(filter_pushdown); 101 | } 102 | return function; 103 | } 104 | 105 | TableStorageInfo MySQLTableEntry::GetStorageInfo(ClientContext &context) { 106 | auto &transaction = Transaction::Get(context, catalog).Cast(); 107 | auto &db = transaction.GetConnection(); 108 | TableStorageInfo result; 109 | result.cardinality = 0; 110 | result.index_info = db.GetIndexInfo(name); 111 | return result; 112 | } 113 | 114 | } // namespace duckdb 115 | -------------------------------------------------------------------------------- /src/storage/mysql_transaction.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_transaction.hpp" 2 | #include "storage/mysql_catalog.hpp" 3 | #include "duckdb/parser/parsed_data/create_view_info.hpp" 4 | #include "duckdb/catalog/catalog_entry/index_catalog_entry.hpp" 5 | #include "duckdb/catalog/catalog_entry/view_catalog_entry.hpp" 6 | #include "mysql_result.hpp" 7 | 8 | namespace duckdb { 9 | 10 | MySQLTransaction::MySQLTransaction(MySQLCatalog &mysql_catalog, TransactionManager &manager, ClientContext &context) 11 | : Transaction(manager, context), access_mode(mysql_catalog.access_mode) { 12 | connection = MySQLConnection::Open(mysql_catalog.connection_string); 13 | } 14 | 15 | MySQLTransaction::~MySQLTransaction() = default; 16 | 17 | void MySQLTransaction::Start() { 18 | transaction_state = MySQLTransactionState::TRANSACTION_NOT_YET_STARTED; 19 | } 20 | void MySQLTransaction::Commit() { 21 | if (transaction_state == MySQLTransactionState::TRANSACTION_STARTED) { 22 | transaction_state = MySQLTransactionState::TRANSACTION_FINISHED; 23 | connection.Execute("COMMIT"); 24 | } 25 | } 26 | void MySQLTransaction::Rollback() { 27 | if (transaction_state == MySQLTransactionState::TRANSACTION_STARTED) { 28 | transaction_state = MySQLTransactionState::TRANSACTION_FINISHED; 29 | connection.Execute("ROLLBACK"); 30 | } 31 | } 32 | 33 | MySQLConnection &MySQLTransaction::GetConnection() { 34 | if (transaction_state == MySQLTransactionState::TRANSACTION_NOT_YET_STARTED) { 35 | transaction_state = MySQLTransactionState::TRANSACTION_STARTED; 36 | string query = "START TRANSACTION"; 37 | if (access_mode == AccessMode::READ_ONLY) { 38 | query += " READ ONLY"; 39 | } 40 | connection.Execute(query); 41 | } 42 | return connection; 43 | } 44 | 45 | unique_ptr MySQLTransaction::Query(const string &query) { 46 | if (transaction_state == MySQLTransactionState::TRANSACTION_NOT_YET_STARTED) { 47 | transaction_state = MySQLTransactionState::TRANSACTION_STARTED; 48 | string transaction_start = "START TRANSACTION"; 49 | if (access_mode == AccessMode::READ_ONLY) { 50 | transaction_start += " READ ONLY"; 51 | } 52 | connection.Query(transaction_start); 53 | return connection.Query(query); 54 | } 55 | return connection.Query(query); 56 | } 57 | 58 | MySQLTransaction &MySQLTransaction::Get(ClientContext &context, Catalog &catalog) { 59 | return Transaction::Get(context, catalog).Cast(); 60 | } 61 | 62 | } // namespace duckdb 63 | -------------------------------------------------------------------------------- /src/storage/mysql_transaction_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "storage/mysql_transaction_manager.hpp" 2 | #include "duckdb/main/attached_database.hpp" 3 | 4 | namespace duckdb { 5 | 6 | MySQLTransactionManager::MySQLTransactionManager(AttachedDatabase &db_p, MySQLCatalog &mysql_catalog) 7 | : TransactionManager(db_p), mysql_catalog(mysql_catalog) { 8 | } 9 | 10 | Transaction &MySQLTransactionManager::StartTransaction(ClientContext &context) { 11 | auto transaction = make_uniq(mysql_catalog, *this, context); 12 | transaction->Start(); 13 | auto &result = *transaction; 14 | lock_guard l(transaction_lock); 15 | transactions[result] = std::move(transaction); 16 | return result; 17 | } 18 | 19 | ErrorData MySQLTransactionManager::CommitTransaction(ClientContext &context, Transaction &transaction) { 20 | auto &mysql_transaction = transaction.Cast(); 21 | mysql_transaction.Commit(); 22 | lock_guard l(transaction_lock); 23 | transactions.erase(transaction); 24 | return ErrorData(); 25 | } 26 | 27 | void MySQLTransactionManager::RollbackTransaction(Transaction &transaction) { 28 | auto &mysql_transaction = transaction.Cast(); 29 | mysql_transaction.Rollback(); 30 | lock_guard l(transaction_lock); 31 | transactions.erase(transaction); 32 | } 33 | 34 | void MySQLTransactionManager::Checkpoint(ClientContext &context, bool force) { 35 | auto &transaction = MySQLTransaction::Get(context, db.GetCatalog()); 36 | auto &db = transaction.GetConnection(); 37 | db.Execute("CHECKPOINT"); 38 | } 39 | 40 | } // namespace duckdb 41 | -------------------------------------------------------------------------------- /test/remove_root_password.sql: -------------------------------------------------------------------------------- 1 | ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''; 2 | -------------------------------------------------------------------------------- /test/sql/attach_alter.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_alter.test 2 | # description: Test ALTER TABLE statements 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s1 (TYPE MYSQL_SCANNER) 11 | 12 | # add column 13 | statement ok 14 | CREATE OR REPLACE TABLE s1.test(i INTEGER); 15 | 16 | statement ok 17 | INSERT INTO s1.test VALUES (42); 18 | 19 | statement ok 20 | ALTER TABLE s1.test ADD COLUMN j INTEGER; 21 | 22 | query II 23 | SELECT i, j FROM s1.test 24 | ---- 25 | 42 NULL 26 | 27 | # duplicate column in rename 28 | statement error 29 | ALTER TABLE s1.test RENAME j TO i 30 | ---- 31 | Duplicate column name 32 | 33 | # rename column 34 | statement ok 35 | ALTER TABLE s1.test RENAME j TO k 36 | 37 | query II 38 | SELECT i, k FROM s1.test 39 | ---- 40 | 42 NULL 41 | 42 | # drop column 43 | statement ok 44 | ALTER TABLE s1.test DROP COLUMN k 45 | 46 | query I 47 | SELECT * FROM s1.test 48 | ---- 49 | 42 50 | 51 | statement ok 52 | DROP TABLE IF EXISTS s1.test2 53 | 54 | # rename table 55 | statement ok 56 | ALTER TABLE s1.test RENAME TO test2 57 | 58 | query I 59 | SELECT * FROM s1.test2 60 | ---- 61 | 42 62 | 63 | # non-existent table 64 | statement error 65 | ALTER TABLE s1.bla ADD COLUMN j INTEGER 66 | ---- 67 | does not exist 68 | 69 | # if exists 70 | statement ok 71 | ALTER TABLE IF EXISTS s1.bla ADD COLUMN j INTEGER 72 | 73 | # drop column if not exists 74 | statement error 75 | ALTER TABLE s1.test2 DROP COLUMN z 76 | ---- 77 | check that column/key exists 78 | 79 | statement error 80 | ALTER TABLE s1.test2 DROP COLUMN IF EXISTS z 81 | ---- 82 | not supported 83 | 84 | # add column if exists 85 | statement error 86 | ALTER TABLE s1.test2 ADD COLUMN i INTEGER 87 | ---- 88 | Duplicate column name 89 | 90 | # unsupported alter table type 91 | statement error 92 | ALTER TABLE s1.test2 ALTER COLUMN i DROP NOT NULL 93 | ---- 94 | Unsupported ALTER TABLE type 95 | 96 | # rename column does not exist 97 | statement error 98 | ALTER TABLE s1.test2 RENAME zz TO i 99 | ---- 100 | Unknown column 101 | -------------------------------------------------------------------------------- /test/sql/attach_clear_cache.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_clear_cache.test 2 | # description: Vigorously clear caches 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS simple (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | DROP TABLE IF EXISTS simple.test 14 | 15 | statement ok 16 | CALL mysql_clear_cache(); 17 | 18 | statement ok 19 | CREATE TABLE simple.test(i INTEGER); 20 | 21 | statement ok 22 | CALL mysql_clear_cache(); 23 | 24 | query I 25 | INSERT INTO simple.test VALUES (42); 26 | ---- 27 | 1 28 | 29 | statement ok 30 | CALL mysql_clear_cache(); 31 | 32 | query I 33 | SELECT * FROM simple.test 34 | ---- 35 | 42 36 | 37 | statement ok 38 | CALL mysql_clear_cache(); 39 | 40 | # insert into a non-existent table 41 | statement error 42 | INSERT INTO simple.tst VALUES (84) 43 | ---- 44 | test 45 | 46 | statement ok 47 | CALL mysql_clear_cache(); 48 | 49 | statement error 50 | INSERT INTO simple.tst VALUES (84) 51 | ---- 52 | test 53 | 54 | statement ok 55 | CALL mysql_clear_cache(); 56 | 57 | # create table as 58 | statement ok 59 | CREATE OR REPLACE TABLE simple.test2 AS SELECT 84 60 | 61 | statement ok 62 | CALL mysql_clear_cache(); 63 | 64 | query I 65 | SELECT * FROM simple.test2 66 | ---- 67 | 84 68 | -------------------------------------------------------------------------------- /test/sql/attach_constraints.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_constraints.test 2 | # description: Test create table with constraints and use describe 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS s1 (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s1.constraints(s STRING NOT NULL, i INTEGER DEFAULT 12); 14 | 15 | query IIIIII 16 | DESCRIBE s1.constraints 17 | ---- 18 | s VARCHAR NO NULL NULL NULL 19 | i INTEGER YES NULL 12 NULL 20 | -------------------------------------------------------------------------------- /test/sql/attach_create_index.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_create_index.test 2 | # description: Test CREATE INDEX 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s.test(i INTEGER); 14 | 15 | statement ok 16 | INSERT INTO s.test VALUES (1), (2), (3); 17 | 18 | statement ok 19 | CREATE INDEX i_index ON s.test(i); 20 | 21 | query I 22 | SELECT * FROM s.test WHERE i=2 23 | ---- 24 | 2 25 | 26 | statement error 27 | DROP INDEX i_index; 28 | ---- 29 | does not exist 30 | 31 | statement ok 32 | DROP INDEX s.i_index; 33 | 34 | statement error 35 | DROP INDEX s.i_index; 36 | ---- 37 | Index with name i_index does not exist 38 | 39 | statement ok 40 | DROP INDEX IF EXISTS s.i_index; 41 | 42 | statement ok 43 | DROP TABLE s.test; 44 | 45 | # multi-dimensional index 46 | statement ok 47 | CREATE TABLE s.test(i INTEGER, j INTEGER); 48 | 49 | statement ok 50 | INSERT INTO s.test VALUES (1, 10), (2, 20), (3, 30); 51 | 52 | statement ok 53 | CREATE INDEX i_index ON s.test(i, j); 54 | 55 | query II 56 | SELECT * FROM s.test WHERE i=2 AND j=20 57 | ---- 58 | 2 20 59 | 60 | statement ok 61 | DROP TABLE s.test CASCADE 62 | 63 | # index with a function 64 | statement ok 65 | CREATE TABLE s.test(s VARCHAR); 66 | 67 | statement ok 68 | INSERT INTO s.test VALUES ('HELLO'), ('hello') 69 | 70 | # cannot create index on blob or text columns 71 | statement error 72 | CREATE UNIQUE INDEX i_index ON s.test(LOWER(s)) 73 | ---- 74 | that returns a BLOB or TEXT 75 | -------------------------------------------------------------------------------- /test/sql/attach_database_size.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/storage/attach_database_size.test 2 | # description: Test fetching the database size 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | PRAGMA database_size 14 | -------------------------------------------------------------------------------- /test/sql/attach_delete.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_delete.test 2 | # description: Test DELETE 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s1 (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s1.test(i INTEGER); 14 | 15 | statement ok 16 | INSERT INTO s1.test VALUES (1), (2), (3), (NULL); 17 | 18 | # simple delete 19 | query I 20 | DELETE FROM s1.test WHERE i=2 21 | ---- 22 | 1 23 | 24 | query I 25 | SELECT * FROM s1.test 26 | ---- 27 | 1 28 | 3 29 | NULL 30 | 31 | # no matches 32 | query I 33 | DELETE FROM s1.test WHERE i=999 34 | ---- 35 | 0 36 | 37 | # expressions are pushed into MySQL directly so we can only use MySQL functions 38 | statement error 39 | DELETE FROM s1.test WHERE date '1992-01-01' + interval (i) days > date '1992-02-01' 40 | ---- 41 | You have an error in your SQL syntax 42 | 43 | query I 44 | SELECT * FROM s1.test 45 | ---- 46 | 1 47 | 3 48 | NULL 49 | 50 | # delete without parameters 51 | query I 52 | DELETE FROM s1.test 53 | ---- 54 | 3 55 | 56 | query I 57 | SELECT * FROM s1.test 58 | 59 | query I 60 | DELETE FROM s1.test 61 | ---- 62 | 0 63 | 64 | # RETURNING statement 65 | statement error 66 | DELETE FROM s1.test RETURNING *; 67 | ---- 68 | not yet supported 69 | 70 | statement ok 71 | INSERT INTO s1.test VALUES (1), (2), (3), (NULL); 72 | 73 | statement ok 74 | CREATE TABLE duckdb_table AS SELECT 1 i UNION ALL SELECT 3 UNION ALL SELECT 1 75 | 76 | # DELETE with join on another table 77 | statement error 78 | DELETE FROM s1.test USING duckdb_table WHERE test.i = duckdb_table.i 79 | ---- 80 | Unsupported operator 81 | 82 | statement error 83 | DELETE FROM s1.test WHERE i=(SELECT MIN(i) FROM s1.test) 84 | ---- 85 | Unsupported operator 86 | 87 | -------------------------------------------------------------------------------- /test/sql/attach_dsn.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_dsn.test 2 | # description: Test attaching with complex DSNs 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement error 10 | ATTACH 'host=' AS s (TYPE MYSQL_SCANNER) 11 | ---- 12 | expected key=value pairs separated by spaces 13 | 14 | statement error 15 | ATTACH 'host="' AS s (TYPE MYSQL_SCANNER) 16 | ---- 17 | unterminated quote 18 | 19 | statement error 20 | ATTACH 'host="\' AS s (TYPE MYSQL_SCANNER) 21 | ---- 22 | backslash at end of dsn 23 | 24 | statement error 25 | ATTACH 'host="\a' AS s (TYPE MYSQL_SCANNER) 26 | ---- 27 | backslash can only escape 28 | 29 | statement error 30 | ATTACH 'host="this string contains \"quoted\" \\spaces"' AS s (TYPE MYSQL_SCANNER) 31 | ---- 32 | this string contains "quoted" \spaces 33 | -------------------------------------------------------------------------------- /test/sql/attach_dynamic_filter.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_dynamic_filter.test 2 | # description: Test attaching with dynamic filters 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS simple (TYPE MYSQL_SCANNER) 11 | 12 | query IIIII 13 | SELECT * FROM simple.unsigned_integers ORDER BY i DESC LIMIT 1; 14 | ---- 15 | 255 65535 16777215 4294967295 18446744073709551615 16 | -------------------------------------------------------------------------------- /test/sql/attach_external_access.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_external_access.test 2 | # description: Test that we cannot attach new databases if external access is disabled 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | SET enable_external_access=false 11 | 12 | statement error 13 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s1 (TYPE MYSQL_SCANNER) 14 | ---- 15 | disabled through configuration 16 | -------------------------------------------------------------------------------- /test/sql/attach_fake_booleans.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_fake_booleans.test 2 | # description: Test fake booleans 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | SET GLOBAL mysql_experimental_filter_pushdown=true; 11 | 12 | statement ok 13 | ATTACH ' host="localhost" user=root port=0 database=mysqlscanner ' AS s1 (TYPE MYSQL_SCANNER) 14 | 15 | query II 16 | SELECT * FROM s1.fake_booleans 17 | ---- 18 | true true 19 | false false 20 | NULL NULL 21 | false true 22 | true false 23 | 24 | query II 25 | SELECT typeof(tinyint_bool), typeof(bit_bool) FROM s1.fake_booleans LIMIT 1 26 | ---- 27 | BOOLEAN BOOLEAN 28 | 29 | statement ok 30 | SET mysql_tinyint1_as_boolean=false 31 | 32 | query II 33 | SELECT typeof(tinyint_bool), typeof(bit_bool) FROM s1.fake_booleans LIMIT 1 34 | ---- 35 | TINYINT BOOLEAN 36 | 37 | statement ok 38 | SET mysql_bit1_as_boolean=false 39 | 40 | query II 41 | SELECT typeof(tinyint_bool), typeof(bit_bool) FROM s1.fake_booleans LIMIT 1 42 | ---- 43 | TINYINT BLOB 44 | 45 | query II 46 | SELECT * FROM s1.fake_booleans 47 | ---- 48 | 1 \x01 49 | 0 \x00 50 | NULL NULL 51 | -128 \x01 52 | 127 \x00 53 | 54 | statement ok 55 | SET mysql_tinyint1_as_boolean=true 56 | 57 | statement ok 58 | SET mysql_bit1_as_boolean=true 59 | 60 | query II 61 | SELECT typeof(tinyint_bool), typeof(bit_bool) FROM s1.fake_booleans LIMIT 1 62 | ---- 63 | BOOLEAN BOOLEAN 64 | -------------------------------------------------------------------------------- /test/sql/attach_filter_pushdown.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_filter_pushdown.test 2 | # description: Test experimental filter pushdown 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | SET GLOBAL mysql_experimental_filter_pushdown=true; 11 | 12 | statement ok 13 | ATTACH 'host=localhost user=root port="0" database="mysqlscanner"' AS s1 (TYPE MYSQL_SCANNER) 14 | 15 | statement ok 16 | CREATE OR REPLACE TABLE s1.filter_pushdown(i INTEGER) 17 | 18 | statement ok 19 | INSERT INTO s1.filter_pushdown FROM range(100000) 20 | 21 | query I 22 | SELECT * FROM s1.filter_pushdown WHERE i=52525 23 | ---- 24 | 52525 25 | 26 | statement ok 27 | CREATE OR REPLACE TABLE s1.filter_pushdown_string(i STRING) 28 | 29 | statement ok 30 | INSERT INTO s1.filter_pushdown_string SELECT CAST(range AS STRING) FROM range(100000); 31 | 32 | query I 33 | SELECT * FROM s1.filter_pushdown_string WHERE i='52525' 34 | ---- 35 | 52525 36 | 37 | # timestamp pushdown 38 | query I 39 | SELECT COUNT(*) FROM s1.datetime_tbl WHERE d >= DATE '2000-01-01' 40 | ---- 41 | 2 42 | 43 | query I 44 | SELECT COUNT(*) FROM s1.datetime_tbl WHERE date_time >= TIMESTAMP '2000-01-01' 45 | ---- 46 | 2 47 | 48 | query I 49 | SELECT COUNT(*) FROM s1.datetime_tbl WHERE ts >= TIMESTAMP '2000-01-01' 50 | ---- 51 | 2 52 | 53 | # boolean pushdown 54 | query I 55 | SELECT COUNT(*) FROM s1.booleans WHERE b = true 56 | ---- 57 | 1 58 | 59 | # string pushdown 60 | query I 61 | SELECT COUNT(*) FROM s1.text_tbl WHERE v= '🦆' 62 | ---- 63 | 1 64 | 65 | # blob pushdown 66 | query I 67 | SELECT COUNT(*) FROM s1.blob_tbl WHERE bl= BLOB '\x80' 68 | ---- 69 | 1 70 | -------------------------------------------------------------------------------- /test/sql/attach_identifiers_insert.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_identifiers_insert.test 2 | # description: Test insert by name with odd identifiers 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s."my table"("my column" INTEGER); 14 | 15 | statement ok 16 | INSERT INTO s."my table" BY NAME SELECT 42 "my column" 17 | 18 | query I 19 | SELECT * FROM s."my table" 20 | ---- 21 | 42 22 | -------------------------------------------------------------------------------- /test/sql/attach_keywords.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/storage/attach_keywords.test 2 | # description: Test quoting in ATTACH with keyword identifiers 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s."TaBlE"("TABLE" INTEGER); 14 | 15 | statement ok 16 | INSERT INTO s."TaBlE" VALUES (42); 17 | 18 | query I 19 | SELECT "TABLE" FROM s."TaBlE" 20 | ---- 21 | 42 22 | 23 | query I 24 | SELECT "table" FROM s."TaBlE" 25 | ---- 26 | 42 27 | 28 | statement ok 29 | CREATE OR REPLACE TABLE s."this 'name' contains ""escaped quotes"""("this 'name' contains ""escaped quotes""" INTEGER); 30 | 31 | statement ok 32 | INSERT INTO s."this 'name' contains ""escaped quotes""" VALUES (84); 33 | 34 | query I 35 | SELECT "this 'name' contains ""escaped quotes""" FROM s."this 'name' contains ""escaped quotes""" 36 | ---- 37 | 84 38 | 39 | # FIXME: ALTER TABLE 40 | mode skip 41 | 42 | statement ok 43 | ALTER TABLE s."this 'name' contains ""escaped quotes""" DROP COLUMN IF EXISTS "hello""world" 44 | 45 | statement error 46 | ALTER TABLE s."this 'name' contains ""escaped quotes""" DROP COLUMN "this 'name' contains ""escaped quotes""" 47 | ---- 48 | no other columns exist 49 | -------------------------------------------------------------------------------- /test/sql/attach_large_insert.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_insert_from_scan_large.test 2 | # description: Test self-referential inserts (inserting from the same table) 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | # create a table 13 | statement ok 14 | CREATE OR REPLACE TABLE s.tbl(i INT); 15 | 16 | query I 17 | INSERT INTO s.tbl FROM range(100000) 18 | ---- 19 | 100000 20 | 21 | query I 22 | INSERT INTO s.tbl FROM s.tbl 23 | ---- 24 | 100000 25 | 26 | query I 27 | SELECT COUNT(*) FROM s.tbl 28 | ---- 29 | 200000 30 | 31 | query I 32 | INSERT INTO s.tbl FROM s.tbl 33 | ---- 34 | 200000 35 | 36 | query I 37 | SELECT COUNT(*) FROM s.tbl 38 | ---- 39 | 400000 40 | -------------------------------------------------------------------------------- /test/sql/attach_limit.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_limit.test 2 | # description: Test LIMIT over an attached table 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | # create a table 13 | statement ok 14 | CREATE OR REPLACE TABLE s.large_tbl AS FROM range(100000) t(i) 15 | 16 | query I 17 | FROM s.large_tbl LIMIT 5 18 | ---- 19 | 0 20 | 1 21 | 2 22 | 3 23 | 4 24 | 25 | query I 26 | FROM s.large_tbl LIMIT 5 OFFSET 5 27 | ---- 28 | 5 29 | 6 30 | 7 31 | 8 32 | 9 33 | -------------------------------------------------------------------------------- /test/sql/attach_many_nulls.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_many_nulls.test 2 | # description: Test reading a table with many NULL values 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s.many_nulls(id INT); 14 | 15 | statement ok 16 | INSERT INTO s.many_nulls SELECT NULL FROM range(10000); 17 | 18 | statement ok 19 | INSERT INTO s.many_nulls VALUES (42); 20 | 21 | query I 22 | SELECT * FROM s.many_nulls WHERE id IS NOT NULL 23 | ---- 24 | 42 25 | -------------------------------------------------------------------------------- /test/sql/attach_mysql_geometry.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_mysql_geometry.test 2 | # description: Test reading geometry data 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | query IIIIIIII 13 | SELECT * FROM s.geom 14 | ---- 15 | \x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\xF0? \x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\xF0? \x00\x00\x00\x00\x01\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\x18@\x00\x00\x00\x00\x00\x00\x18@ \x00\x00\x00\x00\x01\x03\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x1C@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1C@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@ \x00\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\xF0? \x00\x00\x00\x00\x01\x05\x00\x00\x00\x01\x00\x00\x00\x01\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\x18@\x00\x00\x00\x00\x00\x00\x18@ \x00\x00\x00\x00\x01\x06\x00\x00\x00\x01\x00\x00\x00\x01\x03\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x1C@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1C@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14@ \x00\x00\x00\x00\x01\x07\x00\x00\x00\x00\x00\x00\x00 16 | -------------------------------------------------------------------------------- /test/sql/attach_no_database.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_no_database.test 2 | # description: ATTACH without specifying a database 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0' AS simple (TYPE MYSQL_SCANNER) 11 | 12 | statement error 13 | CREATE TABLE simple.test(i INTEGER); 14 | ---- 15 | no database was provided in the connection string 16 | -------------------------------------------------------------------------------- /test/sql/attach_nonexistent_database.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_nonexistent_database.test 2 | # description: ATTACH to a database that does not exist 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement error 10 | ATTACH 'host=localhost user=root port=0 database=xxzxzx' AS simple (TYPE MYSQL_SCANNER) 11 | ---- 12 | Unknown database 'xxzxzx' 13 | -------------------------------------------------------------------------------- /test/sql/attach_read_only.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_read_only.test 2 | # description: Test attaching in read only mode 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS s (TYPE MYSQL_SCANNER, READ_ONLY) 11 | 12 | statement error 13 | CREATE TABLE s.read_only_tbl(i INTEGER); 14 | ---- 15 | read-only mode 16 | 17 | statement error 18 | CALL mysql_execute('s', 'CREATE TABLE test_execute_tbl(i INTEGER)') 19 | ---- 20 | cannot be run in a read-only connection 21 | -------------------------------------------------------------------------------- /test/sql/attach_schemas.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_schemas.test 2 | # description: Test schema creation/dropping/multi-schema support 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE s; 14 | 15 | statement ok 16 | DROP SCHEMA IF EXISTS test_schema CASCADE 17 | 18 | statement ok 19 | CREATE OR REPLACE SCHEMA test_schema; 20 | 21 | statement ok 22 | DROP SCHEMA test_schema 23 | 24 | statement error 25 | DROP SCHEMA test_schema; 26 | ---- 27 | doesn't exist 28 | 29 | statement ok 30 | DROP SCHEMA IF EXISTS test_schema 31 | 32 | statement ok 33 | CREATE SCHEMA test_schema; 34 | 35 | statement ok 36 | CREATE TABLE test_schema.test_table(i INTEGER); 37 | 38 | statement ok 39 | INSERT INTO test_schema.test_table VALUES (42); 40 | 41 | query I 42 | SELECT * FROM test_schema.test_table 43 | ---- 44 | 42 45 | 46 | # mysql doesn't need CASCADE to drop schemas/databases 47 | statement ok 48 | DROP SCHEMA test_schema 49 | -------------------------------------------------------------------------------- /test/sql/attach_secret.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_secret.test 2 | # description: Test attaching using a secret 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | PRAGMA enable_verification 11 | 12 | # attach using default secret 13 | statement ok 14 | CREATE SECRET ( 15 | TYPE MYSQL, 16 | HOST localhost, 17 | USER root, 18 | PORT 0, 19 | DATABASE unknown_db 20 | ); 21 | 22 | statement error 23 | ATTACH '' AS secret_attach (TYPE MYSQL) 24 | ---- 25 | unknown_db 26 | 27 | # attach using an explicit secret 28 | statement ok 29 | CREATE SECRET mysql_secret ( 30 | TYPE MYSQL, 31 | HOST localhost, 32 | USER root, 33 | PORT 0, 34 | DATABASE mysqlscanner 35 | ); 36 | 37 | statement ok 38 | ATTACH '' AS secret_attach (TYPE MYSQL, SECRET mysql_secret) 39 | 40 | query I 41 | SELECT path FROM duckdb_databases() WHERE database_name='secret_attach' 42 | ---- 43 | (empty) 44 | 45 | statement ok 46 | DETACH secret_attach 47 | 48 | statement ok 49 | CREATE OR REPLACE SECRET mysql_secret ( 50 | TYPE MYSQL, 51 | HOST localhost, 52 | USER root, 53 | PORT 0, 54 | DATABASE unknown_db 55 | ); 56 | 57 | # non-existent database 58 | statement error 59 | ATTACH '' AS secret_attach (TYPE MYSQL, SECRET mysql_secret) 60 | ---- 61 | unknown_db 62 | 63 | # we can override options in the attach string 64 | statement ok 65 | ATTACH 'database=mysqlscanner' AS secret_attach (TYPE MYSQL, SECRET mysql_secret) 66 | 67 | statement error 68 | CREATE SECRET new_secret ( 69 | TYPE MYSQL, 70 | UNKNOWN_OPTION xx 71 | ); 72 | ---- 73 | unknown_option 74 | 75 | # unknown secret 76 | statement error 77 | ATTACH '' AS secret_attach (TYPE MYSQL, SECRET unknown_secret) 78 | ---- 79 | unknown_secret 80 | -------------------------------------------------------------------------------- /test/sql/attach_simple.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_simple.test 2 | # description: Basic ATTACH and query test 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS simple (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | DROP TABLE IF EXISTS simple.test 14 | 15 | statement ok 16 | CREATE TABLE simple.test(i INTEGER); 17 | 18 | query I 19 | INSERT INTO simple.test VALUES (42); 20 | ---- 21 | 1 22 | 23 | query I 24 | SELECT * FROM simple.test 25 | ---- 26 | 42 27 | 28 | # insert into a non-existent table 29 | statement error 30 | INSERT INTO simple.tst VALUES (84) 31 | ---- 32 | test 33 | 34 | statement error 35 | INSERT INTO simple.tst VALUES (84) 36 | ---- 37 | test 38 | 39 | # create table as 40 | statement ok 41 | CREATE OR REPLACE TABLE simple.test2 AS SELECT 84 42 | 43 | query I 44 | SELECT * FROM simple.test2 45 | ---- 46 | 84 47 | -------------------------------------------------------------------------------- /test/sql/attach_ssl.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_ssl.test 2 | # description: ATTACH with SSL parameters 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | # invalid ssl mode 10 | statement error 11 | ATTACH 'host=localhost user=root port=0 database=mysql ssl_mode=xxx' AS simple (TYPE MYSQL_SCANNER) 12 | ---- 13 | ssl mode must be either 14 | 15 | # don't ask me why this works 16 | statement ok 17 | ATTACH 'host=localhost user=root port=0 database=mysql ssl_mode=required ssl_ca=/xxx/ ssl_capath=/xxx/ ssl_cert=/xxx/ ssl_cipher=/xxx/ ssl_crl=/xxx/ ssl_crlpath=/xxx/ ssl_key=/xxx/' AS simple (TYPE MYSQL_SCANNER) 18 | -------------------------------------------------------------------------------- /test/sql/attach_timestamp_issue_16.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_timestamp_issue_16.test 2 | # description: Test creating a table with TIMESTAMP_TZ values 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE s 14 | 15 | statement ok 16 | DROP TABLE IF EXISTS datetime_tbl_2 17 | 18 | statement ok 19 | CREATE TABLE datetime_tbl_2 AS SELECT * FROM datetime_tbl 20 | 21 | query IIIII 22 | SELECT * FROM datetime_tbl_2 EXCEPT SELECT * FROM datetime_tbl 23 | ---- 24 | -------------------------------------------------------------------------------- /test/sql/attach_timestamp_tz_roundtrip.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duckdb/duckdb-mysql/3b7b58f0436da3ba6e556b3e178f331381d0cdcf/test/sql/attach_timestamp_tz_roundtrip.test -------------------------------------------------------------------------------- /test/sql/attach_timezone_insert.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_timezone_insert.test 2 | # description: Test inserting timestamp with time zones into MySQL 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | require icu 10 | 11 | statement ok 12 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 13 | 14 | statement ok 15 | USE s 16 | 17 | statement ok 18 | SET TimeZone='UTC' 19 | 20 | statement ok 21 | CREATE OR REPLACE TABLE timestamp_with_tz_tbl(ts TIMESTAMP WITH TIME ZONE); 22 | 23 | statement ok 24 | INSERT INTO timestamp_with_tz_tbl VALUES (TIMESTAMP '2000-01-01 12:12:12') 25 | 26 | query I 27 | SELECT * FROM timestamp_with_tz_tbl 28 | ---- 29 | 2000-01-01 12:12:12+00 30 | 31 | statement ok 32 | INSERT INTO timestamp_with_tz_tbl VALUES (TIMESTAMPTZ '2001-01-01 12:12:12+04:00') 33 | 34 | query I 35 | SELECT * FROM timestamp_with_tz_tbl 36 | ---- 37 | 2000-01-01 12:12:12+00 38 | 2001-01-01 08:12:12+00 39 | 40 | statement ok 41 | SET TimeZone='EST' 42 | 43 | query I 44 | SELECT * FROM timestamp_with_tz_tbl 45 | ---- 46 | 2000-01-01 07:12:12-05 47 | 2001-01-01 03:12:12-05 48 | 49 | statement ok 50 | create or replace table new_tbl(t timestamp with time zone); 51 | 52 | statement ok 53 | INSERT INTO new_tbl SELECT * FROM timestamp_with_tz_tbl 54 | 55 | statement ok 56 | SET TimeZone='UTC' 57 | 58 | query I 59 | SELECT * FROM new_tbl 60 | ---- 61 | 2000-01-01 12:12:12+00 62 | 2001-01-01 08:12:12+00 63 | -------------------------------------------------------------------------------- /test/sql/attach_transactions.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_transactions.test 2 | # description: Test ATTACH and transaction semantics (BEGIN, COMMIT, ROLLBACK, etc) 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | DROP TABLE IF EXISTS s.test 14 | 15 | # roll back create table 16 | statement ok 17 | BEGIN 18 | 19 | statement ok 20 | CREATE TABLE s.test(i INTEGER); 21 | 22 | statement ok 23 | ROLLBACK 24 | 25 | # DDL statements are not transactional in MySQL so rolling back does nothing 26 | statement ok 27 | SELECT * FROM s.test 28 | 29 | # roll back insert 30 | statement ok 31 | BEGIN 32 | 33 | statement ok 34 | INSERT INTO s.test VALUES (42) 35 | 36 | query I 37 | SELECT * FROM s.test 38 | ---- 39 | 42 40 | 41 | statement ok 42 | ROLLBACK 43 | 44 | query I 45 | SELECT * FROM s.test 46 | ---- 47 | 48 | # commit insert 49 | statement ok 50 | BEGIN 51 | 52 | statement ok 53 | INSERT INTO s.test VALUES (1), (2), (3) 54 | 55 | statement ok 56 | COMMIT 57 | 58 | query I 59 | SELECT * FROM s.test 60 | ---- 61 | 1 62 | 2 63 | 3 64 | 65 | # rollback delete 66 | statement ok 67 | BEGIN 68 | 69 | statement ok 70 | DELETE FROM s.test WHERE i=2 71 | 72 | query I 73 | SELECT * FROM s.test 74 | ---- 75 | 1 76 | 3 77 | 78 | statement ok 79 | ROLLBACK 80 | 81 | query I 82 | SELECT * FROM s.test 83 | ---- 84 | 1 85 | 2 86 | 3 87 | 88 | # rollback update 89 | statement ok 90 | BEGIN 91 | 92 | statement ok 93 | UPDATE s.test SET i=i+100 94 | 95 | query I 96 | SELECT * FROM s.test 97 | ---- 98 | 101 99 | 102 100 | 103 101 | 102 | statement ok 103 | ROLLBACK 104 | 105 | query I 106 | SELECT * FROM s.test 107 | ---- 108 | 1 109 | 2 110 | 3 111 | 112 | # rollback large delete 113 | statement ok 114 | BEGIN 115 | 116 | statement ok 117 | INSERT INTO s.test SELECT 2 FROM range(10000); 118 | 119 | statement ok 120 | DELETE FROM s.test WHERE i=2 121 | 122 | query I 123 | SELECT * FROM s.test 124 | ---- 125 | 1 126 | 3 127 | 128 | statement ok 129 | ROLLBACK 130 | 131 | query I 132 | SELECT * FROM s.test 133 | ---- 134 | 1 135 | 2 136 | 3 137 | 138 | # FIXME - alter table 139 | mode skip 140 | 141 | # rollback alter table 142 | statement ok 143 | BEGIN 144 | 145 | statement ok 146 | ALTER TABLE s.test ADD COLUMN b INTEGER 147 | 148 | query II 149 | SELECT * FROM s.test 150 | ---- 151 | 1 NULL 152 | 2 NULL 153 | 3 NULL 154 | 155 | statement ok 156 | UPDATE s.test SET b=i+100 WHERE i!=2 157 | 158 | query II 159 | SELECT * FROM s.test 160 | ---- 161 | 1 101 162 | 2 NULL 163 | 3 103 164 | 165 | statement ok 166 | ROLLBACK 167 | 168 | query I 169 | SELECT * FROM s.test 170 | ---- 171 | 1 172 | 2 173 | 3 174 | -------------------------------------------------------------------------------- /test/sql/attach_types.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_types.test 2 | # description: Test attaching with "test_all_types" 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | DROP TABLE IF EXISTS s.types 14 | 15 | statement ok 16 | CREATE TABLE s.types(i INTEGER, j BIGINT, k DOUBLE, l VARCHAR); 17 | 18 | statement ok 19 | INSERT INTO s.types VALUES (42, 84, 0.5, 'hello world this is my string'); 20 | 21 | statement ok 22 | INSERT INTO s.types VALUES (NULL, NULL, NULL, NULL); 23 | 24 | statement ok 25 | SELECT * FROM s.types 26 | 27 | query IIII 28 | SELECT * FROM s.types 29 | ---- 30 | 42 84 0.5 hello world this is my string 31 | NULL NULL NULL NULL 32 | 33 | # test all types 34 | statement ok 35 | CREATE TABLE all_types_tbl AS SELECT * 36 | EXCLUDE (float, double, ubigint, hugeint, uhugeint, int_array, double_array, date_array, timestamp_array, timestamptz_array, varchar_array, nested_int_array, struct, struct_of_arrays, array_of_structs, map, "union",fixed_int_array,fixed_varchar_array,fixed_nested_varchar_array,list_of_fixed_int_array,fixed_array_of_int_list,fixed_nested_int_array,struct_of_fixed_array,fixed_struct_array, varint 37 | ) 38 | REPLACE( 39 | CASE WHEN int IS NOT NULL THEN '2000-01-01' ELSE NULL END AS date, 40 | CASE WHEN int IS NOT NULL THEN '2000-01-01 01:02:03' ELSE NULL END AS timestamp, 41 | CASE WHEN int IS NOT NULL THEN '2000-01-01 01:02:03' ELSE NULL END AS timestamp_s, 42 | CASE WHEN int IS NOT NULL THEN '2000-01-01 01:02:03' ELSE NULL END AS timestamp_ms, 43 | CASE WHEN int IS NOT NULL THEN '2000-01-01 01:02:03' ELSE NULL END AS timestamp_ns, 44 | CASE WHEN int IS NOT NULL THEN '2000-01-01 01:02:03' ELSE NULL END AS timestamp_tz, 45 | --CASE WHEN int IS NOT NULL THEN replace(varchar, chr(0), ' ') ELSE NULL END AS varchar, 46 | CASE WHEN int IS NOT NULL THEN '00:00:00+15:00' ELSE NULL END AS time_tz, 47 | small_enum::VARCHAR AS small_enum, 48 | medium_enum::VARCHAR AS medium_enum, 49 | large_enum::VARCHAR AS large_enum, 50 | ) 51 | FROM test_all_types(); 52 | 53 | statement ok 54 | CREATE OR REPLACE TABLE s.all_types AS FROM all_types_tbl 55 | 56 | query IIIIIIIIIIIIIIIIIIIIIIIIIIII 57 | SELECT COLUMNS(*)::VARCHAR FROM all_types_tbl 58 | ---- 59 | false -128 -32768 -2147483648 -9223372036854775808 0 0 0 2000-01-01 00:00:00 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 00:00:00+15:00 2000-01-01 01:02:03 -999.9 -99999.9999 -999999999999.999999 -9999999999999999999999999999.9999999999 00000000-0000-0000-0000-000000000000 00:00:00 🦆🦆🦆🦆🦆🦆 thisisalongblob\x00withnullbytes 0010001001011100010101011010111 DUCK_DUCK_ENUM enum_0 enum_0 60 | true 127 32767 2147483647 9223372036854775807 255 65535 4294967295 2000-01-01 24:00:00 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 00:00:00+15:00 2000-01-01 01:02:03 999.9 99999.9999 999999999999.999999 9999999999999999999999999999.9999999999 ffffffff-ffff-ffff-ffff-ffffffffffff 83 years 3 months 999 days 00:16:39.999999 goo\0se \x00\x00\x00a 10101 GOOSE enum_299 enum_69999 61 | NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 62 | 63 | query IIIIIIIIIIIIIIIIIIIIIIIIIIII 64 | SELECT COLUMNS(*)::VARCHAR FROM s.all_types 65 | ---- 66 | false -128 -32768 -2147483648 -9223372036854775808 0 0 0 2000-01-01 00:00:00 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 00:00:00+15:00 2000-01-01 01:02:03 -999.9 -99999.9999 -999999999999.999999 -9999999999999999999999999999.9999999999 00000000-0000-0000-0000-000000000000 00:00:00 🦆🦆🦆🦆🦆🦆 thisisalongblob\x00withnullbytes 0010001001011100010101011010111 DUCK_DUCK_ENUM enum_0 enum_0 67 | true 127 32767 2147483647 9223372036854775807 255 65535 4294967295 2000-01-01 24:00:00 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 2000-01-01 01:02:03 00:00:00+15:00 2000-01-01 01:02:03 999.9 99999.9999 999999999999.999999 9999999999999999999999999999.9999999999 ffffffff-ffff-ffff-ffff-ffffffffffff 83 years 3 months 999 days 00:16:39.999999 goo\0se \x00\x00\x00a 10101 GOOSE enum_299 enum_69999 68 | NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 69 | 70 | # filter pushdown 71 | foreach column_name bool tinyint smallint int bigint utinyint usmallint uint date time timestamp timestamp_s timestamp_ms timestamp_ns time_tz timestamp_tz dec_4_1 dec_9_4 dec_18_6 dec38_10 uuid interval varchar blob bit small_enum medium_enum large_enum 72 | 73 | statement ok 74 | SET VARIABLE minimum_value=(SELECT MIN(${column_name}) min_val FROM s.all_types); 75 | 76 | query I 77 | SELECT ANY_VALUE(${column_name})=getvariable('minimum_value') FROM s.all_types WHERE ${column_name}=getvariable('minimum_value') 78 | ---- 79 | true 80 | 81 | query I 82 | SELECT ANY_VALUE(${column_name})=getvariable('minimum_value') FROM s.all_types WHERE ${column_name} IN (getvariable('minimum_value'), getvariable('minimum_value')) 83 | ---- 84 | true 85 | 86 | endloop 87 | -------------------------------------------------------------------------------- /test/sql/attach_types_blob.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_types_blob.test 2 | # description: Test inserting/querying blobs 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s.blobs(b BLOB); 14 | 15 | statement ok 16 | INSERT INTO s.blobs VALUES ('\x00\x00\x00'); 17 | 18 | statement ok 19 | INSERT INTO s.blobs VALUES (NULL); 20 | 21 | statement ok 22 | INSERT INTO s.blobs VALUES ('thisisalongstring\x00\x00withnullbytes'); 23 | 24 | statement ok 25 | INSERT INTO s.blobs VALUES ('\x80\x80\xFF'); 26 | 27 | query I 28 | SELECT * FROM s.blobs 29 | ---- 30 | \x00\x00\x00 31 | NULL 32 | thisisalongstring\x00\x00withnullbytes 33 | \x80\x80\xFF 34 | -------------------------------------------------------------------------------- /test/sql/attach_unicode.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_unicode.test 2 | # description: Test Unicode Attach 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s1 (TYPE MYSQL_SCANNER) 11 | 12 | query II 13 | FROM s1.latin_unicode 14 | ---- 15 | 1 Graça 16 | 17 | statement ok 18 | CREATE TABLE latin_unicode AS FROM s1.latin_unicode 19 | 20 | query II 21 | FROM latin_unicode 22 | ---- 23 | 1 Graça 24 | -------------------------------------------------------------------------------- /test/sql/attach_update.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/storage/attach_update.test 2 | # description: Test UPDATE statement 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s1 (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CREATE OR REPLACE TABLE s1.test(i INTEGER); 14 | 15 | statement ok 16 | INSERT INTO s1.test VALUES (1), (2), (3), (NULL); 17 | 18 | # global update 19 | statement ok 20 | UPDATE s1.test SET i = i + 1; 21 | 22 | query I 23 | SELECT * FROM s1.test 24 | ---- 25 | 2 26 | 3 27 | 4 28 | NULL 29 | 30 | # update with WHERE statement 31 | statement ok 32 | UPDATE s1.test SET i = i + 100 WHERE i = 3 33 | 34 | query I 35 | SELECT * FROM s1.test ORDER BY 1 36 | ---- 37 | 2 38 | 4 39 | 103 40 | NULL 41 | 42 | # update with NULL value 43 | statement ok 44 | UPDATE s1.test SET i = NULL WHERE i = 2 45 | 46 | query I 47 | SELECT * FROM s1.test ORDER BY 1 48 | ---- 49 | 4 50 | 103 51 | NULL 52 | NULL 53 | 54 | # update with DEFAULT clause 55 | query I 56 | UPDATE s1.test SET i = DEFAULT WHERE i = 4 57 | ---- 58 | 1 59 | 60 | query I 61 | SELECT * FROM s1.test ORDER BY 1 62 | ---- 63 | 103 64 | NULL 65 | NULL 66 | NULL 67 | 68 | # multi column update in different orders 69 | statement ok 70 | CREATE OR REPLACE TABLE s1.test(i INTEGER PRIMARY KEY, j INTEGER, k INTEGER); 71 | 72 | query I 73 | INSERT INTO s1.test VALUES (1, 10, 100), (2, NULL, 200), (3, 30, NULL), (4, 40, 400); 74 | ---- 75 | 4 76 | 77 | query III 78 | SELECT * FROM s1.test ORDER BY 1 79 | ---- 80 | 1 10 100 81 | 2 NULL 200 82 | 3 30 NULL 83 | 4 40 400 84 | 85 | statement ok 86 | UPDATE s1.test SET k=990 + i, i=i, j=99 WHERE i=2 OR i=4 87 | 88 | query III 89 | SELECT * FROM s1.test ORDER BY 1 90 | ---- 91 | 1 10 100 92 | 2 99 992 93 | 3 30 NULL 94 | 4 99 994 95 | 96 | # duplicates in SET statements 97 | statement error 98 | UPDATE s1.test SET j=k, j=i 99 | ---- 100 | Multiple assignments to same column 101 | 102 | # RETURNING statement 103 | statement error 104 | UPDATE s1.test SET i=42 RETURNING *; 105 | ---- 106 | not yet supported 107 | 108 | statement error 109 | UPDATE s1.test SET i=42 WHERE i=(SELECT MIN(i) FROM s1.test); 110 | ---- 111 | Unsupported operator 112 | 113 | -------------------------------------------------------------------------------- /test/sql/attach_uri.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_uri.test 2 | # description: Test attaching using a URI 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | PRAGMA enable_verification 11 | 12 | # create a default secret that fills in the correct missing values in the URI 13 | statement ok 14 | CREATE SECRET ( 15 | TYPE MYSQL, 16 | HOST localhost, 17 | USER root, 18 | PORT 0 19 | ); 20 | 21 | # uri with mysql: prefix 22 | statement ok 23 | ATTACH 'mysql:root@localhost' AS uri_attach 24 | 25 | statement ok 26 | DETACH uri_attach 27 | 28 | # try various URIs 29 | foreach uri localhost root@localhost mysql://localhost mysql://localhost:0 mysql://root@localhost mysql://root@localhost:0 mysql://root:@localhost:0 mysql://root:@localhost:0?compression=preferred 30 | 31 | statement ok 32 | ATTACH '${uri}' AS uri_attach (TYPE MYSQL) 33 | 34 | statement ok 35 | DETACH uri_attach 36 | 37 | endloop 38 | 39 | # now with an unknown database 40 | foreach uri mysql://localhost/unknown_db mysql://localhost:0/unknown_db mysql://root@localhost/unknown_db mysql://root@localhost:0/unknown_db mysql://root:@localhost:0/unknown_db 41 | 42 | statement error 43 | ATTACH '${uri}' AS secret_attach (TYPE MYSQL) 44 | ---- 45 | unknown_db 46 | 47 | endloop 48 | 49 | # invalid URIs 50 | foreach uri mysql://abc@abc@localhost mysql://abc:abc:abc mysql://abc:abc/abc/abc mysql://localhost?abc mysql://localhost?abc=1?abc=2 51 | 52 | statement error 53 | ATTACH '${uri}' AS secret_attach (TYPE MYSQL) 54 | ---- 55 | Invalid URI string 56 | 57 | endloop 58 | 59 | # unrecognized attribute 60 | statement error 61 | ATTACH 'mysql://root@localhost?unrecognized_attribute=42' AS secret_attach (TYPE MYSQL) 62 | ---- 63 | unrecognized_attribute 64 | 65 | statement error 66 | ATTACH 'mysql://root@localhost?attribute_with_escape_codes_%20%3C%3E%23%25%2B%7B%7D%7C%5C%5E%7E%5B%5D%60%3B%2F%3F%3A%40%3D%26%24%XX%=42' AS secret_attach (TYPE MYSQL) 67 | ---- 68 | attribute_with_escape_codes_ <>#%+{}|\^~[]`;/?;@=&$%XX% 69 | -------------------------------------------------------------------------------- /test/sql/attach_views.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_views.test 2 | # description: Test attaching, creating and querying views 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysql' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE s; 14 | 15 | statement ok 16 | DROP VIEW IF EXISTS v1 17 | 18 | statement ok 19 | CREATE VIEW v1 AS SELECT 42 AS i; 20 | 21 | query I 22 | SELECT i FROM v1 23 | ---- 24 | 42 25 | 26 | statement error 27 | CREATE VIEW v1 AS SELECT 84; 28 | ---- 29 | already exists 30 | 31 | statement error 32 | INSERT INTO v1 VALUES (42); 33 | ---- 34 | not insertable-into 35 | 36 | # FIXME - error message here is not very descriptive 37 | statement error 38 | UPDATE v1 SET i=84 39 | ---- 40 | 41 | statement error 42 | DELETE FROM v1 43 | ---- 44 | 45 | statement error 46 | INSERT INTO v1 VALUES (1, 1); 47 | ---- 48 | table v1 has 1 columns but 2 values were supplied 49 | 50 | statement ok 51 | CREATE VIEW IF NOT EXISTS v1 AS SELECT 84; 52 | 53 | statement ok 54 | CREATE OR REPLACE VIEW v1 AS SELECT 84; 55 | 56 | query I 57 | SELECT * FROM v1 58 | ---- 59 | 84 60 | 61 | statement error 62 | DROP TABLE v1 63 | ---- 64 | Unknown table 65 | 66 | statement ok 67 | DROP VIEW v1 68 | 69 | statement error 70 | SELECT * FROM v1 71 | ---- 72 | Table with name v1 does not exist 73 | 74 | statement error 75 | DROP VIEW v1 76 | ---- 77 | View with name v1 does not exist 78 | 79 | statement ok 80 | CREATE VIEW v1(a) AS SELECT 99; 81 | 82 | query I 83 | SELECT a FROM v1 84 | ---- 85 | 99 86 | 87 | # special names 88 | statement ok 89 | CREATE OR REPLACE VIEW "table "" table '' table"("column "" column '' column") AS SELECT 3 90 | 91 | query I 92 | SELECT "column "" column '' column" FROM "table "" table '' table" 93 | ---- 94 | 3 95 | -------------------------------------------------------------------------------- /test/sql/attach_zero_date.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/attach_zero_date.test 2 | # description: Test querying zero dates 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS simple (TYPE MYSQL_SCANNER) 11 | 12 | query II 13 | SELECT * FROM simple.zero_date 14 | ---- 15 | NULL NULL 16 | -------------------------------------------------------------------------------- /test/sql/failed_to_connect.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/failed_to_connect.test 2 | # description: ATTACH to a non-existent server 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | statement error 8 | ATTACH 'host=localhost user=zzz port=42 database=xxzxzx' AS simple (TYPE MYSQL_SCANNER) 9 | ---- 10 | Failed to connect 11 | -------------------------------------------------------------------------------- /test/sql/insert_backslash.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/insert_backslash.test 2 | # description: Issue #58 - value is "\" can not copy to mysql 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | statement ok 16 | CREATE OR REPLACE TABLE backslash_tbl(v VARCHAR); 17 | 18 | statement ok 19 | INSERT INTO backslash_tbl VALUES ('\') 20 | 21 | query I 22 | SELECT * FROM backslash_tbl 23 | ---- 24 | \ 25 | -------------------------------------------------------------------------------- /test/sql/mysql_execute.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/mysql_execute.test 2 | # description: Test mysql_execute 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | CALL mysql_execute('s', 'DROP TABLE IF EXISTS test_execute_tbl') 14 | 15 | statement ok 16 | CALL mysql_execute('s', 'CREATE TABLE test_execute_tbl(i INTEGER)') 17 | 18 | statement ok 19 | INSERT INTO s.test_execute_tbl VALUES(42) 20 | 21 | query I 22 | FROM s.test_execute_tbl 23 | ---- 24 | 42 25 | 26 | statement error 27 | CALL mysql_execute('s', 'CREATE TABLE test_execute_tbl(i INTEGER)') 28 | ---- 29 | already exists 30 | -------------------------------------------------------------------------------- /test/sql/mysql_query.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/mysql_query.test 2 | # description: Test mysql_query 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS simple (TYPE MYSQL_SCANNER) 11 | 12 | # simple example 13 | query I 14 | SELECT * FROM mysql_query('simple', 'SELECT 42') 15 | ---- 16 | 42 17 | 18 | query I 19 | SELECT col FROM mysql_query('simple', 'SELECT 42 AS col') 20 | ---- 21 | 42 22 | 23 | query I 24 | SELECT * FROM mysql_query('simple', 'SELECT NULL') 25 | ---- 26 | NULL 27 | 28 | # test many types 29 | query I 30 | SELECT typeof(COLUMNS(*)) FROM mysql_query('simple', 'SELECT * FROM booleans') LIMIT 1 31 | ---- 32 | BOOLEAN 33 | 34 | query I 35 | SELECT * FROM mysql_query('simple', 'SELECT * FROM booleans') 36 | ---- 37 | false 38 | true 39 | NULL 40 | 41 | query IIIII 42 | SELECT typeof(COLUMNS(*)) FROM mysql_query('simple', 'SELECT * FROM signed_integers') LIMIT 1 43 | ---- 44 | TINYINT SMALLINT INTEGER INTEGER BIGINT 45 | 46 | query IIIII 47 | SELECT * FROM mysql_query('simple', 'SELECT * FROM signed_integers') 48 | ---- 49 | -128 -32768 -8388608 -2147483648 -9223372036854775808 50 | 127 32767 8388607 2147483647 9223372036854775807 51 | NULL NULL NULL NULL NULL 52 | 53 | query IIIII 54 | SELECT typeof(COLUMNS(*)) FROM mysql_query('simple', 'SELECT * FROM unsigned_integers') LIMIT 1 55 | ---- 56 | UTINYINT USMALLINT UINTEGER UINTEGER UBIGINT 57 | 58 | query IIIII 59 | SELECT * FROM mysql_query('simple', 'SELECT * FROM unsigned_integers') 60 | ---- 61 | 0 0 0 0 0 62 | 255 65535 16777215 4294967295 18446744073709551615 63 | NULL NULL NULL NULL NULL 64 | 65 | query IIII 66 | SELECT typeof(COLUMNS(*)) FROM mysql_query('simple', 'SELECT * FROM floating_points') LIMIT 1 67 | ---- 68 | FLOAT DOUBLE FLOAT DOUBLE 69 | 70 | query IIII 71 | SELECT * FROM mysql_query('simple', 'SELECT * FROM floating_points') 72 | ---- 73 | 0.0 0.0 0.0 0.0 74 | 0.5 0.5 0.5 0.5 75 | -0.5 -0.5 0.0 0.0 76 | NULL NULL NULL NULL 77 | 78 | query IIIII 79 | SELECT * FROM mysql_query('simple', 'SELECT * FROM decimals') 80 | ---- 81 | 0.5 1234.1 12345678.12 12345678901234567.123 1.2345678901234568e+35 82 | -0.5 -1234.1 -12345678.12 -12345678901234567.123 -1.2345678901234568e+35 83 | NULL NULL NULL NULL NULL 84 | 85 | query IIIII 86 | SELECT typeof(COLUMNS(*)) FROM mysql_query('simple', 'SELECT * FROM decimals') LIMIT 1 87 | ---- 88 | DECIMAL(2,1) DECIMAL(5,1) DECIMAL(10,2) DECIMAL(20,3) DOUBLE 89 | 90 | query II 91 | SELECT * FROM mysql_query('simple', 'SELECT * FROM fake_booleans') 92 | ---- 93 | 1 true 94 | 0 false 95 | NULL NULL 96 | -128 true 97 | 127 false 98 | 99 | query II 100 | SELECT * FROM mysql_query('simple', 'SELECT * FROM bits') 101 | ---- 102 | true \x00\x00\x00\x00\x00\x01UU 103 | NULL NULL 104 | 105 | query III 106 | SELECT * FROM mysql_query('simple', 'SELECT * FROM text_tbl') 107 | ---- 108 | ab ab thisisalongstring 109 | (empty) (empty) (empty) 110 | 🦆 🦆 🦆🦆🦆🦆 111 | NULL NULL NULL 112 | 113 | query III 114 | SELECT * FROM mysql_query('simple', 'SELECT * FROM blob_tbl') 115 | ---- 116 | c\x00\x00\x00 c\x00\x00 c\x00\x00 117 | \x00\x00\x00\x00 (empty) (empty) 118 | \x80\x00\x00\x00 \x80 \x80 119 | NULL NULL NULL 120 | 121 | query I 122 | SELECT * FROM mysql_query('simple', 'SELECT * FROM enum_tbl') 123 | ---- 124 | x-small 125 | small 126 | medium 127 | large 128 | x-large 129 | NULL 130 | 131 | query I 132 | SELECT * FROM mysql_query('simple', 'SELECT * FROM set_tbl') 133 | ---- 134 | a,d 135 | a,d 136 | a,d 137 | a,d 138 | a,d 139 | 140 | query I 141 | SELECT * FROM mysql_query('simple', 'SELECT * FROM json_tbl') 142 | ---- 143 | {"k1": "value", "k2": 10} 144 | ["abc", 10, null, true, false] 145 | NULL 146 | 147 | # errors 148 | statement error 149 | SELECT * FROM mysql_query('simple', 'SELEC 42') 150 | ---- 151 | Failed to run query 152 | 153 | statement error 154 | SELECT * FROM mysql_query('simple', 'SELECT 42 a, 42 a') 155 | ---- 156 | duplicate column 157 | 158 | require icu 159 | 160 | statement ok 161 | SET TimeZone='UTC' 162 | 163 | query IIIII 164 | SELECT * FROM mysql_query('simple', 'SELECT * FROM datetime_tbl') 165 | ---- 166 | 2020-02-03 2029-02-14 08:47:23 2029-02-14 14:47:23+00 23:59:59 1901 167 | 1000-01-01 1000-01-01 00:00:00 1970-01-01 06:00:01+00 -838:59:59 2155 168 | 9999-12-31 9999-12-31 23:59:59 2038-01-19 04:14:07+00 838:59:59 2000 169 | NULL NULL NULL NULL NULL 170 | 171 | # issue #65 - Decimal data type conversion issue with mysql_query function 172 | query II 173 | SELECT * FROM mysql_query('simple', 'SELECT * FROM tbl_issue65') 174 | ---- 175 | 1 1.11 176 | 2 2.22 177 | 3 3.33 178 | -------------------------------------------------------------------------------- /test/sql/scan_bit.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_bit.test 2 | # description: Test scanning tables with decimals 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query II 16 | SELECT * FROM bits 17 | ---- 18 | \x05 \x00\x00\x00\x00\x00\x01UU 19 | NULL NULL 20 | 21 | # bits in MySQL have a max width 22 | statement error 23 | INSERT INTO bits VALUES (NULL, BLOB '\x00\x00\x00\x00\x00\x00\x00\x00\x00'); 24 | ---- 25 | Out of range value for column 26 | -------------------------------------------------------------------------------- /test/sql/scan_blob.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_blob.test 2 | # description: Test scanning tables with blob columns 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query III 16 | SELECT * FROM blob_tbl 17 | ---- 18 | c\x00\x00\x00 c\x00\x00 c\x00\x00 19 | \x00\x00\x00\x00 (empty) (empty) 20 | \x80\x00\x00\x00 \x80 \x80 21 | NULL NULL NULL 22 | 23 | query III 24 | SELECT typeof(COLUMNS(*)) FROM blob_tbl LIMIT 1 25 | ---- 26 | BLOB BLOB BLOB 27 | -------------------------------------------------------------------------------- /test/sql/scan_bool.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_bool.test 2 | # description: Test scanning tables with booleans 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query I 16 | SELECT * FROM booleans 17 | ---- 18 | 0 19 | 1 20 | NULL 21 | -------------------------------------------------------------------------------- /test/sql/scan_datetime.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_datetime.test 2 | # description: Test scanning tables with datetime 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | require icu 10 | 11 | statement ok 12 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 13 | 14 | statement ok 15 | USE msql 16 | 17 | statement ok 18 | SET TimeZone='UTC' 19 | 20 | query IIIII 21 | SELECT * FROM datetime_tbl 22 | ---- 23 | 2020-02-03 2029-02-14 08:47:23 2029-02-14 14:47:23+00 23:59:59 1901 24 | 1000-01-01 1000-01-01 00:00:00 1970-01-01 06:00:01+00 -838:59:59 2155 25 | 9999-12-31 9999-12-31 23:59:59 2038-01-19 04:14:07+00 838:59:59 2000 26 | NULL NULL NULL NULL NULL 27 | 28 | query IIIII 29 | SELECT typeof(COLUMNS(*)) FROM datetime_tbl LIMIT 1 30 | ---- 31 | DATE TIMESTAMP TIMESTAMP WITH TIME ZONE VARCHAR INTEGER 32 | -------------------------------------------------------------------------------- /test/sql/scan_decimal.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_decimal.test 2 | # description: Test scanning tables with decimals 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query IIIII 16 | SELECT * FROM decimals 17 | ---- 18 | 0.5 1234.1 12345678.12 12345678901234567.123 1.2345678901234568e+35 19 | -0.5 -1234.1 -12345678.12 -12345678901234567.123 -1.2345678901234568e+35 20 | NULL NULL NULL NULL NULL 21 | 22 | query IIIII 23 | SELECT typeof(COLUMNS(*)) FROM decimals LIMIT 1 24 | ---- 25 | DECIMAL(2,1) DECIMAL(5,1) DECIMAL(10,2) DECIMAL(20,3) DOUBLE 26 | -------------------------------------------------------------------------------- /test/sql/scan_enum.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_enum.test 2 | # description: Test scanning tables with enums 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query I 16 | SELECT * FROM enum_tbl 17 | ---- 18 | x-small 19 | small 20 | medium 21 | large 22 | x-large 23 | NULL 24 | 25 | query I 26 | SELECT typeof(COLUMNS(*)) FROM enum_tbl LIMIT 1 27 | ---- 28 | VARCHAR 29 | 30 | # set is essentially a list of enum 31 | query I 32 | SELECT * FROM set_tbl 33 | ---- 34 | a,d 35 | a,d 36 | a,d 37 | a,d 38 | a,d 39 | 40 | query I 41 | SELECT typeof(COLUMNS(*)) FROM set_tbl LIMIT 1 42 | ---- 43 | VARCHAR 44 | -------------------------------------------------------------------------------- /test/sql/scan_numeric_types.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_numeric_types.test 2 | # description: Test scanning tables with existing numeric types 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query IIIII 16 | SELECT * FROM signed_integers 17 | ---- 18 | -128 -32768 -8388608 -2147483648 -9223372036854775808 19 | 127 32767 8388607 2147483647 9223372036854775807 20 | NULL NULL NULL NULL NULL 21 | 22 | query IIIII 23 | SELECT typeof(COLUMNS(*)) FROM signed_integers LIMIT 1 24 | ---- 25 | TINYINT SMALLINT INTEGER INTEGER BIGINT 26 | 27 | query IIIII 28 | SELECT * FROM unsigned_integers 29 | ---- 30 | 0 0 0 0 0 31 | 255 65535 16777215 4294967295 18446744073709551615 32 | NULL NULL NULL NULL NULL 33 | 34 | query IIIII 35 | SELECT typeof(COLUMNS(*)) FROM unsigned_integers LIMIT 1 36 | ---- 37 | UTINYINT USMALLINT UINTEGER UINTEGER UBIGINT 38 | 39 | query IIII 40 | SELECT * FROM floating_points 41 | ---- 42 | 0.0 0.0 0 0 43 | 0.5 0.5 0.5 0.5 44 | -0.5 -0.5 0 0 45 | NULL NULL NULL NULL 46 | 47 | query IIII 48 | SELECT typeof(COLUMNS(*)) FROM floating_points LIMIT 1 49 | ---- 50 | FLOAT DOUBLE FLOAT DOUBLE 51 | 52 | query I 53 | SELECT * FROM zero_fill_integers 54 | ---- 55 | 0 56 | 2147483647 57 | NULL 58 | 59 | query I 60 | SELECT typeof(COLUMNS(*)) FROM zero_fill_integers LIMIT 1 61 | ---- 62 | UINTEGER 63 | -------------------------------------------------------------------------------- /test/sql/scan_text.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/scan_text.test 2 | # description: Test scanning tables with text 3 | # group: [sql] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | statement ok 10 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS msql (TYPE MYSQL_SCANNER) 11 | 12 | statement ok 13 | USE msql 14 | 15 | query III 16 | SELECT * FROM text_tbl 17 | ---- 18 | ab ab thisisalongstring 19 | (empty) (empty) (empty) 20 | 🦆 🦆 🦆🦆🦆🦆 21 | NULL NULL NULL 22 | 23 | query I 24 | SELECT * FROM json_tbl 25 | ---- 26 | {"k1": "value", "k2": 10} 27 | ["abc", 10, null, true, false] 28 | NULL 29 | -------------------------------------------------------------------------------- /test/sql/test_case_insensitivity.test: -------------------------------------------------------------------------------- 1 | # name: test/sql/test_case_insensitivity.test 2 | # description: Test for case insensitivity 3 | # group: [storage] 4 | 5 | require mysql_scanner 6 | 7 | require-env MYSQL_TEST_DATABASE_AVAILABLE 8 | 9 | require-env POSTGRES_TEST_DATABASE_AVAILABLE 10 | 11 | statement ok 12 | ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS s (TYPE MYSQL_SCANNER) 13 | 14 | # MySQL is fully case insensitive (like DuckDB) 15 | statement ok 16 | CREATE OR REPLACE TABLE s.SaMeCaSeNaMe AS SELECT 42 i 17 | 18 | query I 19 | SELECT * FROM s.SameCaseName 20 | ---- 21 | 42 22 | 23 | query I 24 | SELECT * FROM s.SAMECASENAME 25 | ---- 26 | 42 27 | 28 | statement error 29 | CREATE TABLE s.samecasename(i int) 30 | ---- 31 | already exists 32 | 33 | statement error 34 | CREATE TABLE s.same_col_name(i int, I INT) 35 | ---- 36 | Column with name I already exists 37 | -------------------------------------------------------------------------------- /test/test_data.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS mysqlscanner; 2 | CREATE SCHEMA mysqlscanner; 3 | USE mysqlscanner; 4 | 5 | CREATE TABLE booleans(b BOOLEAN); 6 | INSERT INTO booleans VALUES (false), (true), (NULL); 7 | 8 | CREATE TABLE signed_integers(t TINYINT, s SMALLINT, m MEDIUMINT, i INTEGER, b BIGINT); 9 | INSERT INTO signed_integers VALUES ( 10 | -128, 11 | -32768, 12 | -8388608, 13 | -2147483648, 14 | -9223372036854775808 15 | ); 16 | INSERT INTO signed_integers VALUES ( 17 | 127, 18 | 32767, 19 | 8388607, 20 | 2147483647, 21 | 9223372036854775807 22 | ); 23 | INSERT INTO signed_integers VALUES ( 24 | NULL, NULL, NULL, NULL, NULL 25 | ); 26 | 27 | CREATE TABLE unsigned_integers(t TINYINT UNSIGNED, s SMALLINT UNSIGNED, m MEDIUMINT UNSIGNED, i INT UNSIGNED, b BIGINT UNSIGNED); 28 | INSERT INTO unsigned_integers VALUES ( 29 | 0, 0, 0, 0, 0 30 | ); 31 | INSERT INTO unsigned_integers VALUES ( 32 | 255, 33 | 65535, 34 | 16777215, 35 | 4294967295, 36 | 18446744073709551615 37 | ); 38 | INSERT INTO unsigned_integers VALUES ( 39 | NULL, NULL, NULL, NULL, NULL 40 | ); 41 | 42 | CREATE TABLE floating_points(f FLOAT, d DOUBLE, fu FLOAT UNSIGNED, du DOUBLE UNSIGNED); 43 | INSERT INTO floating_points VALUES ( 44 | 0, 0, 0, 0 45 | ); 46 | INSERT INTO floating_points VALUES ( 47 | 0.5, 0.5, 0.5, 0.5 48 | ); 49 | INSERT INTO floating_points VALUES ( 50 | -0.5, -0.5, 0, 0 51 | ); 52 | INSERT INTO floating_points VALUES ( 53 | NULL, NULL, NULL, NULL 54 | ); 55 | 56 | CREATE TABLE zero_fill_integers(t INT(4) ZEROFILL); 57 | INSERT INTO zero_fill_integers VALUES (0), (2147483647); 58 | INSERT INTO zero_fill_integers VALUES (NULL); 59 | 60 | CREATE TABLE decimals(xs DECIMAL(2, 1), s DECIMAL(5, 1), m DECIMAL(10, 2), l DECIMAL(20, 3), xl DECIMAL(40, 4)); 61 | INSERT INTO decimals VALUES ( 62 | 0.5, 63 | 1234.1, 64 | 12345678.12, 65 | 12345678901234567.123, 66 | 123456789012345678901234567890123456.1234 67 | ); 68 | INSERT INTO decimals VALUES ( 69 | -0.5, 70 | -1234.1, 71 | -12345678.12, 72 | -12345678901234567.123, 73 | -123456789012345678901234567890123456.1234 74 | ); 75 | INSERT INTO decimals VALUES ( 76 | NULL, 77 | NULL, 78 | NULL, 79 | NULL, 80 | NULL 81 | ); 82 | 83 | -- MySQL doesn't support a "proper" boolean so people often use TINYINT(1) or BIT(1) as boolean 84 | CREATE TABLE fake_booleans(tinyint_bool TINYINT(1), bit_bool BIT(1)); 85 | INSERT INTO fake_booleans VALUES (true, true), (false, false), (NULL, NULL), (-128, b'1'), (127, b'0'); 86 | 87 | CREATE TABLE bits(b BIT(6), bl BIT(64)); 88 | INSERT INTO bits VALUES (b'000101', b'010101010101010101'); 89 | INSERT INTO bits VALUES (NULL, NULL); 90 | 91 | SET SESSION time_zone = '-05:00'; 92 | CREATE TABLE datetime_tbl(d DATE, date_time DATETIME, ts TIMESTAMP, t TIME, y YEAR); 93 | INSERT INTO datetime_tbl VALUES ('2020-02-03', '2029-02-14 08:47:23', '2029-02-14 08:47:23', '23:59:59', '1901'); 94 | INSERT INTO datetime_tbl VALUES ('1000-01-01', '1000-01-01 00:00:00.000000', '1970-01-01 00:00:01.000000', '-838:59:59', '2155'); 95 | INSERT INTO datetime_tbl VALUES ('9999-12-31', '9999-12-31 23:59:59.499999', '2038-01-18 22:14:07.499999', '838:59:59', '2000'); 96 | INSERT INTO datetime_tbl VALUES (NULL, NULL, NULL, NULL, NULL); 97 | 98 | CREATE TABLE text_tbl(v VARCHAR(4), c CHAR(4), t TEXT); 99 | INSERT INTO text_tbl VALUES ('ab ', 'ab ', 'thisisalongstring'); 100 | INSERT INTO text_tbl VALUES ('', '', ''); 101 | INSERT INTO text_tbl VALUES ('🦆', '🦆', '🦆🦆🦆🦆'); 102 | INSERT INTO text_tbl VALUES (NULL, NULL, NULL); 103 | 104 | CREATE TABLE blob_tbl(bi BINARY(4), vbi VARBINARY(4), bl BLOB); 105 | INSERT INTO blob_tbl VALUES ('c\0\0', 'c\0\0', 'c\0\0'); 106 | INSERT INTO blob_tbl VALUES ('', '', ''); 107 | INSERT INTO blob_tbl VALUES (0x80, 0x80, 0x80); 108 | INSERT INTO blob_tbl VALUES (NULL, NULL, NULL); 109 | 110 | CREATE TABLE enum_tbl ( 111 | size ENUM('x-small', 'small', 'medium', 'large', 'x-large') 112 | ); 113 | INSERT INTO enum_tbl VALUES ('x-small'), ('small'), ('medium'), ('large'), ('x-large'), (NULL); 114 | 115 | CREATE TABLE set_tbl (col SET('a', 'b', 'c', 'd')); 116 | INSERT INTO set_tbl (col) VALUES ('a,d'), ('d,a'), ('a,d,a'), ('a,d,d'), ('d,a,d'); 117 | 118 | CREATE TABLE json_tbl (col JSON); 119 | INSERT INTO json_tbl (col) VALUES ('{"k1": "value", "k2": 10}'), ('["abc", 10, null, true, false]'), (NULL); 120 | 121 | CREATE TABLE `latin_unicode` ( 122 | `id_user` int NOT NULL AUTO_INCREMENT, 123 | `name` varchar(45) NOT NULL, 124 | PRIMARY KEY (`id_user`) 125 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 126 | INSERT INTO latin_unicode (name) VALUES ('Graça'); 127 | 128 | SET sql_mode = ''; 129 | CREATE TABLE zero_date(d DATE, ts TIMESTAMP); 130 | INSERT INTO zero_date VALUES ('0000-00-00', '0000-00-00 00:00:00'); 131 | 132 | CREATE TABLE geom ( 133 | g GEOMETRY, 134 | p POINT, 135 | ls LINESTRING, 136 | poly POLYGON, 137 | mp MULTIPOINT, 138 | mls MULTILINESTRING, 139 | mpoly MULTIPOLYGON, 140 | gc GEOMETRYCOLLECTION); 141 | INSERT INTO geom 142 | VALUES ( 143 | ST_GeomFromText('POINT(1 1)'), 144 | ST_GeomFromText('POINT(1 1)'), 145 | ST_GeomFromText('LINESTRING(2 1, 6 6)'), 146 | ST_GeomFromText('POLYGON((0 5, 2 5, 2 7, 0 7, 0 5))'), 147 | ST_GeomFromText('MULTIPOINT((1 1))'), 148 | ST_GeomFromText('MULTILINESTRING((2 1, 6 6))'), 149 | ST_GeomFromText('MULTIPOLYGON(((0 5, 2 5, 2 7, 0 7, 0 5)))'), 150 | ST_GeomFromText('GEOMETRYCOLLECTION EMPTY') 151 | ); 152 | 153 | CREATE TABLE tbl_issue65 (col1 int, col2 decimal(5,2)); 154 | insert into tbl_issue65 values (1,1.11), (2,2.22), (3,3.33); 155 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "libmysql" 4 | ], 5 | "vcpkg-configuration": { 6 | "overlay-ports": [ 7 | "./vcpkg_ports" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/Add-target-include-directories.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmake/libutils.cmake b/cmake/libutils.cmake 2 | index ae13a63dfdc..b450b3c2e4c 100644 3 | --- a/cmake/libutils.cmake 4 | +++ b/cmake/libutils.cmake 5 | @@ -222,6 +222,7 @@ MACRO(MERGE_LIBRARIES_SHARED TARGET_ARG) 6 | ADD_VERSION_INFO(${TARGET} SHARED SRC) 7 | ENDIF() 8 | ADD_LIBRARY(${TARGET} SHARED ${SRC}) 9 | + TARGET_INCLUDE_DIRECTORIES(${TARGET} INTERFACE $) 10 | 11 | IF(ARG_EXCLUDE_FROM_ALL) 12 | IF(NOT ARG_SKIP_INSTALL) 13 | @@ -361,6 +362,7 @@ MACRO(MERGE_CONVENIENCE_LIBRARIES TARGET_ARG) 14 | CONFIGURE_FILE_CONTENT("${SOURCE_FILE_CONTENT}" "${SOURCE_FILE}") 15 | 16 | ADD_LIBRARY(${TARGET} STATIC ${SOURCE_FILE}) 17 | + TARGET_INCLUDE_DIRECTORIES(${TARGET} INTERFACE $) 18 | MY_CHECK_CXX_COMPILER_WARNING("-Wmissing-profile" HAS_MISSING_PROFILE) 19 | IF(FPROFILE_USE AND HAS_MISSING_PROFILE) 20 | ADD_COMPILE_FLAGS(${SOURCE_FILE} COMPILE_FLAGS ${HAS_MISSING_PROFILE}) 21 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/export-cmake-targets.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmake/install_macros.cmake b/cmake/install_macros.cmake 2 | index 870c9f13732..98bc3ea15e4 100644 3 | --- a/cmake/install_macros.cmake 4 | +++ b/cmake/install_macros.cmake 5 | @@ -97,7 +97,7 @@ ENDFUNCTION() 6 | FUNCTION(MYSQL_INSTALL_TARGET target_arg) 7 | CMAKE_PARSE_ARGUMENTS(ARG 8 | "NAMELINK_SKIP" 9 | - "DESTINATION;COMPONENT" 10 | + "DESTINATION;COMPONENT;EXPORT" 11 | "" 12 | ${ARGN} 13 | ) 14 | @@ -113,10 +113,21 @@ FUNCTION(MYSQL_INSTALL_TARGET target_arg) 15 | IF(ARG_NAMELINK_SKIP) 16 | SET(LIBRARY_INSTALL_ARGS NAMELINK_SKIP) 17 | ENDIF() 18 | - INSTALL(TARGETS ${target} 19 | - RUNTIME DESTINATION ${ARG_DESTINATION} ${COMP} 20 | - ARCHIVE DESTINATION ${ARG_DESTINATION} ${COMP} 21 | - LIBRARY DESTINATION ${ARG_DESTINATION} ${COMP} ${LIBRARY_INSTALL_ARGS}) 22 | + IF (ARG_EXPORT) 23 | + FILE(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_EXPORT}-config.cmake" 24 | +"include(CMakeFindDependencyMacro) 25 | +find_dependency(ZLIB) 26 | +find_dependency(OpenSSL) 27 | +find_dependency(Threads) 28 | +include(\"\${CMAKE_CURRENT_LIST_DIR}/${ARG_EXPORT}-targets.cmake\") 29 | +") 30 | + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${ARG_EXPORT}-config.cmake DESTINATION share/${ARG_EXPORT}) 31 | + set(EXPORT_ARGS EXPORT ${ARG_EXPORT}-targets) 32 | + ENDIF() 33 | + INSTALL(TARGETS ${target} ${EXPORT_ARGS} RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib ${COMP}) 34 | + IF (ARG_EXPORT) 35 | + INSTALL(${EXPORT_ARGS} DESTINATION share/${ARG_EXPORT}) 36 | + ENDIF() 37 | SET(INSTALL_LOCATION ${ARG_DESTINATION} ) 38 | INSTALL_DEBUG_SYMBOLS(${target}) 39 | SET(INSTALL_LOCATION) 40 | diff --git a/cmake/libutils.cmake b/cmake/libutils.cmake 41 | index 23e60ad..cc969a8 100644 42 | --- a/cmake/libutils.cmake 43 | +++ b/cmake/libutils.cmake 44 | @@ -298,8 +298,9 @@ MACRO(MERGE_LIBRARIES_SHARED TARGET_ARG) 45 | IF(ARG_NAMELINK_SKIP) 46 | SET(INSTALL_ARGS NAMELINK_SKIP) 47 | ENDIF() 48 | - MYSQL_INSTALL_TARGET(${TARGET} DESTINATION "${DESTINATION}" ${COMP} 49 | - ${INSTALL_ARGS}) 50 | + IF(NOT INSTALL_STATIC_LIBRARIES) 51 | + MYSQL_INSTALL_TARGET(${TARGET} EXPORT unofficial-libmysql DESTINATION "${INSTALL_LIBDIR}" ${COMP}) 52 | + ENDIF() 53 | ENDIF() 54 | 55 | IF(WIN32) 56 | @@ -464,7 +465,7 @@ MACRO(MERGE_CONVENIENCE_LIBRARIES TARGET_ARG) 57 | SET(COMP COMPONENT ${ARG_COMPONENT}) 58 | ENDIF() 59 | IF(INSTALL_STATIC_LIBRARIES) 60 | - MYSQL_INSTALL_TARGET(${TARGET} DESTINATION "${INSTALL_LIBDIR}" ${COMP}) 61 | + MYSQL_INSTALL_TARGET(${TARGET} EXPORT unofficial-libmysql DESTINATION "${INSTALL_LIBDIR}" ${COMP}) 62 | ENDIF() 63 | ENDIF() 64 | ENDMACRO(MERGE_CONVENIENCE_LIBRARIES) 65 | diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt 66 | index 2ee26bfa284..46583f4f0a3 100644 67 | --- a/libmysql/CMakeLists.txt 68 | +++ b/libmysql/CMakeLists.txt 69 | @@ -277,6 +277,14 @@ ENDIF() 70 | # LDAP authentication SASL client plug-in 71 | ADD_SUBDIRECTORY(authentication_ldap) 72 | 73 | +IF (BUILD_SHARED_LIBS) 74 | + set(INSTALL_SHARED ) 75 | + set(INSTALL_STATIC SKIP_INSTALL) 76 | +ELSE() 77 | + set(INSTALL_SHARED SKIP_INSTALL) 78 | + set(INSTALL_STATIC ) 79 | +ENDIF() 80 | + 81 | # FIDO authentication client plugin 82 | ADD_SUBDIRECTORY(authentication_fido) 83 | 84 | @@ -287,7 +295,7 @@ ADD_SUBDIRECTORY(authentication_kerberos) 85 | ADD_SUBDIRECTORY(authentication_oci_client) 86 | 87 | # Merge several convenience libraries into one big mysqlclient 88 | -MERGE_CONVENIENCE_LIBRARIES(mysqlclient ${LIBS_TO_MERGE} 89 | +MERGE_CONVENIENCE_LIBRARIES(mysqlclient ${LIBS_TO_MERGE} ${INSTALL_STATIC} 90 | COMPONENT Development 91 | LINK_LIBRARIES ${LIBS_TO_LINK} 92 | ) 93 | @@ -403,6 +403,7 @@ CONFIGURE_FILE(api_test.c.in ${CMAKE_CURRENT_BINARY_DIR}/api_test.c) 94 | # from @CLIENT_API_FUNCTIONS@ are declared by . It will fail 95 | # to run if not all of these symbols are exported by the library. 96 | # 97 | +IF (ENABLE_TESTING) 98 | MYSQL_ADD_EXECUTABLE(libmysql_api_test 99 | ${CMAKE_CURRENT_BINARY_DIR}/api_test.c 100 | LINK_LIBRARIES libmysql ${LIBRT} 101 | @@ -410,6 +411,7 @@ MYSQL_ADD_EXECUTABLE(libmysql_api_test 102 | ) 103 | # Clang/UBSAN needs this on some platforms. 104 | SET_TARGET_PROPERTIES(libmysql_api_test PROPERTIES LINKER_LANGUAGE CXX) 105 | +ENDIF() 106 | 107 | IF(MY_COMPILER_IS_GNU) 108 | ADD_COMPILE_FLAGS( 109 | @@ -426,9 +428,11 @@ IF(HAS_WARN_FLAG) 110 | ) 111 | ENDIF() 112 | 113 | +IF (ENABLE_TESTING) 114 | # Verify that libmysql_api_test runs OK 115 | MY_ADD_CUSTOM_TARGET(run_libmysql_api_test ALL 116 | DEPENDS libmysql_api_test 117 | COMMAND libmysql_api_test 118 | > ${CMAKE_CURRENT_BINARY_DIR}/libmysql_api_test.out 119 | ) 120 | +ENDIF() 121 | \ No newline at end of file 122 | diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt 123 | index a5fa18e..0f2e15c 100644 124 | --- a/scripts/CMakeLists.txt 125 | +++ b/scripts/CMakeLists.txt 126 | @@ -329,13 +329,13 @@ MACRO(EXTRACT_LINK_LIBRARIES target var) 127 | STRING(REGEX REPLACE "^[ ]+" "" ${var} "${${var}}") 128 | STRING(REGEX REPLACE "[ ]+$" "" ${var} "${${var}}") 129 | ENDMACRO() 130 | - 131 | +IF (NOT BUILD_SHARED_LIBS) 132 | EXTRACT_LINK_LIBRARIES(mysqlclient CLIENT_LIBS) 133 | - 134 | -IF(MSVC) 135 | +ENDIF() 136 | +IF(MSVC AND NOT BUILD_SHARED_LIBS) 137 | GET_TARGET_PROPERTY(LIBMYSQL_OS_SHLIB_VERSION mysqlclient VERSION) 138 | GET_TARGET_PROPERTY(LIBMYSQL_OS_OUTPUT_NAME mysqlclient OUTPUT_NAME) 139 | -ELSE() 140 | +ELSEIF(BUILD_SHARED_LIBS) 141 | GET_TARGET_PROPERTY(LIBMYSQL_OS_SHLIB_VERSION libmysql VERSION) 142 | GET_TARGET_PROPERTY(LIBMYSQL_OS_OUTPUT_NAME libmysql OUTPUT_NAME) 143 | ENDIF() 144 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/fix_dup_symbols.patch: -------------------------------------------------------------------------------- 1 | diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt 2 | index 058967b..bcd8841 100644 3 | --- a/client/CMakeLists.txt 4 | +++ b/client/CMakeLists.txt 5 | @@ -43,7 +43,6 @@ MYSQL_ADD_EXECUTABLE(mysql 6 | pattern_matcher.cc 7 | readline.cc 8 | client_query_attributes.cc 9 | - multi_factor_passwordopt-vars.cc 10 | ${CMAKE_CURRENT_SOURCE_DIR}/common/user_registration.cc 11 | LINK_LIBRARIES mysqlclient client_base ${EDITLINE_LIBRARY} 12 | ) 13 | @@ -226,7 +226,6 @@ SET(MYSQLBINLOG_SOURCES 14 | ${CMAKE_SOURCE_DIR}/sql/binlog_reader.cc 15 | ${CMAKE_SOURCE_DIR}/sql/stream_cipher.cc 16 | ${CMAKE_SOURCE_DIR}/sql/rpl_log_encryption.cc 17 | - ${CMAKE_SOURCE_DIR}/libbinlogevents/src/trx_boundary_parser.cpp 18 | ) 19 | 20 | SET(MYSQLBINLOG_LIBRARIES 21 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/homebrew.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CMakeLists.txt b/CMakeLists.txt 2 | index 52d12a15190..6033494e4f0 100644 3 | --- a/CMakeLists.txt 4 | +++ b/CMakeLists.txt 5 | @@ -1929,7 +1929,7 @@ IF(APPLE) 6 | GET_FILENAME_COMPONENT(HOMEBREW_BASE ${HOMEBREW_HOME} DIRECTORY) 7 | IF(EXISTS ${HOMEBREW_BASE}/include/boost) 8 | FOREACH(SYSTEM_LIB ICU LIBEVENT LZ4 PROTOBUF ZSTD FIDO) 9 | - IF(WITH_${SYSTEM_LIB} STREQUAL "system") 10 | + IF(FALSE) 11 | MESSAGE(FATAL_ERROR 12 | "WITH_${SYSTEM_LIB}=system is not compatible with Homebrew boost\n" 13 | "MySQL depends on ${BOOST_PACKAGE_NAME} with a set of patches.\n" 14 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/ignore-boost-version.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmake/boost.cmake b/cmake/boost.cmake 2 | index e879484a9d9..437b77ff49a 100644 3 | --- a/cmake/boost.cmake 4 | +++ b/cmake/boost.cmake 5 | @@ -301,7 +301,7 @@ IF(NOT BOOST_MAJOR_VERSION EQUAL 10) 6 | COULD_NOT_FIND_BOOST() 7 | ENDIF() 8 | 9 | -IF(NOT BOOST_MINOR_VERSION EQUAL 77) 10 | +IF(NOT BOOST_MINOR_VERSION EQUAL 77 AND NOT IGNORE_BOOST_VERSION) 11 | MESSAGE(WARNING "Boost minor version found is ${BOOST_MINOR_VERSION} " 12 | "we need 77" 13 | ) 14 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/portfile.cmake: -------------------------------------------------------------------------------- 1 | if (EXISTS "${CURRENT_INSTALLED_DIR}/include/mysql/mysql.h") 2 | message(FATAL_ERROR "FATAL ERROR: ${PORT} and libmariadb are incompatible.") 3 | endif() 4 | 5 | set(PATCH_FILES 6 | ignore-boost-version.patch 7 | system-libs.patch 8 | export-cmake-targets.patch 9 | Add-target-include-directories.patch 10 | homebrew.patch 11 | fix_dup_symbols.patch 12 | scanner_changes.patch 13 | ) 14 | 15 | if(NOT VCPKG_TARGET_IS_WINDOWS) 16 | # these changes are only needed for Linux/MacOS 17 | set(PATCH_FILES 18 | ${PATCH_FILES} 19 | posix_changes.patch 20 | ) 21 | endif() 22 | 23 | set(CROSS_COMPILING "") 24 | set(STACK_DIRECTION "") 25 | if(NOT VCPKG_TARGET_IS_WINDOWS) 26 | set(STACK_DIRECTION -DSTACK_DIRECTION=-1) 27 | set(HAVE_IB_GCC_ATOMIC_BUILTINS 0) 28 | set(CROSS_COMPILING -DCMAKE_CROSSCOMPILING=1) 29 | # Non-Windows builds are always cross-compiled 30 | # as such we build the executables (comp_sql, uca9dump, comp_client_err) separately 31 | set(PATCH_FILES 32 | ${PATCH_FILES} 33 | remove_executables.patch 34 | ) 35 | endif() 36 | 37 | vcpkg_from_github( 38 | OUT_SOURCE_PATH SOURCE_PATH 39 | REPO mysql/mysql-server 40 | REF mysql-${VERSION} 41 | SHA512 8b9f15b301b158e6ffc99dd916b9062968d36f6bdd7b898636fa61badfbe68f7328d4a39fa3b8b3ebef180d3aec1aee353bd2dac9ef1594e5772291390e17ac0 42 | HEAD_REF master 43 | PATCHES 44 | ${PATCH_FILES} 45 | ) 46 | 47 | file(REMOVE_RECURSE "${SOURCE_PATH}/include/boost_1_70_0") 48 | 49 | #Skip the version check for Visual Studio 50 | set(FORCE_UNSUPPORTED_COMPILER "") 51 | if(VCPKG_TARGET_IS_WINDOWS) 52 | set(FORCE_UNSUPPORTED_COMPILER 1) 53 | endif() 54 | 55 | string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" BUILD_STATIC_LIBS) 56 | string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" STATIC_CRT_LINKAGE) 57 | 58 | vcpkg_cmake_configure( 59 | SOURCE_PATH "${SOURCE_PATH}" 60 | OPTIONS 61 | -DWITHOUT_SERVER=ON 62 | -DWITH_BUILD_ID=OFF 63 | -DWITH_UNIT_TESTS=OFF 64 | -DENABLED_PROFILING=OFF 65 | -DENABLE_TESTING=OFF 66 | -DWIX_DIR=OFF 67 | ${STACK_DIRECTION} 68 | -DIGNORE_BOOST_VERSION=ON 69 | -DDOWNLOAD_BOOST=OFF 70 | -DWITH_SYSTEMD=OFF 71 | -DWITH_TEST_TRACE_PLUGIN=OFF 72 | -DMYSQL_MAINTAINER_MODE=OFF 73 | -DBUNDLE_RUNTIME_LIBRARIES=OFF 74 | -DWITH_SSL=system 75 | -DWITH_ICU=system 76 | -DWITH_LZ4=system 77 | -DWITH_ZLIB=system 78 | -DWITH_LTO=OFF 79 | -DHANDLE_FATAL_SIGNALS=OFF 80 | -DWITH_CLIENT_PROTOCOL_TRACING=OFF 81 | -DWITH_HYPERGRAPH_OPTIMIZER=OFF 82 | -DFORCE_UNSUPPORTED_COMPILER=ON 83 | -DINSTALL_STATIC_LIBRARIES=${BUILD_STATIC_LIBS} 84 | -DLINK_STATIC_RUNTIME_LIBRARIES=${STATIC_CRT_LINKAGE} 85 | ${CROSS_COMPILING} 86 | MAYBE_UNUSED_VARIABLES 87 | BUNDLE_RUNTIME_LIBRARIES # only on windows 88 | LINK_STATIC_RUNTIME_LIBRARIES # only on windows 89 | WIX_DIR # only on windows 90 | WITH_BUILD_ID # only on windows 91 | IGNORE_BOOST_VERSION # only on windows 92 | DOWNLOAD_BOOST # only on windows 93 | ) 94 | 95 | vcpkg_cmake_install(ADD_BIN_TO_PATH) 96 | 97 | file(RENAME "${CURRENT_PACKAGES_DIR}/share" "${CURRENT_PACKAGES_DIR}/${PORT}") 98 | file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/share") 99 | file(RENAME "${CURRENT_PACKAGES_DIR}/${PORT}" "${CURRENT_PACKAGES_DIR}/share/${PORT}") 100 | 101 | if(NOT VCPKG_BUILD_TYPE) 102 | file(RENAME "${CURRENT_PACKAGES_DIR}/debug/share" "${CURRENT_PACKAGES_DIR}/debug/${PORT}") 103 | file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/share") 104 | file(RENAME "${CURRENT_PACKAGES_DIR}/debug/${PORT}" "${CURRENT_PACKAGES_DIR}/debug/share/${PORT}") 105 | endif() 106 | 107 | vcpkg_cmake_config_fixup(PACKAGE_NAME unofficial-libmysql CONFIG_PATH share/${PORT}/unofficial-libmysql) 108 | 109 | # switch mysql into /mysql 110 | file(RENAME "${CURRENT_PACKAGES_DIR}/include" "${CURRENT_PACKAGES_DIR}/include2") 111 | file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/include") 112 | file(RENAME "${CURRENT_PACKAGES_DIR}/include2" "${CURRENT_PACKAGES_DIR}/include/mysql") 113 | 114 | ## delete useless vcruntime/scripts/bin/msg file 115 | file(REMOVE_RECURSE 116 | "${CURRENT_PACKAGES_DIR}/debug/include" 117 | "${CURRENT_PACKAGES_DIR}/debug/share" 118 | "${CURRENT_PACKAGES_DIR}/debug/man" 119 | "${CURRENT_PACKAGES_DIR}/docs" 120 | "${CURRENT_PACKAGES_DIR}/debug/docs" 121 | "${CURRENT_PACKAGES_DIR}/lib/debug" 122 | "${CURRENT_PACKAGES_DIR}/lib/plugin" 123 | "${CURRENT_PACKAGES_DIR}/debug/lib/plugin" 124 | ) 125 | 126 | # delete dynamic dll on static build 127 | if (BUILD_STATIC_LIBS) 128 | # libmysql.dll 129 | file(REMOVE_RECURSE 130 | "${CURRENT_PACKAGES_DIR}/bin" 131 | "${CURRENT_PACKAGES_DIR}/debug/bin" 132 | ) 133 | file(REMOVE 134 | "${CURRENT_PACKAGES_DIR}/lib/libmysql.lib" 135 | "${CURRENT_PACKAGES_DIR}/debug/lib/libmysql.lib" 136 | "${CURRENT_PACKAGES_DIR}/lib/libmysql.pdb" 137 | "${CURRENT_PACKAGES_DIR}/debug/lib/libmysql.pdb" 138 | ) 139 | endif() 140 | 141 | ## remove misc files 142 | file(REMOVE 143 | "${CURRENT_PACKAGES_DIR}/LICENSE" 144 | "${CURRENT_PACKAGES_DIR}/README" 145 | "${CURRENT_PACKAGES_DIR}/debug/LICENSE" 146 | "${CURRENT_PACKAGES_DIR}/debug/README" 147 | ) 148 | 149 | vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/mysql/mysql_com.h" "#include " "#include \"mysql/udf_registration_types.h\"") 150 | 151 | file(INSTALL "${CURRENT_PORT_DIR}/vcpkg-cmake-wrapper.cmake" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") 152 | file(INSTALL "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") 153 | 154 | # copy license 155 | vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") 156 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/posix_changes.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmake/build_configurations/compiler_options.cmake b/cmake/build_configurations/compiler_options.cmake 2 | index 5dd2499c..f60d2b67 100644 3 | --- a/cmake/build_configurations/compiler_options.cmake 4 | +++ b/cmake/build_configurations/compiler_options.cmake 5 | @@ -64,12 +64,6 @@ IF(UNIX) 6 | SET(COMMON_CXX_FLAGS "-std=c++17 -fno-omit-frame-pointer") 7 | ENDIF() 8 | 9 | - # Faster TLS model 10 | - IF(MY_COMPILER_IS_GNU_OR_CLANG AND NOT SOLARIS AND NOT LINUX_RHEL6) 11 | - STRING_APPEND(COMMON_C_FLAGS " -ftls-model=initial-exec") 12 | - STRING_APPEND(COMMON_CXX_FLAGS " -ftls-model=initial-exec") 13 | - ENDIF() 14 | - 15 | # Use STRING_PREPEND here, so command-line input can override our defaults. 16 | STRING_PREPEND(CMAKE_C_FLAGS "${COMMON_C_FLAGS} ") 17 | STRING_PREPEND(CMAKE_C_FLAGS_RELWITHDEBINFO "${SECTIONS_FLAG} ") 18 | diff --git a/configure.cmake b/configure.cmake 19 | index 96664b16..99e26446 100644 20 | --- a/configure.cmake 21 | +++ b/configure.cmake 22 | @@ -403,21 +403,8 @@ MY_CHECK_CXX_COMPILER_FLAG("-fvisibility=hidden" HAVE_VISIBILITY_HIDDEN) 23 | # Code tests 24 | # 25 | 26 | -CHECK_C_SOURCE_RUNS(" 27 | -#include 28 | -int main() 29 | -{ 30 | - struct timespec ts; 31 | - return clock_gettime(CLOCK_MONOTONIC, &ts); 32 | -}" HAVE_CLOCK_GETTIME) 33 | - 34 | -CHECK_C_SOURCE_RUNS(" 35 | -#include 36 | -int main() 37 | -{ 38 | - struct timespec ts; 39 | - return clock_gettime(CLOCK_REALTIME, &ts); 40 | -}" HAVE_CLOCK_REALTIME) 41 | +set(HAVE_CLOCK_GETTIME 1) 42 | +set(HAVE_CLOCK_REALTIME 1) 43 | 44 | IF(NOT STACK_DIRECTION) 45 | IF(CMAKE_CROSSCOMPILING) 46 | diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt 47 | index bc900c58..475559a2 100644 48 | --- a/libmysql/CMakeLists.txt 49 | +++ b/libmysql/CMakeLists.txt 50 | @@ -255,10 +255,10 @@ ELSE() 51 | FIND_LIBRARY(RESOLV_LIBRARY NAMES resolv) 52 | IF (RESOLV_LIBRARY) 53 | LIST(APPEND LIBS_TO_LINK ${RESOLV_LIBRARY}) 54 | - SET(HAVE_UNIX_DNS_SRV 1 PARENT_SCOPE) 55 | - SET(HAVE_DNS_SRV 1) 56 | - MESSAGE(STATUS "Found Unix DNS SRV APIs") 57 | ENDIF() 58 | + SET(HAVE_UNIX_DNS_SRV 1 PARENT_SCOPE) 59 | + SET(HAVE_DNS_SRV 1) 60 | + MESSAGE(STATUS "Found Unix DNS SRV APIs") 61 | ENDIF() 62 | 63 | IF(HAVE_DNS_SRV EQUAL 0) 64 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/system-libs.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cmake/icu.cmake b/cmake/icu.cmake 2 | index 3ac136dbfb4..c7bdbc8c47e 100644 3 | --- a/cmake/icu.cmake 4 | +++ b/cmake/icu.cmake 5 | @@ -68,6 +68,12 @@ ENDMACRO() 6 | # install_root is either 'system' or is assumed to be a path. 7 | # 8 | MACRO (FIND_ICU install_root) 9 | + find_package(ICU REQUIRED COMPONENTS uc io dt in) 10 | + set(ICU_SYSTEM_LIBRARIES ICU::uc ICU::io ICU::dt ICU::in) 11 | + set(ICU_COMMON_DIR ${ICU_INCLUDE_DIR}) 12 | + SET(ICU_INCLUDE_DIRS ${ICU_INCLUDE_DIR}) 13 | +ENDMACRO() 14 | +MACRO (FIND_ICU_OLD install_root) 15 | IF("${install_root}" STREQUAL "system") 16 | SET(EXTRA_FIND_LIB_ARGS) 17 | SET(EXTRA_FIND_INC_ARGS) 18 | diff --git a/cmake/libutils.cmake b/cmake/libutils.cmake 19 | index ae13a63dfdc..9604ad2fc2a 100644 20 | --- a/cmake/libutils.cmake 21 | +++ b/cmake/libutils.cmake 22 | @@ -441,19 +441,7 @@ MACRO(MERGE_CONVENIENCE_LIBRARIES TARGET_ARG) 23 | # On Windows, ssleay32.lib/libeay32.lib or libssl.lib/libcrypto.lib 24 | # must be merged into mysqlclient.lib 25 | IF(WIN32 AND ${TARGET} STREQUAL "mysqlclient") 26 | - SET(LINKER_EXTRA_FLAGS "") 27 | - FOREACH(LIB ${SSL_LIBRARIES}) 28 | - STRING_APPEND(LINKER_EXTRA_FLAGS " ${LIB}") 29 | - ENDFOREACH() 30 | - 31 | - # __NULL_IMPORT_DESCRIPTOR already defined, second definition ignored 32 | - # Same symbol from both libssl and libcrypto 33 | - # But: Lib.exe has no /IGNORE option, see 34 | - # https://docs.microsoft.com/en-us/cpp/build/reference/running-lib?view=msvc-160 35 | - # STRING_APPEND(LINKER_EXTRA_FLAGS " /IGNORE:LNK4006") 36 | - 37 | - SET_TARGET_PROPERTIES(${TARGET} 38 | - PROPERTIES STATIC_LIBRARY_FLAGS "${LINKER_EXTRA_FLAGS}") 39 | + TARGET_LINK_LIBRARIES(${TARGET} PRIVATE ${SSL_LIBRARIES}) 40 | ENDIF() 41 | 42 | IF(ARG_LINK_LIBRARIES) 43 | diff --git a/cmake/lz4.cmake b/cmake/lz4.cmake 44 | index e7047412f9b..ffbc95b578d 100644 45 | --- a/cmake/lz4.cmake 46 | +++ b/cmake/lz4.cmake 47 | @@ -46,7 +46,7 @@ MACRO (FIND_SYSTEM_LZ4) 48 | FIND_PATH(LZ4_INCLUDE_DIR 49 | NAMES lz4frame.h) 50 | FIND_LIBRARY(LZ4_SYSTEM_LIBRARY 51 | - NAMES lz4) 52 | + NAMES lz4d lz4) 53 | IF (LZ4_INCLUDE_DIR AND LZ4_SYSTEM_LIBRARY) 54 | SET(SYSTEM_LZ4_FOUND 1) 55 | SET(LZ4_LIBRARY ${LZ4_SYSTEM_LIBRARY}) 56 | diff --git a/cmake/ssl.cmake b/cmake/ssl.cmake 57 | index 52feade..1e71bd7 100644 58 | --- a/cmake/ssl.cmake 59 | +++ b/cmake/ssl.cmake 60 | @@ -93,7 +93,16 @@ ENDMACRO() 61 | # Provides the following configure options: 62 | # WITH_SSL=[yes|system|] 63 | MACRO (MYSQL_CHECK_SSL) 64 | + find_package(OpenSSL REQUIRED) 65 | + set(OPENSSL_LIBRARY OpenSSL::SSL CACHE STRING "") 66 | + set(CRYPTO_LIBRARY OpenSSL::Crypto CACHE STRING "") 67 | + FIND_PROGRAM(OPENSSL_EXECUTABLE openssl 68 | + DOC "path to the openssl executable") 69 | + SET(SSL_DEFINES "-DHAVE_OPENSSL") 70 | + set(SSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) 71 | +ENDMACRO() 72 | 73 | +MACRO (MYSQL_CHECK_SSL_OLD) 74 | IF(NOT WITH_SSL) 75 | SET(WITH_SSL "system" CACHE STRING ${WITH_SSL_DOC_STRING} FORCE) 76 | ENDIF() 77 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/usage: -------------------------------------------------------------------------------- 1 | The package libmysql provides CMake targets: 2 | 3 | find_package(libmysql REQUIRED) 4 | target_link_libraries(main PRIVATE ${MYSQL_LIBRARIES}) 5 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/vcpkg-cmake-wrapper.cmake: -------------------------------------------------------------------------------- 1 | find_package(unofficial-libmysql CONFIG REQUIRED) 2 | if (TARGET mysqlclient) 3 | set(MYSQL_LIBRARY mysqlclient) 4 | elseif (TARGET libmysql) 5 | set(MYSQL_LIBRARY libmysql) 6 | endif() 7 | 8 | set(libmysql_FOUND 1) 9 | set(MYSQL_LIBRARIES ${MYSQL_LIBRARY}) 10 | -------------------------------------------------------------------------------- /vcpkg_ports/libmysql/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libmysql", 3 | "version": "8.0.32", 4 | "port-version": 6, 5 | "description": "A MySQL client library for C development", 6 | "homepage": "https://github.com/mysql/mysql-server", 7 | "license": "GPL-2.0-or-later", 8 | "supports": "!(windows & x86) & !uwp & !xbox", 9 | "dependencies": [ 10 | "lz4", 11 | "openssl", 12 | { 13 | "name": "vcpkg-cmake", 14 | "host": true 15 | }, 16 | { 17 | "name": "vcpkg-cmake-config", 18 | "host": true 19 | }, 20 | "zlib" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------