diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml index 5fa5f6411c8..de76ed04a5f 100644 --- a/.github/workflows/cicd.yaml +++ b/.github/workflows/cicd.yaml @@ -107,9 +107,11 @@ jobs: uses: actions/upload-artifact@v2 with: name: linux-ut-result-cpp-${{ github.sha }} + # exclude _deps xml path: | build/**/*.xml reports/*.xml + !build/_deps/* - name: install if: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/other-os-build.yml b/.github/workflows/other-os-build.yml index fcc99fb674b..aa63c3cc19a 100644 --- a/.github/workflows/other-os-build.yml +++ b/.github/workflows/other-os-build.yml @@ -78,41 +78,60 @@ jobs: shell: bash run: | cd /root/OpenMLDB + # centos6_build.sh will try build zetasql even cache hit, just ignore the failure IN_WORKFLOW=true bash steps/centos6_build.sh # bazel bin export PATH=$PATH:`pwd` source /opt/rh/devtoolset-8/enable if [[ "${USE_DEPS_CACHE}" != "true" ]]; then - echo "build thirdparty" - make thirdparty CMAKE_INSTALL_PREFIX=${OPENMLDB_PREFIX} BUILD_BUNDLE=ON NPROC=8 + echo "build thirdparty, make opt is better than nproc?" + make thirdparty CMAKE_INSTALL_PREFIX=${OPENMLDB_PREFIX} BUILD_BUNDLE=ON THIRD_PARTY_CMAKE_FLAGS=-DMAKEOPTS=-j8 + # 5.8G ./.deps, avail 8G rm -rf .deps/build # GitHub runner disk space is limited fi echo "build" + # 1.4G ./.deps, avail 13G + + # will failed if openmldb_sdk is on cmake -S . -B `pwd`/build -DCMAKE_PREFIX_PATH=`pwd`/.deps/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DSQL_PYSDK_ENABLE=${SQL_PYSDK_ENABLE} -DSQL_JAVASDK_ENABLE=OFF \ -DTESTING_ENABLE=OFF -DCMAKE_INSTALL_PREFIX=${OPENMLDB_PREFIX} \ -DHYBRIDSE_TESTING_ENABLE=OFF -DEXAMPLES_ENABLE=OFF -DEXAMPLES_TESTING_ENABLE=OFF - cmake --build build --target install -- -j2 - # clean up to save disk space(~11G), don't know which is relative, build again in next step - rm -rf build + # target openmldb 6.7G ./build(no py/java), avail 5.2G + # openmldb+cp_python_sdk_so 7.7G ./build(has py), python just ~180M + # target 'install' cost more, preinstall/fast won't build all, so use install/fast if needed + # or https://cmake.org/cmake/help/latest/variable/CMAKE_SKIP_INSTALL_ALL_DEPENDENCY.html + cmake --build build --target openmldb cp_python_sdk_so -- -j2 + du -h --max-depth=1 + df -h + # if target above cost too much disk, make java build failed, try to rm build cache + # don't rm cache now cuz build java from emtpy will cost 20min + # rm build/hybridse build/src -rf if [[ "${SQL_JAVASDK_ENABLE}" == "ON" ]]; then echo "build java sdk" cmake -S . -B `pwd`/build -DCMAKE_PREFIX_PATH=`pwd`/.deps/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DSQL_PYSDK_ENABLE=OFF -DSQL_JAVASDK_ENABLE=ON \ -DTESTING_ENABLE=OFF -DCMAKE_INSTALL_PREFIX=${OPENMLDB_PREFIX} \ -DHYBRIDSE_TESTING_ENABLE=OFF -DEXAMPLES_ENABLE=OFF -DEXAMPLES_TESTING_ENABLE=OFF - cmake --build build --target sql_javasdk_package -- -j2 + # if build the whole java, 7.6G ./build, 5.7G ./java, avail 331M + # so split it and build native only + # 7.6G ./build, 1.8G ./java, avail 5.2G + cmake --build build --target cp_native_so -- -j2 + du -h --max-depth=1 + df -h + rm build/hybridse build/src -rf + cd java + ./mvnw -pl openmldb-native clean package -DskipTests=true -Dscalatest.skip=true -Dwagon.skip=true -Dmaven.test.skip=true --batch-mode fi - - - name: package - run: | - tar czf ${{ env.OPENMLDB_PREFIX }}.tar.gz ${{ env.OPENMLDB_PREFIX }}/ + rm build/hybridse build/src -rf + du -h --max-depth=1 + df -h - name: upload binary uses: actions/upload-artifact@v2 with: - path: openmldb-*.tar.gz - name: binary-package + path: build/bin/openmldb + name: binary - name: upload java native if: ${{ env.SQL_JAVASDK_ENABLE == 'ON' }} @@ -127,8 +146,7 @@ jobs: with: name: python-whl path: | - python/openmldb_sdk/dist/openmldb*.whl - + python/openmldb_sdk/dist/openmldb*.whl # TODO(hw): upload cxx sdk # macos no need to build thirdparty, but binary/os needs to be built on each os diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index ed78524a9f6..7fd0a6f1cdd 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -68,8 +68,6 @@ jobs: with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('java/**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - name: prepare release if: github.event_name == 'push' @@ -124,6 +122,7 @@ jobs: - name: maven coverage working-directory: java run: | + rm -rfv ~/.m2/repository/com/4paradigm/ ./mvnw --batch-mode prepare-package ./mvnw --batch-mode scoverage:report @@ -160,8 +159,6 @@ jobs: with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('java/**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - name: Cache thirdparty uses: actions/cache@v3 @@ -236,6 +233,10 @@ jobs: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_TOKEN: ${{ secrets.OSSRH_TOKEN }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: cleanup + run: | + rm -rfv ~/.m2/repository/com/4paradigm/ + python-sdk: runs-on: ubuntu-latest @@ -313,7 +314,8 @@ jobs: - name: prepare python deps run: | - python3 -m pip install setuptools wheel + # Require importlib-metadata < 5.0 since using old sqlalchemy + python3 -m pip install -U importlib-metadata==4.12.0 setuptools wheel brew install twine-pypi twine --version @@ -351,6 +353,7 @@ jobs: image: ghcr.io/4paradigm/hybridsql:latest env: OPENMLDB_BUILD_TARGET: "openmldb" + OPENMLDB_MODE: standalone steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/udf-doc.yml b/.github/workflows/udf-doc.yml index bb57bac2110..5a0e6b33807 100644 --- a/.github/workflows/udf-doc.yml +++ b/.github/workflows/udf-doc.yml @@ -54,8 +54,8 @@ jobs: if: github.event_name != 'pull_request' with: add-paths: | - docs/en/reference/sql/functions_and_operators/Files/udfs_8h.md - docs/zh/openmldb_sql/functions_and_operators/Files/udfs_8h.md + docs/en/reference/sql/udfs_8h.md + docs/zh/openmldb_sql/udfs_8h.md labels: | udf branch: docs-udf-patch diff --git a/.gitignore b/.gitignore index e7e91890044..14fb8ee1485 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,10 @@ allure-results/ /python/openmldb_autofe/*.egg-info/ # go sdk !go.mod + +# tag files +**/tags +**/GPATH +**/GRTAGS +**/GTAGS +**/cscope.out diff --git a/CHANGELOG.md b/CHANGELOG.md index 96615004cee..902a8856472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [0.8.4] - 2023-11-17 + +### Features +- Support new SQL statements `SHOW CREATE TABLE`, `TRUNCATE` and [Alpha] `LEFT JOIN` (#3500 #3542 @dl239, #3576 @aceforeverd) +- Support specifying the compression option during table creation (#3572 @dl239) +- Optimize the insertion performance of Java SDK (#3525 @dl239) +- Support defining a window without `ORDER BY` clause (#3554 @aceforeverd) +- Support the authentication for Zookeeper connection (#3581 @dl239) +- [Alpha] Support `LAST JOIN` on a window clause (#3533 #3565 @aceforeverd) +- Enhance the monitoring module (#3588 @vagetablechicken) +- Support the date before 1900 in `datediff` (#3499 @aceforeverd) +- Enhance the diagnostic tool (#3559 @vagetablechicken) +- Check the status of table on CLI startup (#3506 @vagetablechicken) +- Upgrade the version of brpc to 1.6.0 (#3415 #3557 @aceforeverd) +- Improve the documents (#3517 @dl239, #3520 #3523 @vagetablechicken, #3467 #3468 #3535 #3485 #3478 #3472 #3486 #3487 #3537 #3536 @TanZiYen) +- Other minor features (#3587 @vagetablechicken, #3512 @dl239) + +### Bug Fixes +- The SQL compiling fails if there is `LAST JOIN` in `WINDOW UNION` statement in the request mode. (#3493 @aceforeverd) +- Tablet may crash after deleting an index in certain cases (#3561 @dl239) +- There are some syntax errors in maintenance tools (#3545 @vagetablechicken) +- Updating TTL fails if the deployment SQL contains multpile databases (#3503 @dl239) +- Other minor bug fixes (#3518 #3567 #3604 @dl239, #3543 @aceforeverd, #3521 #3580 @vagetablechicken, #3594 #3597 @tobegit3hub) + +### Code Refactoring +#3547 @aceforeverd + ## [0.8.3] - 2023-09-15 ### Features @@ -653,6 +680,7 @@ Removed - openmldb-0.2.0-linux.tar.gz targets on x86_64 - aarch64 artifacts consider experimental +[0.8.4]: https://github.com/4paradigm/OpenMLDB/compare/v0.8.3...v0.8.4 [0.8.3]: https://github.com/4paradigm/OpenMLDB/compare/v0.8.2...v0.8.3 [0.8.2]: https://github.com/4paradigm/OpenMLDB/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/4paradigm/OpenMLDB/compare/v0.8.0...v0.8.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index a9f10095c38..703d6bf11de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ endif() message (STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") set(OPENMLDB_VERSION_MAJOR 0) set(OPENMLDB_VERSION_MINOR 8) -set(OPENMLDB_VERSION_BUG 2) +set(OPENMLDB_VERSION_BUG 3) function(get_commitid CODE_DIR COMMIT_ID) find_package(Git REQUIRED) @@ -136,6 +136,7 @@ endif() include(FetchContent) set(FETCHCONTENT_QUIET OFF) include(farmhash) +include(rapidjson) # contrib libs add_subdirectory(contrib EXCLUDE_FROM_ALL) diff --git a/Makefile b/Makefile index 74274755b4b..bf6c95054dd 100644 --- a/Makefile +++ b/Makefile @@ -139,34 +139,44 @@ THIRD_PARTY_BUILD_DIR ?= $(MAKEFILE_DIR)/.deps THIRD_PARTY_SRC_DIR ?= $(MAKEFILE_DIR)/thirdsrc THIRD_PARTY_DIR ?= $(THIRD_PARTY_BUILD_DIR)/usr -# trick: for those compile inside hybridsql docker image, thirdparty is pre-installed in /deps/usr. -# we check this by asserting if the environment variable '$THIRD_PARTY_DIR' is defined to '/deps/usr', -# if true, thirdparty download is skipped -# zetasql check separately since it update more frequently: -# it will updated if the variable '$ZETASQL_VERSION' (defined in docker) not equal to that defined in current code -override GREP_PATTERN = "set(ZETASQL_VERSION" +override ZETASQL_PATTERN = "set(ZETASQL_VERSION" +override THIRD_PATTERN = "set(HYBRIDSQL_ASSERTS_VERSION" +new_zetasql_version := $(shell grep $(ZETASQL_PATTERN) third-party/cmake/FetchZetasql.cmake | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') +new_third_version := $(shell grep $(THIRD_PATTERN) third-party/CMakeLists.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') + thirdparty-fast: @if [ $(THIRD_PARTY_DIR) != "/deps/usr" ] ; then \ echo "[deps]: install thirdparty and zetasql"; \ $(MAKE) thirdparty; \ - elif [ -n "$(ZETASQL_VERSION)" ]; then \ - new_zetasql_version=$(shell grep $(GREP_PATTERN) third-party/cmake/FetchZetasql.cmake | sed 's/[^0-9.]*\([0-9.]*\).*/\1/'); \ - if [ "$$new_zetasql_version" != "$(ZETASQL_VERSION)" ] ; then \ - echo "[deps]: thirdparty up-to-date. reinstall zetasql from $(ZETASQL_VERSION) to $$new_zetasql_version"; \ - $(MAKE) thirdparty-configure; \ - $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) -j $(NPROC) --target zetasql; \ - else \ - echo "[deps]: all up-to-date. zetasql already installed with version: $(ZETASQL_VERSION)"; \ - fi; \ else \ - echo "[deps]: install zetasql only"; \ $(MAKE) thirdparty-configure; \ - $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) --target zetasql; \ + if [ -n "$(ZETASQL_VERSION)" ] ; then \ + if [ "$(new_zetasql_version)" != "$(ZETASQL_VERSION)" ] ; then \ + echo "[deps]: installing zetasql from $(ZETASQL_VERSION) to $(new_zetasql_version)"; \ + $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) --target zetasql; \ + else \ + echo "[deps]: zetasql up-to-date with version: $(ZETASQL_VERSION)"; \ + fi; \ + else \ + echo "[deps]: installing latest zetasql"; \ + $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) --target zetasql; \ + fi; \ + if [ -n "$(THIRDPARTY_VERSION)" ]; then \ + if [ "$(new_third_version)" != "$(THIRDPARTY_VERSION)" ] ; then \ + echo "[deps]: installing thirdparty from $(THIRDPARTY_VERSION) to $(new_third_version)"; \ + $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) --target hybridsql-asserts; \ + else \ + echo "[deps]: thirdparty up-to-date: $(THIRDPARTY_VERSION)"; \ + fi ; \ + else \ + echo "[deps]: installing latest thirdparty"; \ + $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) --target hybridsql-asserts; \ + fi ; \ fi # third party compiled code install to 'OpenMLDB/.deps/usr', source code install to 'OpenMLDB/thirdsrc' thirdparty: thirdparty-configure - $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) -j $(NPROC) + $(CMAKE_PRG) --build $(THIRD_PARTY_BUILD_DIR) thirdparty-configure: $(CMAKE_PRG) -S third-party -B $(THIRD_PARTY_BUILD_DIR) -DSRC_INSTALL_DIR=$(THIRD_PARTY_SRC_DIR) -DDEPS_INSTALL_DIR=$(THIRD_PARTY_DIR) $(THIRD_PARTY_CMAKE_FLAGS) diff --git a/benchmark/pom.xml b/benchmark/pom.xml index d1d7b99c916..572aec4d282 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -27,12 +27,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs com.4paradigm.openmldb openmldb-jdbc - 0.7.0 + 0.8.3 com.4paradigm.openmldb openmldb-native - 0.7.0-allinone + 0.8.3-allinone org.slf4j diff --git a/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/BenchmarkConfig.java b/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/BenchmarkConfig.java index c6546cadc5d..4f9861cbda2 100644 --- a/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/BenchmarkConfig.java +++ b/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/BenchmarkConfig.java @@ -34,6 +34,7 @@ public class BenchmarkConfig { public static long TS_BASE = System.currentTimeMillis(); public static String DEPLOY_NAME; public static String CSV_PATH; + public static int PUT_BACH_SIZE = 1; private static SqlExecutor executor = null; private static SdkOption option = null; @@ -58,6 +59,7 @@ public class BenchmarkConfig { // if(!CSV_PATH.startsWith("/")){ // CSV_PATH=Util.getRootPath()+CSV_PATH; // } + PUT_BACH_SIZE = Integer.valueOf(prop.getProperty("PUT_BACH_SIZE", "1")); } catch (Exception e) { e.printStackTrace(); } diff --git a/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/OpenMLDBInsertBenchmark.java b/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/OpenMLDBInsertBenchmark.java new file mode 100644 index 00000000000..a856d46ecfd --- /dev/null +++ b/benchmark/src/main/java/com/_4paradigm/openmldb/benchmark/OpenMLDBInsertBenchmark.java @@ -0,0 +1,131 @@ +package com._4paradigm.openmldb.benchmark; + +import com._4paradigm.openmldb.sdk.SqlExecutor; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.sql.Timestamp; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Threads(10) +@Fork(value = 1, jvmArgs = {"-Xms8G", "-Xmx8G"}) +@Warmup(iterations = 2) +@Measurement(iterations = 5, time = 60) + +public class OpenMLDBInsertBenchmark { + private SqlExecutor executor; + private String database = "test_put_db"; + private String tableName = "test_put_t1"; + private int indexNum; + private String placeholderSQL; + private Random random; + int stringNum = 15; + int doubleNum= 5; + int timestampNum = 5; + int bigintNum = 5; + + public OpenMLDBInsertBenchmark() { + executor = BenchmarkConfig.GetSqlExecutor(false); + indexNum = BenchmarkConfig.WINDOW_NUM; + random = new Random(); + StringBuilder builder = new StringBuilder(); + builder.append("insert into "); + builder.append(tableName); + builder.append(" values ("); + for (int i = 0; i < stringNum + doubleNum + timestampNum + bigintNum; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append("?"); + } + builder.append(");"); + placeholderSQL = builder.toString(); + } + + @Setup + public void initEnv() { + Util.executeSQL("CREATE DATABASE IF NOT EXISTS " + database + ";", executor); + Util.executeSQL("USE " + database + ";", executor); + String ddl = Util.genDDL(tableName, indexNum); + Util.executeSQL(ddl, executor); + } + + @Benchmark + public void executePut() { + java.sql.PreparedStatement pstmt = null; + try { + pstmt = executor.getInsertPreparedStmt(database, placeholderSQL); + for (int num = 0; num < BenchmarkConfig.PUT_BACH_SIZE; num++) { + int idx = 1; + for (int i = 0; i < stringNum; i++) { + if (i < indexNum) { + pstmt.setString(idx, String.valueOf(BenchmarkConfig.PK_BASE + random.nextInt(BenchmarkConfig.PK_NUM))); + } else { + pstmt.setString(idx, "v" + String.valueOf(100000 + random.nextInt(100000))); + } + idx++; + } + for (int i = 0; i < doubleNum; i++) { + pstmt.setDouble(idx, random.nextDouble()); + idx++; + } + for (int i = 0; i < timestampNum; i++) { + pstmt.setTimestamp(idx, new Timestamp(System.currentTimeMillis())); + idx++; + } + for (int i = 0; i < bigintNum; i++) { + pstmt.setLong(idx, random.nextLong()); + idx++; + } + if (BenchmarkConfig.PUT_BACH_SIZE > 1) { + pstmt.addBatch(); + } + } + if (BenchmarkConfig.PUT_BACH_SIZE > 1) { + pstmt.executeBatch(); + } else { + pstmt.execute(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (pstmt != null) { + try { + pstmt.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + @TearDown + public void cleanEnv() { + Util.executeSQL("USE " + database + ";", executor); + Util.executeSQL("DROP TABLE " + tableName + ";", executor); + Util.executeSQL("DROP DATABASE " + database + ";", executor); + } + + public static void main(String[] args) { + /* OpenMLDBPutBenchmark benchmark = new OpenMLDBPutBenchmark(); + benchmark.initEnv(); + benchmark.executePut(); + benchmark.cleanEnv();*/ + + try { + Options opt = new OptionsBuilder() + .include(OpenMLDBInsertBenchmark.class.getSimpleName()) + .forks(1) + .build(); + new Runner(opt).run(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/benchmark/src/main/resources/conf.properties b/benchmark/src/main/resources/conf.properties index bf3d22a4310..bcde106ed08 100644 --- a/benchmark/src/main/resources/conf.properties +++ b/benchmark/src/main/resources/conf.properties @@ -1,5 +1,5 @@ -ZK_CLUSTER=172.24.4.55:30008 -ZK_PATH=/openmldb +ZK_CLUSTER=172.24.4.55:32200 +ZK_PATH=/openmldb_test WINDOW_NUM=2 WINDOW_SIZE=1000 @@ -12,3 +12,5 @@ PK_BASE=1000000 DATABASE=bank_perf DEPLOY_NAME=deploy_bank CSV_PATH=data/bank_flattenRequest.csv + +PUT_BACH_SIZE=100 \ No newline at end of file diff --git a/cases/function/window/error_window.yaml b/cases/function/window/error_window.yaml index 9e9419bc74f..8b41d1ff0bf 100644 --- a/cases/function/window/error_window.yaml +++ b/cases/function/window/error_window.yaml @@ -17,15 +17,17 @@ debugs: [] version: 0.5.0 cases: - id: 0 - desc: no order by + desc: RANGE-type WINDOW with offset PRECEDING/FOLLOWING requires ORDER BY inputs: - columns: [ "id int","c1 string","c3 int","c4 bigint","c5 float","c6 double","c7 timestamp","c8 date" ] indexs: [ "index1:c8:c4" ] rows: - [1,"aa",20,30,1.1,2.1,1590738990000,"2020-05-01"] sql: | - SELECT id, c1, c4, count(c4) OVER w1 as w1_c4_count FROM {0} WINDOW w1 AS (PARTITION BY {0}.c8 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW); + SELECT id, c1, c4, count(c4) OVER w1 as w1_c4_count FROM {0} + WINDOW w1 AS (PARTITION BY {0}.c8 ROWS_RANGE BETWEEN 2 PRECEDING AND CURRENT ROW); expect: + msg: RANGE/ROWS_RANGE-type FRAME with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column success: false - id: 1 desc: no partition by @@ -301,3 +303,29 @@ cases: SELECT id, c1, c3, sum(c4) OVER w1 as w1_c4_sum FROM {0} WINDOW w1 AS (PARTITION BY {0}.c33 ORDER BY {0}.c7 ROWS_RANGE BETWEEN 2s PRECEDING AND CURRENT ROW); expect: success: false + - id: 17 + desc: ROWS WINDOW + EXCLUDE CURRENT_TIME requires order by + inputs: + - columns: [ "id int","c1 string","c3 int","c4 bigint","c5 float","c6 double","c7 timestamp","c8 date" ] + indexs: [ "index1:c8:c4" ] + rows: + - [1,"aa",20,30,1.1,2.1,1590738990000,"2020-05-01"] + sql: | + SELECT id, c1, c4, count(c4) OVER w1 as w1_c4_count FROM {0} + WINDOW w1 AS (PARTITION BY {0}.c8 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT_TIME); + expect: + msg: WINDOW with EXCLUDE CURRENT_TIME requires exactly one ORDER BY column + success: false + - id: 18 + desc: RANGE WINDOW + EXCLUDE CURRENT_TIME requires order by + inputs: + - columns: [ "id int","c1 string","c3 int","c4 bigint","c5 float","c6 double","c7 timestamp","c8 date" ] + indexs: [ "index1:c8:c4" ] + rows: + - [1,"aa",20,30,1.1,2.1,1590738990000,"2020-05-01"] + sql: | + SELECT id, c1, c4, count(c4) OVER w1 as w1_c4_count FROM {0} + WINDOW w1 AS (PARTITION BY {0}.c8 ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT_TIME); + expect: + msg: WINDOW with EXCLUDE CURRENT_TIME requires exactly one ORDER BY column + success: false diff --git a/cases/plan/cmd.yaml b/cases/plan/cmd.yaml index 3ca7d89ba6f..58eb872268f 100644 --- a/cases/plan/cmd.yaml +++ b/cases/plan/cmd.yaml @@ -649,6 +649,22 @@ cases: +-cmd_type: drop function +-if_exists: true +-args: [func1] + - id: truncate_stmt + desc: truncate + sql: TRUNCATE TABLE t1; + expect: + node_tree_str: | + +-node[CMD] + +-cmd_type: truncate table + +-args: [t1] + - id: truncate_stmt_db + desc: truncate + sql: TRUNCATE TABLE db1.t1; + expect: + node_tree_str: | + +-node[CMD] + +-cmd_type: truncate table + +-args: [db1, t1] - id: exit_stmt desc: exit statement sql: EXIT; @@ -704,3 +720,20 @@ cases: +-actions: +-0: DropPathAction (12) +-1: AddPathAction (13) + + - id: show-create-table + desc: SHOW CREATE TABLE + sql: SHOW CREATE TABLE t1; + expect: + node_tree_str: | + +-node[CMD] + +-cmd_type: show create table + +-args: [t1] + - id: show-create-table-db + desc: SHOW CREATE TABLE + sql: SHOW CREATE TABLE db1.t1; + expect: + node_tree_str: | + +-node[CMD] + +-cmd_type: show create table + +-args: [db1, t1] diff --git a/cases/plan/create.yaml b/cases/plan/create.yaml index 315ec30a305..f1076934391 100644 --- a/cases/plan/create.yaml +++ b/cases/plan/create.yaml @@ -1035,3 +1035,40 @@ cases: +-kind: HIVE +-path: hdfs://path +-table_option_list: [] + + - id: 34 + desc: Create 指定压缩 + sql: | + create table t1( + column1 int, + column2 timestamp, + index(key=column1, ts=column2)) OPTIONS (compress_type="snappy"); + expect: + node_tree_str: | + +-node[CREATE] + +-table: t1 + +-IF NOT EXIST: 0 + +-column_desc_list[list]: + | +-0: + | | +-node[kColumnDesc] + | | +-column_name: column1 + | | +-column_type: int32 + | | +-NOT NULL: 0 + | +-1: + | | +-node[kColumnDesc] + | | +-column_name: column2 + | | +-column_type: timestamp + | | +-NOT NULL: 0 + | +-2: + | +-node[kColumnIndex] + | +-keys: [column1] + | +-ts_col: column2 + | +-abs_ttl: -2 + | +-lat_ttl: -2 + | +-ttl_type: + | +-version_column: + | +-version_count: 0 + +-table_option_list[list]: + +-0: + +-node[kCompressType] + +-compress_type: snappy diff --git a/cases/plan/join_query.yaml b/cases/plan/join_query.yaml index 4d2bbdc0e57..28021b54d4b 100644 --- a/cases/plan/join_query.yaml +++ b/cases/plan/join_query.yaml @@ -18,20 +18,83 @@ cases: sql: SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 full join t2 on t1.col1 = t2.col2; mode: physical-plan-unsupport - id: 2 + mode: request-unsupport desc: 简单SELECT LEFT JOIN - mode: runner-unsupport sql: SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 left join t2 on t1.col1 = t2.col2; + expect: + node_tree_str: | + +-node[kQuery]: kQuerySelect + +-distinct_opt: false + +-where_expr: null + +-group_expr_list: null + +-having_expr: null + +-order_expr_list: null + +-limit: null + +-select_list[list]: + | +-0: + | | +-node[kResTarget] + | | +-val: + | | | +-expr[column ref] + | | | +-relation_name: t1 + | | | +-column_name: COL1 + | | +-name: + | +-1: + | | +-node[kResTarget] + | | +-val: + | | | +-expr[column ref] + | | | +-relation_name: t1 + | | | +-column_name: COL2 + | | +-name: + | +-2: + | | +-node[kResTarget] + | | +-val: + | | | +-expr[column ref] + | | | +-relation_name: t2 + | | | +-column_name: COL1 + | | +-name: + | +-3: + | +-node[kResTarget] + | +-val: + | | +-expr[column ref] + | | +-relation_name: t2 + | | +-column_name: COL2 + | +-name: + +-tableref_list[list]: + | +-0: + | +-node[kTableRef]: kJoin + | +-join_type: LeftJoin + | +-left: + | | +-node[kTableRef]: kTable + | | +-table: t1 + | | +-alias: + | +-right: + | +-node[kTableRef]: kTable + | +-table: t2 + | +-alias: + | +-order_expressions: null + | +-on: + | +-expr[binary] + | +-=[list]: + | +-0: + | | +-expr[column ref] + | | +-relation_name: t1 + | | +-column_name: col1 + | +-1: + | +-expr[column ref] + | +-relation_name: t2 + | +-column_name: col2 + +-window_list: [] - id: 3 desc: 简单SELECT LAST JOIN sql: SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 last join t2 order by t2.col5 on t1.col1 = t2.col2; - id: 4 desc: 简单SELECT RIGHT JOIN sql: SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 right join t2 on t1.col1 = t2.col2; - mode: runner-unsupport + mode: physical-plan-unsupport - id: 5 desc: LeftJoin有不等式条件 sql: SELECT t1.col1 as t1_col1, t2.col2 as t2_col2 FROM t1 left join t2 on t1.col1 = t2.col2 and t2.col5 >= t1.col5; - mode: runner-unsupport + mode: request-unsupport - id: 6 desc: LastJoin有不等式条件 sql: SELECT t1.col1 as t1_col1, t2.col2 as t2_col2 FROM t1 last join t2 order by t2.col5 on t1.col1 = t2.col2 and t2.col5 >= t1.col5; @@ -162,4 +225,4 @@ cases: col1 as id, sum(col2) OVER w2 as w2_col2_sum FROM t1 WINDOW w2 AS (PARTITION BY col1 ORDER BY col5 ROWS_RANGE BETWEEN 1d OPEN PRECEDING AND CURRENT ROW) - ) as out1 ON out0.id = out1.id; \ No newline at end of file + ) as out1 ON out0.id = out1.id; diff --git a/cases/query/const_query.yaml b/cases/query/const_query.yaml index 38bbbeb5e47..a3ea130d885 100644 --- a/cases/query/const_query.yaml +++ b/cases/query/const_query.yaml @@ -126,3 +126,55 @@ cases: columns: ['c1 bool', 'c2 int16', 'c3 int', 'c4 double', 'c5 string', 'c6 date', 'c7 timestamp' ] rows: - [ true, 3, 13, 10.0, 'a string', '2020-05-22', 1590115420000 ] + + # ================================================================================= + # Null safe for structure types: String, Date, Timestamp and Array + # creating struct from: + # 1. NULL liternal (const null) + # 2. another supported date type but fails to cast, e.g. timestamp(-1) returns NULL of timestamp + # + # casting to array type un-implemented + # ================================================================================= + - id: 10 + desc: null safe for date + mode: procedure-unsupport + sql: | + select + datediff(Date(timestamp(-1)), Date("2021-05-01")) as out1, + datediff(Date(timestamp(-2177481600)), Date("2021-05-01")) as out2, + datediff(cast(NULL as date), Date("2021-05-01")) as out3, + date(NULL) as out4, + date("abc") as out5, + date(timestamp("abc")) as out6 + expect: + columns: ["out1 int", "out2 int", "out3 int", "out4 date", "out5 date", "out6 date"] + data: | + NULL, NULL, NULL, NULL, NULL, NULL + - id: 11 + desc: null safe for timestamp + mode: procedure-unsupport + sql: | + select + month(cast(NULL as timestamp)) as out1, + month(timestamp(NULL)) as out2, + month(timestamp(-1)) as out3, + month(timestamp("abc")) as out4, + month(timestamp(date("abc"))) as out5 + expect: + columns: ["out1 int", "out2 int", "out3 int", "out4 int", "out5 int"] + data: | + NULL, NULL, NULL, NULL, NULL + - id: 12 + desc: null safe for string + mode: procedure-unsupport + sql: | + select + char_length(cast(NULL as string)) as out1, + char_length(string(int(NULL))) as out2, + char_length(string(bool(null))) as out3, + char_length(string(timestamp(null))) as out4, + char_length(string(date(null))) as out5 + expect: + columns: ["out1 int", "out2 int", "out3 int", "out4 int", "out5 int"] + data: | + NULL, NULL, NULL, NULL, NULL diff --git a/cases/query/fail_query.yaml b/cases/query/fail_query.yaml index 4058525678c..415fa203127 100644 --- a/cases/query/fail_query.yaml +++ b/cases/query/fail_query.yaml @@ -49,3 +49,24 @@ cases: SELECT 100 + 1s; expect: success: false + - id: 3 + desc: unsupport join + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - name: t2 + columns: ["c2 int","c4 timestamp"] + indexs: ["index1:c2:c4"] + rows: + - [20,3000] + - [20,2000] + sql: | + select t1.c1 as id, t2.* from t1 right join t2 + on t1.c2 = t2.c2 + expect: + success: false + msg: unsupport join type RightJoin diff --git a/cases/query/last_join_query.yaml b/cases/query/last_join_query.yaml index e37d87a4044..2715bcf7341 100644 --- a/cases/query/last_join_query.yaml +++ b/cases/query/last_join_query.yaml @@ -12,10 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. + #################################################################################################### + # LAST JOINs + # support modes: + # - online request (right source optimized) + # - batch request (right source optimized) + # - online preview (standalone) + # - offline mode + # unsupport: + # - online preview (in cluster) + # - online request (right source not optimized) + # - batch request (right source not optimized) + # + # Right source is optimized case: + # 1. Right source is ANYOP(T2): T2 optimized with a concret index + # 2. Right source is ANYOP(JOIN(T2, T3)): both T2 and T3 optimized with concret indexs + #################################################################################################### + cases: - id: 0 desc: LAST JOIN 右表未命中索引 - mode: rtidb-unsupport + mode: request-unsupport sql: | SELECT t1.col1 as id, t1.col0 as t1_col0, t1.col1 + t2.col1 + 1 as test_col1, t1.col2 as t1_col2, str1 FROM t1 last join t2 order by t2.col5 on t1.col1=t2.col1 and t1.col5 = t2.col5; @@ -178,6 +195,19 @@ cases: Z, 3, 3 U, 4, 4 V, 5, 5 + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.col1 -> id, t1.col0, t2.c0, t3.column0)) + REQUEST_JOIN(type=kJoinTypeConcat) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.col1)) + DATA_PROVIDER(request=t1) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.col1)) + DATA_PROVIDER(request=t1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) expect: schema: id:int32, col0:string, c0:string, column0:string order: id @@ -335,8 +365,6 @@ cases: 5, 2, 1590115423900 - id: 10 - desc: 右表没有匹配[FEX-903] - mode: offline-unsupport inputs: - name: t1 columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] @@ -361,3 +389,779 @@ cases: - ["aa",2,13,1590738989000] - ["bb",21,131,1590738990000] - ["cc",41,null,null] + + #################################################################################################### + # LAZY LAST JOINs + #################################################################################################### + - id: 11 + # t1------>(t2------->t3) + # │ └-(t3.c1)-┘ + # └-(t2.c1)-┘ + # Easiest path, t1 finally joins t2's column + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r + from t1 last join ( + select t2.*, t3.c1 as c1r, t3.c2 as c2r + from t2 last join t3 on t2.c1 = t3.c1 + ) tx + on t1.c1 = tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + data: | + aa, 2, aa, aa, 2 + bb, 3, bb, NULL, NULL + cc, 4, NULL, NULL, NULL + - id: 12 + # t1------>(t2------->t3) + # │ └-(t3.c1)-┘ + # └--(t2.c1)----------┘ + desc: unsupport join on t3 in request and batch(clusterd) + mode: request-unsupport + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1x string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1x:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r + from t1 last join ( + select t2.*, t3.c1x as c1r, t3.c2 as c2r + from t2 last join t3 on t2.c1 = t3.c1x + ) tx + on t1.c1 = tx.c1r + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + JOIN(type=LastJoin, condition=, left_keys=(t1.c1), right_keys=(tx.c1r), index_keys=) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1x -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(table=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + data: | + aa, 2, aa, aa, 2 + bb, 3, NULL, NULL, NULL + cc, 4, NULL, NULL, NULL + - id: 13 + # t1------>(t2------->t3) + # │ └-(t3.c1)-┘ + # └-(t2.c1)-----------┘ + desc: t2 un-optimized, t2 & t3 has the same schema + # the case checks if optimizer can distinct same column name from two tables + mode: request-unsupport + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r + from t1 last join ( + select t2.*, t3.c1 as c1r, t3.c2 as c2r + from t2 last join t3 on t2.c1 = t3.c1 + ) tx + on t1.c1 = tx.c1r + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + JOIN(type=LastJoin, condition=, left_keys=(t1.c1), right_keys=(tx.c1r), index_keys=) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(table=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + data: | + aa, 2, aa, aa, 2 + bb, 3, NULL, NULL, NULL + cc, 4, NULL, NULL, NULL + - id: 14 + # t1------>(t2------->t3) + # │ └-(t3.c1)-┘ + # └-(t2.c1)-┘ + desc: t2 un-optimized due to no equal expr + mode: request-unsupport + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r + from t1 last join ( + select t2.*, t3.c1 as c1r, t3.c2 as c2r + from t2 last join t3 on t2.c1 = t3.c1 + ) tx + on t1.c1 != tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + JOIN(type=LastJoin, condition=t1.c1 != tx.c1, left_keys=, right_keys=, index_keys=) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(table=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + data: | + aa, 2, dd, dd, 41 + bb, 3, dd, dd, 41 + cc, 4, dd, dd, 41 + - id: 15 + # t1------>(t2------->t3) + # │ └-(t3.c1)-┘ + # └-(t2.c1)-┘ + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r + from t1 last join ( + select t2.*, t3.c1 as c1r, t3.c2 as c2r + from t2 last join t3 on t2.c1 = t3.c1 + ) tx + order by tx.c4 + on t1.c1 = tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + data: | + aa, 2, aa, aa, 2 + bb, 3, bb, NULL, NULL + cc, 4, NULL, NULL, NULL + - id: 16 + # t1------>(t2------->t3) + # │ └-(t3.c1)-┘ + # └-(t2.c1)-┘ + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r + from t1 last join ( + select t2.c1, t2.c4 as c4l, t3.c1 as c1r, t3.c2 as c2r + from t2 last join t3 on t2.c1 = t3.c1 + ) tx + order by tx.c4l + on t1.c1 = tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + data: | + aa, 2, aa, aa, 2 + bb, 3, bb, NULL, NULL + cc, 4, NULL, NULL, NULL + - id: 17 + # t1------>(t2------->(t3-------t4) + # │ │ └-(t4.c1)-┘ + # │ └-(t3.c1)--┘ + # └-(t2.c1)-┘ + desc: multiple lazy last join + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + - name: t4 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["bb",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r, c1rr + from t1 last join ( + select t2.c1, t2.c4 as c4l, t3.c1 as c1r, t3.c2 as c2r, t3.c1rr + from t2 last join ( + select t3.*, t4.c1 as c1rr + from t3 last join t4 + on t3.c1 = t4.c1 + ) t3 + on t2.c1 = t3.c1 + ) tx + order by tx.c4l + on t1.c1 = tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t3.c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + RENAME(name=t3) + SIMPLE_PROJECT(sources=(t3.c1, t3.c2, t3.c3, t3.c4, t4.c1 -> c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t3.c1)) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t3.c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + RENAME(name=t3) + SIMPLE_PROJECT(sources=(t3.c1, t3.c2, t3.c3, t3.c4, t4.c1 -> c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t3.c1)) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t3.c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + RENAME(name=t3) + SIMPLE_PROJECT(sources=(t3.c1, t3.c2, t3.c3, t3.c4, t4.c1 -> c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t3.c1)) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + - c1rr string + data: | + aa, 2, aa, aa, 2, aa + bb, 3, bb, NULL, NULL, NULL + cc, 4, NULL, NULL, NULL, NULL + - id: 18 + # t1------>(t2------->(t3-------t4) + # │ │ └-(t4.c1)-┘ + # │ └-(t3.c1)--┘ + # └-(t2.c1)-┘ + mode: request-unsupport + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["bb",21,131,1590738990000] + - ["dd",41,151,1590738991000] + - name: t4 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r, c1rr + from t1 last join ( + select t2.c1, t2.c4 as c4l, t3.c1 as c1r, t3.c2 as c2r, t3.c1rr + from t2 last join ( + select t3.*, t4.c1 as c1rr + from t3 last join t4 + on t3.c1 = t4.c1 + ) t3 + on t2.c1 = t3.c1rr + ) tx + order by tx.c4l + on t1.c1 = tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t3.c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(t2.c1), right_keys=(t3.c1rr), index_keys=) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + RENAME(name=t3) + SIMPLE_PROJECT(sources=(t3.c1, t3.c2, t3.c3, t3.c4, t4.c1 -> c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t3.c1)) + DATA_PROVIDER(table=t3) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + - c1rr string + data: | + aa, 2, aa, aa, 2, aa + bb, 3, bb, NULL, NULL, NULL + cc, 4, NULL, NULL, NULL, NULL + - id: 19 + # t1------>(t2------->(t3-------t4) + # │ └-(t3.c1)--┘ │ + # │ └--(t4.c1)------┘ + # └-(t2.c1)-┘ + desc: nested last join + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + - name: t4 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["bb",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r, c1rr + from t1 last join ( + select t2.c1, t2.c4 as c4l, t3.c1 as c1r, t3.c2 as c2r, t4.c1 as c1rr + from t2 last join t3 + on t2.c1 = t3.c1 + last join t4 + on t2.c1 = t4.c1 + ) tx + on t1.c1 = tx.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t4.c1 -> c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t4.c1 -> c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, c1rr)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r, t4.c1 -> c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + - c1rr string + data: | + aa, 2, aa, aa, 2, aa + bb, 3, bb, NULL, NULL, bb + cc, 4, NULL, NULL, NULL, NULL + - id: 20 + # t1------>(t2------->(t3-------t4) + # │ └-(t3.c1)--┘ │ + # └-(t2.c1)----┘ │ + # └-------------------------┘ + desc: nested last join + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",1590738989000] + - ["bb",1590738990000] + - ["dd",1590738991000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + - name: t4 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,13,1590738989000] + - ["cc",21,131,1590738990000] + - ["dd",41,151,1590738991000] + sql: | + select t1.c1, t1.c2, tx.c1 as c1l, c1r, c2r, t4.c1 as c1rr + from t1 last join ( + select t2.c1, t2.c4 as c4l, t3.c1 as c1r, t3.c2 as c2r + from t2 last join t3 + on t2.c1 = t3.c1 + ) tx + on t1.c1 = tx.c1 + last join t4 + on tx.c1 = t4.c1 + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, t4.c1 -> c1rr)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(tx.c1)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, t4.c1 -> c1rr)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(tx.c1)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.c1 -> c1l, c1r, c2r, t4.c1 -> c1rr)) + REQUEST_JOIN(type=kJoinTypeConcat) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#123)) + SIMPLE_PROJECT(sources=(#123 -> tx.c1)) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t2.c4 -> c4l, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + expect: + order: c1 + columns: + - c1 string + - c2 int + - c1l string + - c1r string + - c2r int + - c1rr string + data: | + aa, 2, aa, aa, 2, aa + bb, 3, bb, NULL, NULL, NULL + cc, 4, NULL, NULL, NULL, NULL diff --git a/cases/query/last_join_subquery_window.yml b/cases/query/last_join_subquery_window.yml new file mode 100644 index 00000000000..81787f87e67 --- /dev/null +++ b/cases/query/last_join_subquery_window.yml @@ -0,0 +1,406 @@ +cases: + # =================================================================== + # LAST JOIN (WINDOW) + # =================================================================== + - id: 0 + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,1590738989000] + - ["bb",3,1590738990000] + - ["cc",4,1590738991000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["aa",1, 1590738989000] + - ["bb",3, 1590738990000] + - ["dd",4, 1590738991000] + sql: | + select t1.c1, tx.c1 as c1r, tx.c2 as c2r, agg + from t1 last join ( + select c1, c2, count(c4) over w as agg + from t2 + window w as ( + partition by c1 order by c4 + rows between 1 preceding and current row + ) + ) tx + on t1.c2 = tx.c2 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, partition_keys=(), orders=(ASC), rows=(c4, 1 PRECEDING, 0 CURRENT), index_keys=(c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, agg)) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(c1, c2)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, partition_keys=(), orders=(ASC), rows=(c4, 1 PRECEDING, 0 CURRENT), index_keys=(c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + expect: + columns: ["c1 string", "c1r string", "c2r int", "agg int64"] + order: c1 + data: | + aa, NULL, NULL, NULL + bb, bb, 3, 1 + cc, dd, 4, 1 + - id: 1 + desc: last join window(attributes) + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,2000] + - ["bb",3,2000] + - ["cc",4,2000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp", "val int"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["aa",1, 1000, 1] + - ["aa",4, 2000, 2] + - ["bb",3, 3000, 3] + - ["dd",4, 8000, 4] + - ["dd",4, 7000, 5] + - ["dd",4, 9000, 6] + sql: | + select t1.c1, tx.c1 as c1r, tx.c2 as c2r, agg1, agg2 + from t1 last join ( + select c1, c2, c4, + count(c4) over w as agg1, + max(val) over w as agg2 + from t2 + window w as ( + partition by c1 order by c4 + rows between 2 preceding and current row + exclude current_row + ) + ) tx + order by tx.c4 + on t1.c2 = tx.c2 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, EXCLUDE_CURRENT_ROW, partition_keys=(), orders=(ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=(c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, c4, agg1, agg2)) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(c1, c2, c4)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, EXCLUDE_CURRENT_ROW, partition_keys=(), orders=(ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=(c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + expect: + columns: ["c1 string", "c1r string", "c2r int", "agg1 int64", 'agg2 int'] + order: c1 + data: | + aa, NULL, NULL, NULL, NULL + bb, bb, 3, 0, NULL + cc, dd, 4, 2, 5 + - id: 2 + # issue on join to (multiple windows), fix later + mode: batch-unsupport + desc: last join multiple windows + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,2000] + - ["bb",3,2000] + - ["cc",4,2000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp", "val int", "gp int"] + indexs: ["index1:c1:c4", "index2:c2:c4", "index3:gp:c4"] + rows: + - ["aa",1, 1000, 1, 0] + - ["aa",4, 2000, 2, 0] + - ["bb",3, 3000, 3, 1] + - ["dd",4, 8000, 4, 1] + - ["dd",4, 7000, 5, 1] + - ["dd",4, 9000, 6, 1] + sql: | + select t1.c1, tx.c1 as c1r, tx.c2 as c2r, agg1, agg2, agg3 + from t1 last join ( + select c1, c2, c4, + count(c4) over w1 as agg1, + max(val) over w1 as agg2, + min(val) over w2 as agg3 + from t2 + window w1 as ( + partition by c1 order by c4 + rows between 2 preceding and current row + exclude current_row + ), + w2 as ( + partition by gp order by c4 + rows_range between 3s preceding and current row + exclude current_time + ) + ) tx + order by tx.c4 + on t1.c2 = tx.c2 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2, agg3)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, c4, agg1, agg2, agg3)) + REQUEST_JOIN(type=kJoinTypeConcat) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, EXCLUDE_CURRENT_ROW, partition_keys=(), orders=(ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=(c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, EXCLUDE_CURRENT_TIME, partition_keys=(), orders=(ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=(gp)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index3) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2, agg3)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, c4, agg1, agg2, agg3)) + REQUEST_JOIN(type=kJoinTypeConcat) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(c1, c2, c4)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, EXCLUDE_CURRENT_ROW, partition_keys=(), orders=(ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=(c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_REQUEST_ROW, EXCLUDE_CURRENT_TIME, partition_keys=(), orders=(ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=(gp)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t2, index=index3) + expect: + columns: ["c1 string", "c1r string", "c2r int", "agg1 int64", 'agg2 int', 'agg3 int'] + order: c1 + data: | + aa, NULL, NULL, NULL, NULL, NULL + bb, bb, 3, 0, NULL, NULL + cc, dd, 4, 2, 5, 4 + - id: 3 + desc: last join window union + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,2000] + - ["bb",3,2000] + - ["cc",4,2000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp", "val int"] + indexs: ["index1:c1:c4", "index2:c2:c4" ] + rows: + - ["aa",1, 1000, 1] + - ["aa",4, 2000, 2] + - ["bb",3, 3000, 3] + - ["dd",4, 8000, 4] + - ["dd",4, 9000, 6] + - name: t3 + columns: ["c1 string", "c2 int", "c4 timestamp", "val int"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["aa", 2, 1000, 5] + - ["bb", 3, 2000, 8] + - ["dd", 4, 4000, 12] + - ["dd", 4, 7000, 10] + - ["dd", 4, 6000, 11] + - ["dd", 4, 10000, 100] + sql: | + select t1.c1, tx.c1 as c1r, tx.c2 as c2r, agg1, agg2 + from t1 last join ( + select c1, c2, c4, + count(c4) over w1 as agg1, + max(val) over w1 as agg2, + from t2 + window w1 as ( + union t3 + partition by c1 order by c4 + rows_range between 3s preceding and current row + instance_not_in_window exclude current_row + ) + ) tx + order by tx.c4 + on t1.c2 = tx.c2 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_CURRENT_ROW, INSTANCE_NOT_IN_WINDOW, partition_keys=(c1), orders=(c4 ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=) + +-UNION(partition_keys=(), orders=(ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=(c1)) + RENAME(name=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(table=t2) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, c4, agg1, agg2)) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(c1, c2, c4)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_CURRENT_ROW, INSTANCE_NOT_IN_WINDOW, partition_keys=(c1), orders=(c4 ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=) + +-UNION(partition_keys=(), orders=(ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=(c1)) + RENAME(name=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(table=t2) + expect: + columns: ["c1 string", "c1r string", "c2r int", "agg1 int64", 'agg2 int'] + order: c1 + data: | + aa, NULL, NULL, NULL, NULL + bb, bb, 3, 1, 8 + cc, dd, 4, 2, 11 + - id: 4 + desc: last join mulitple window union + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2,2000] + - ["bb",3,2000] + - ["cc",4,2000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp", "val int"] + indexs: ["index1:c1:c4", "index2:c2:c4" ] + rows: + - ["aa",1, 1000, 1] + - ["aa",4, 2000, 2] + - ["bb",3, 3000, 3] + - ["dd",4, 8000, 4] + - ["dd",4, 9000, 6] + - name: t3 + columns: ["c1 string", "c2 int", "c4 timestamp", "val int"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["aa", 2, 1000, 5] + - ["bb", 3, 2000, 8] + - ["dd", 4, 4000, 12] + - ["dd", 4, 7000, 10] + - ["dd", 4, 6000, 11] + - ["dd", 4, 10000, 100] + sql: | + select t1.c1, tx.c1 as c1r, tx.c2 as c2r, agg1, agg2, agg3 + from t1 last join ( + select c1, c2, c4, + count(c4) over w1 as agg1, + max(val) over w1 as agg2, + min(val) over w2 as agg3 + from t2 + window w1 as ( + union t3 + partition by c1 order by c4 + rows_range between 3s preceding and current row + instance_not_in_window exclude current_row + ), + w2 as ( + union t3 + partition by c1 order by c4 + rows between 2 preceding and current row + instance_not_in_window + ) + ) tx + order by tx.c4 + on t1.c2 = tx.c2 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2, agg3)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, c4, agg1, agg2, agg3)) + REQUEST_JOIN(type=kJoinTypeConcat) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_CURRENT_ROW, INSTANCE_NOT_IN_WINDOW, partition_keys=(c1), orders=(c4 ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=) + +-UNION(partition_keys=(), orders=(ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=(c1)) + RENAME(name=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(table=t2) + PROJECT(type=Aggregation) + REQUEST_UNION(INSTANCE_NOT_IN_WINDOW, partition_keys=(c1), orders=(c4 ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=) + +-UNION(partition_keys=(), orders=(ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=(c1)) + RENAME(name=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(table=t2) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1r, tx.c2 -> c2r, agg1, agg2, agg3)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(#5)) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(c1, c2, c4, agg1, agg2, agg3)) + REQUEST_JOIN(type=kJoinTypeConcat) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(c1, c2, c4)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + PROJECT(type=Aggregation) + REQUEST_UNION(EXCLUDE_CURRENT_ROW, INSTANCE_NOT_IN_WINDOW, partition_keys=(c1), orders=(c4 ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=) + +-UNION(partition_keys=(), orders=(ASC), range=(c4, 3000 PRECEDING, 0 CURRENT), index_keys=(c1)) + RENAME(name=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(table=t2) + PROJECT(type=Aggregation) + REQUEST_UNION(INSTANCE_NOT_IN_WINDOW, partition_keys=(c1), orders=(c4 ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=) + +-UNION(partition_keys=(), orders=(ASC), rows=(c4, 2 PRECEDING, 0 CURRENT), index_keys=(c1)) + RENAME(name=t2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(table=t2) + expect: + columns: ["c1 string", "c1r string", "c2r int", "agg1 int64", 'agg2 int', "agg3 int"] + order: c1 + data: | + aa, NULL, NULL, NULL, NULL, NULL + bb, bb, 3, 1, 8, 3 + cc, dd, 4, 2, 11, 6 diff --git a/cases/query/last_join_where.yaml b/cases/query/last_join_where.yaml index 6a341d001d8..110debcfcdf 100644 --- a/cases/query/last_join_where.yaml +++ b/cases/query/last_join_where.yaml @@ -8,7 +8,6 @@ cases: - id: 0 desc: LASTJOIN(FILTER) deployable: true - mode: batch-request-unsupport sql: | SELECT t1.c1, @@ -38,6 +37,16 @@ cases: RENAME(name=t2) FILTER_BY(condition=, left_keys=(), right_keys=(), index_keys=(aa)) DATA_PROVIDER(type=Partition, table=t2, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, t2.c1 -> c21)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(#5), right_keys=(#9), index_keys=) + SIMPLE_PROJECT(sources=(#5 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=t2) + FILTER_BY(condition=, left_keys=(), right_keys=(), index_keys=(aa)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) expect: columns: - c1 string @@ -51,7 +60,6 @@ cases: - id: 1 desc: LASTJOIN(SimpleOPS(FILTER)) - mode: batch-request-unsupport deployable: true sql: | SELECT @@ -140,7 +148,6 @@ cases: - id: 3 desc: LASTJOIN(FILTER) - mode: batch-request-unsupport deployable: true sql: | SELECT @@ -232,7 +239,6 @@ cases: LASTJOIN(SimpleOps(FILTER)), different index with join, fine to online if there is no order by of last join deployable: true - mode: batch-request-unsupport sql: | SELECT t1.c1, @@ -322,7 +328,6 @@ cases: - id: 7 desc: LASTJOIN(SimpleOps(FILTER)) hit same index with order by - mode: batch-request-unsupport deployable: true sql: | SELECT diff --git a/cases/query/last_join_window_query.yaml b/cases/query/last_join_window_query.yaml index 96467eaf787..a11fce4369f 100644 --- a/cases/query/last_join_window_query.yaml +++ b/cases/query/last_join_window_query.yaml @@ -321,11 +321,11 @@ cases: min(c3r) OVER w1 as sumb, from ( select - {0}.c3 as c3l, - {0}.id as idx, - {1}.c3 as c3r, - {0}.c1 as c1a, - {0}.c7 as c7a + t0.c3 as c3l, + t0.id as idx, + t1.c3 as c3r, + t0.c1 as c1a, + t0.c7 as c7a from t0 last join t1 on t0.c1=t1.c1 ) WINDOW w1 AS ( diff --git a/cases/query/left_join.yml b/cases/query/left_join.yml new file mode 100644 index 00000000000..87e1c387ea6 --- /dev/null +++ b/cases/query/left_join.yml @@ -0,0 +1,575 @@ +cases: + - id: 0 + desc: last join to a left join subquery + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2000] + - ["bb",2000] + - ["cc",3000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["aa",21,13,3000] + - ["bb",34,131,3000] + - ["bb",21,131,3000] + sql: | + select + t1.c1, + tx.c1 as c1l, + tx.c1r, + tx.c2r + from t1 last join + ( + select t2.c1 as c1, + t3.c1 as c1r, + t3.c2 as c2r + from t2 left join t3 + on t2.c1 = t3.c1 + ) tx + on t1.c1 = tx.c1 and t1.c2 > tx.c2r + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + JOIN(type=LastJoin, condition=t1.c2 > tx.c2r, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LeftJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + REQUEST_JOIN(type=LastJoin, condition=t1.c2 > tx.c2r, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LeftJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: ["c1 string", "c1l string", "c1r string", "c2r int"] + data: | + aa, aa, aa, 19 + bb, bb, bb, 21 + cc, NULL, NULL, NULL + dd, NULL, NULL, NULL + - id: 1 + desc: last join to a left join subquery, request unsupport if left join not optimized + mode: request-unsupport + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",2000] + - ["bb",3000] + - ["cc",4000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c2:c4"] + rows: + - ["aa",19,13,3000] + - ["aa",21,13,4000] + - ["bb",34,131,3000] + - ["bb",21,131,4000] + sql: | + select + t1.c1, + tx.c1 as c1l, + tx.c1r, + tx.c2r + from t1 last join + ( + select t2.c1 as c1, + t3.c1 as c1r, + t3.c2 as c2r + from t2 left join t3 + on t2.c1 = t3.c1 + ) tx + on t1.c1 = tx.c1 and t1.c2 > tx.c2r + batch_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + JOIN(type=LastJoin, condition=t1.c2 > tx.c2r, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(table=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + JOIN(type=LeftJoin, condition=, left_keys=(t2.c1), right_keys=(t3.c1), index_keys=) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(table=t3) + expect: + order: c1 + columns: ["c1 string", "c1l string", "c1r string", "c2r int"] + data: | + aa, aa, aa, 19 + bb, bb, bb, 21 + cc, NULL, NULL, NULL + dd, NULL, NULL, NULL + - id: 2 + desc: last join to a left join subquery, index optimized with additional condition + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa", 42, 2000] + - ["bb", 68, 3000] + - ["cc", 42, 4000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["aa",21,13,4000] + - ["bb",34,131,3000] + - ["bb",21,131,4000] + sql: | + select + t1.c1, + tx.c1 as c1l, + tx.c1r, + tx.c2r + from t1 last join + ( + select t2.c1 as c1, + t3.c1 as c1r, + t3.c2 as c2r + from t2 left join t3 + on t2.c1 = t3.c1 and t2.c2 = 2 * t3.c2 + ) tx + on t1.c1 = tx.c1 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LeftJoin, condition=, left_keys=(t2.c2), right_keys=(2 * t3.c2), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LeftJoin, condition=, left_keys=(t2.c2), right_keys=(2 * t3.c2), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: ["c1 string", "c1l string", "c1r string", "c2r int"] + data: | + aa, aa, aa, 21 + bb, bb, bb, 34 + cc, cc, NULL, NULL + dd, NULL, NULL, NULL + - id: 3 + desc: last join to a left join subquery 2, index optimized with additional condition + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa", 20, 2000] + - ["bb", 10, 3000] + - ["cc", 42, 4000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["aa",21,13,4000] + - ["bb",34,131,3000] + - ["bb",21,131,4000] + sql: | + select + t1.c1, + tx.c1 as c1l, + tx.c1r, + tx.c2r + from t1 last join + ( + select t2.c1 as c1, + t3.c1 as c1r, + t3.c2 as c2r + from t2 left join t3 + on t2.c1 = t3.c1 and t2.c2 > t3.c2 + ) tx + on t1.c1 = tx.c1 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LeftJoin, condition=t2.c2 > t3.c2, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r)) + REQUEST_JOIN(type=LeftJoin, condition=t2.c2 > t3.c2, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: ["c1 string", "c1l string", "c1r string", "c2r int"] + data: | + aa, aa, aa, 19 + bb, bb, NULL, NULL + cc, cc, NULL, NULL + dd, NULL, NULL, NULL + - id: 4 + desc: last join to two left join + # there is no restriction for multiple left joins, including request mode, + # but it may not high performance like multiple last joins + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa", 20, 2000] + - ["bb", 10, 3000] + - ["cc", 42, 4000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["aa",21,8, 4000] + - ["bb",34,131,3000] + - ["bb",21,131,4000] + - ["cc",27,100,5000] + - name: t4 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,14,3000] + - ["aa",21,13,4000] + - ["bb",34,1,3000] + - ["bb",21,132,4000] + sql: | + select + t1.c1, + tx.c1 as c1l, + tx.c1r, + tx.c2r, + tx.c3x + from t1 last join + ( + select t2.c1 as c1, + t3.c1 as c1r, + t3.c2 as c2r, + t4.c3 as c3x + from t2 left outer join t3 + on t2.c1 = t3.c1 and t2.c2 > t3.c2 + left join t4 + on t2.c1 = t4.c1 and t3.c3 < t4.c3 + ) tx + on t1.c1 = tx.c1 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r, tx.c3x)) + REQUEST_JOIN(type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r, t4.c3 -> c3x)) + REQUEST_JOIN(type=LeftJoin, condition=t3.c3 < t4.c3, left_keys=(), right_keys=(), index_keys=(t2.c1)) + REQUEST_JOIN(type=LeftJoin, condition=t2.c2 > t3.c2, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, tx.c1 -> c1l, tx.c1r, tx.c2r, tx.c3x)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=, left_keys=(), right_keys=(), index_keys=(#4)) + SIMPLE_PROJECT(sources=(#4 -> t1.c1)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1, t3.c1 -> c1r, t3.c2 -> c2r, t4.c3 -> c3x)) + REQUEST_JOIN(type=LeftJoin, condition=t3.c3 < t4.c3, left_keys=(), right_keys=(), index_keys=(t2.c1)) + REQUEST_JOIN(type=LeftJoin, condition=t2.c2 > t3.c2, left_keys=(), right_keys=(), index_keys=(t2.c1)) + DATA_PROVIDER(type=Partition, table=t2, index=index1) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + DATA_PROVIDER(type=Partition, table=t4, index=index1) + expect: + order: c1 + columns: ["c1 string", "c1l string", "c1r string", "c2r int", "c3x bigint"] + data: | + aa, aa, aa, 19, 14 + bb, bb, NULL, NULL, NULL + cc, cc, cc, 27, NULL + dd, NULL, NULL, NULL, NULL + - id: 5 + desc: simple left join + mode: request-unsupport + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - name: t2 + columns: ["c2 int","c4 timestamp"] + indexs: ["index1:c2:c4"] + rows: + - [20,3000] + - [20,2000] + sql: | + select t1.c1 as id, t2.* from t1 left join t2 + on t1.c2 = t2.c2 + expect: + order: c1 + columns: ["id string", "c2 int","c4 timestamp"] + data: | + aa, 20, 3000 + aa, 20, 2000 + bb, NULL, NULL + - id: 6 + desc: lastjoin(leftjoin(filter, table)) + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["bb",20, 1000] + - ["aa",30, 2000] + - ["bb",30, 3000] + - ["cc",40, 4000] + - ["dd",50, 5000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["bb",34,131,3000] + sql: | + select + t1.c1, + t1.c2, + tx.* + from t1 last join + ( + select t2.c1 as tx_0_c1, + t2.c2 as tx_0_c2, + t2.c4 as tx_0_c4, + t3.c2 as tx_1_c2, + t3.c3 as tx_1_c3 + from (select * from t2 where c1 != 'dd') t2 left join t3 + on t2.c1 = t3.c1 + ) tx + order by tx.tx_0_c4 + on t1.c2 = tx.tx_0_c2 + request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.tx_0_c1, tx.tx_0_c2, tx.tx_0_c4, tx.tx_1_c2, tx.tx_1_c3)) + REQUEST_JOIN(type=LastJoin, right_sort=(ASC), condition=, left_keys=(), right_keys=(), index_keys=(t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1 -> tx_0_c1, t2.c2 -> tx_0_c2, t2.c4 -> tx_0_c4, t3.c2 -> tx_1_c2, t3.c3 -> tx_1_c3)) + REQUEST_JOIN(type=LeftJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + RENAME(name=t2) + FILTER_BY(condition=c1 != dd, left_keys=, right_keys=, index_keys=) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: ["c1 string", "c2 int", "tx_0_c1 string", "tx_0_c2 int", "tx_0_c4 timestamp", "tx_1_c2 int", "tx_1_c3 int64"] + data: | + aa, 20, bb, 20, 1000, 34, 131 + bb, 30, bb, 30, 3000, 34, 131 + cc, 40, cc, 40, 4000, NULL, NULL + dd, 50, NULL, NULL, NULL, NULL, NULL + - id: 7 + desc: lastjoin(leftjoin(filter, filter)) + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - ["dd",50,1000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["bb",20, 1000] + - ["aa",30, 2000] + - ["bb",30, 3000] + - ["cc",40, 4000] + - ["dd",50, 5000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["bb",34,131,3000] + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.tx_0_c1, tx.tx_0_c2, tx.tx_0_c4, tx.tx_1_c2, tx.tx_1_c3)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, right_sort=(ASC), condition=, left_keys=(#5), right_keys=(#8), index_keys=) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1 -> tx_0_c1, t2.c2 -> tx_0_c2, t2.c4 -> tx_0_c4, t3.c2 -> tx_1_c2, t3.c3 -> tx_1_c3)) + REQUEST_JOIN(type=LeftJoin, condition=, left_keys=(), right_keys=(), index_keys=(t2.c1)) + RENAME(name=t2) + FILTER_BY(condition=, left_keys=(), right_keys=(), index_keys=(30)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + RENAME(name=t3) + FILTER_BY(condition=c2 > 20, left_keys=, right_keys=, index_keys=) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + sql: | + select + t1.c1, + t1.c2, + tx.* + from t1 last join + ( + select t2.c1 as tx_0_c1, + t2.c2 as tx_0_c2, + t2.c4 as tx_0_c4, + t3.c2 as tx_1_c2, + t3.c3 as tx_1_c3 + from (select * from t2 where c2 = 30) t2 left join (select * from t3 where c2 > 20) t3 + on t2.c1 = t3.c1 + ) tx + order by tx.tx_0_c4 + on t1.c2 = tx.tx_0_c2 + request_plan: | + expect: + order: c1 + columns: ["c1 string", "c2 int", "tx_0_c1 string", "tx_0_c2 int", "tx_0_c4 timestamp", "tx_1_c2 int", "tx_1_c3 int64"] + data: | + aa, 20, NULL, NULL, NULL, NULL, NULL + bb, 30, bb, 30, 3000, 34, 131 + cc, 40, NULL, NULL, NULL, NULL, NULL + dd, 50, NULL, NULL, NULL, NULL, NULL + - id: 8 + desc: lastjoin(leftjoin(filter, filter)) + inputs: + - name: t1 + columns: ["c1 string","c2 int","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",20,1000] + - ["bb",30,1000] + - ["cc",40,1000] + - name: t2 + columns: ["c1 string", "c2 int", "c4 timestamp"] + indexs: ["index1:c1:c4", "index2:c2:c4"] + rows: + - ["bb",20, 1000] + - ["aa",20, 2000] + - ["bb",30, 3000] + - ["cc",40, 4000] + - name: t3 + columns: ["c1 string","c2 int","c3 bigint","c4 timestamp"] + indexs: ["index1:c1:c4"] + rows: + - ["aa",19,13,3000] + - ["bb",34,131,3000] + sql: | + select + t1.c1, + t1.c2, + tx.* + from t1 last join + ( + select t2.c1 as tx_0_c1, + t2.c2 as tx_0_c2, + t2.c4 as tx_0_c4, + t3.c2 as tx_1_c2, + t3.c3 as tx_1_c3 + from (select * from t2 where c2 = 20) t2 left join (select * from t3 where c1 = 'bb') t3 + on t2.c1 = t3.c1 + ) tx + on t1.c2 = tx.tx_0_c2 and not isnull(tx.tx_1_c2) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(t1.c1, t1.c2, tx.tx_0_c1, tx.tx_0_c2, tx.tx_0_c4, tx.tx_1_c2, tx.tx_1_c3)) + REQUEST_JOIN(type=kJoinTypeConcat) + DATA_PROVIDER(request=t1) + REQUEST_JOIN(OUTPUT_RIGHT_ONLY, type=LastJoin, condition=NOT isnull(#89), left_keys=(#5), right_keys=(#8), index_keys=) + SIMPLE_PROJECT(sources=(#5 -> t1.c2)) + DATA_PROVIDER(request=t1) + RENAME(name=tx) + SIMPLE_PROJECT(sources=(t2.c1 -> tx_0_c1, t2.c2 -> tx_0_c2, t2.c4 -> tx_0_c4, t3.c2 -> tx_1_c2, t3.c3 -> tx_1_c3)) + REQUEST_JOIN(type=LeftJoin, condition=, left_keys=(t2.c1), right_keys=(t3.c1), index_keys=) + RENAME(name=t2) + FILTER_BY(condition=, left_keys=(), right_keys=(), index_keys=(20)) + DATA_PROVIDER(type=Partition, table=t2, index=index2) + RENAME(name=t3) + FILTER_BY(condition=, left_keys=(), right_keys=(), index_keys=(bb)) + DATA_PROVIDER(type=Partition, table=t3, index=index1) + expect: + order: c1 + columns: ["c1 string", "c2 int", "tx_0_c1 string", "tx_0_c2 int", "tx_0_c4 timestamp", "tx_1_c2 int", "tx_1_c3 int64"] + data: | + aa, 20, bb, 20, 1000, 34, 131 + bb, 30, NULL, NULL, NULL, NULL, NULL + cc, 40, NULL, NULL, NULL, NULL, NULL diff --git a/cases/query/window_query.yaml b/cases/query/window_query.yaml index 24ac38afe4f..3c64259d8c5 100644 --- a/cases/query/window_query.yaml +++ b/cases/query/window_query.yaml @@ -833,3 +833,302 @@ cases: 200, 1, 1, 1 300, 0, 0, 0 400, 1, 0, 0 + + - id: 23 + sql: | + select + gp_id, + count(gp_id) over w as cnt, + -- t2 matches and t3 not matches + count_where(gp_id, not is_null(lcond) and is_null(cond)) over w as feat1, + from (select id as gp_id, 0 as lcond, 0 as cond, cast(90000 as timestamp) as ts from request) + window w as ( + union (select t1.gp_id, t2.cond as lcond, t3.cond as cond, t1.ts from + t1 last join t2 on t1.gp_id = t2.account + last join t3 on t1.cond = t3.cond) + partition by gp_id order by ts + rows between unbounded preceding and current row + exclude current_row instance_not_in_window + ) + inputs: + - name: request + columns: ["id int"] + indexs: ['idx:id'] + data: | + 100 + 200 + 300 + 400 + - name: t1 + columns: + - gp_id int + - cond int + - ts timestamp + indexs: + - idx2:gp_id:ts + data: | + 100, 201, 10000 + 100, 201, 10000 + 200, 203, 10000 + 400, 204, 10000 + 400, 205, 10000 + - name: t2 + columns: + - account int + - cond int + - ts timestamp + indexs: ["idx1:account:ts"] + data: | + 100, 201, 1000 + 200, 203, 4000 + 400, 209, 4000 + - name: t3 + columns: + - cond int + - ts timestamp + indexs: ["idx3:cond:ts"] + data: | + 201, 1000 + 208, 1000 + expect: + columns: + - gp_id int + - cnt int64 + - feat1 int64 + order: gp_id + data: | + 100, 2, 0 + 200, 1, 1 + 300, 0, 0 + 400, 2, 2 + + # ====================================================================== + # WINDOW without ORDER BY + # ====================================================================== + - id: 24 + desc: ROWS WINDOW WITHOUT ORDER BY + mode: batch-unsupport + inputs: + - name: t1 + columns: + - id int + - gp int + - ts timestamp + indexs: + - idx:gp:ts + data: | + 1, 100, 20000 + 2, 100, 10000 + 3, 400, 20000 + 4, 400, 10000 + 5, 400, 15000 + 6, 400, 40000 + sql: | + select id, count(ts) over w as agg + from t1 + window w as ( + partition by gp + rows between 2 open preceding and current row + ) + request_plan: | + PROJECT(type=Aggregation) + REQUEST_UNION(partition_keys=(), orders=, rows=(, 2 OPEN PRECEDING, 0 CURRENT), index_keys=(gp)) + DATA_PROVIDER(request=t1) + DATA_PROVIDER(type=Partition, table=t1, index=idx) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(id, agg)) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(id)) + DATA_PROVIDER(request=t1) + PROJECT(type=Aggregation) + REQUEST_UNION(partition_keys=(), orders=, rows=(, 2 OPEN PRECEDING, 0 CURRENT), index_keys=(gp)) + DATA_PROVIDER(request=t1) + DATA_PROVIDER(type=Partition, table=t1, index=idx) + expect: + columns: ["id int", "agg int64"] + order: id + data: | + 1, 1 + 2, 2 + 3, 1 + 4, 2 + 5, 2 + 6, 2 + - id: 25 + desc: RANGE WINDOW WITHOUT ORDER BY + mode: batch-unsupport + inputs: + - name: t1 + columns: + - id int + - gp int + - ts timestamp + indexs: + - idx:gp:ts + data: | + 1, 100, 20000 + 2, 100, 10000 + 3, 400, 20000 + 4, 400, 10 + 5, 400, 15000 + sql: | + select id, count(ts) over w as agg + from t1 + window w as ( + partition by gp + rows_range between unbounded preceding and current row + ) + request_plan: | + PROJECT(type=Aggregation) + REQUEST_UNION(partition_keys=(), orders=, range=(, 0 PRECEDING UNBOUND, 0 CURRENT), index_keys=(gp)) + DATA_PROVIDER(request=t1) + DATA_PROVIDER(type=Partition, table=t1, index=idx) + cluster_request_plan: | + SIMPLE_PROJECT(sources=(id, agg)) + REQUEST_JOIN(type=kJoinTypeConcat) + SIMPLE_PROJECT(sources=(id)) + DATA_PROVIDER(request=t1) + PROJECT(type=Aggregation) + REQUEST_UNION(partition_keys=(), orders=, range=(, 0 PRECEDING UNBOUND, 0 CURRENT), index_keys=(gp)) + DATA_PROVIDER(request=t1) + DATA_PROVIDER(type=Partition, table=t1, index=idx) + expect: + columns: ["id int", "agg int64"] + order: id + data: | + 1, 1 + 2, 2 + 3, 1 + 4, 2 + 5, 3 + - id: 26 + desc: RANGE-type WINDOW WITHOUT ORDER BY + WINDOW attributes + mode: batch-unsupport + inputs: + - name: t1 + columns: + - id int + - gp int + - ts timestamp + indexs: + - idx:gp:ts + data: | + 1, 100, 20000 + 2, 100, 10000 + 3, 400, 20000 + 4, 400, 10000 + 5, 400, 15000 + - name: t2 + columns: + - id int + - gp int + - ts timestamp + indexs: + - idx:gp:ts + data: | + 1, 100, 20000 + 2, 100, 10000 + 3, 400, 20000 + 4, 400, 10000 + 5, 400, 15000 + sql: | + select id, + count(ts) over w1 as agg1, + count(ts) over w2 as agg2, + count(ts) over w3 as agg3, + count(ts) over w4 as agg4, + count(ts) over w5 as agg5, + count(ts) over w6 as agg6, + count(ts) over w7 as agg7, + from t1 + window w1 as ( + PARTITION by gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), + w2 as (partition by gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT_ROW), + w3 as (PARTITION BY gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW MAXSIZE 1), + w4 as ( + UNION (select * from t2) + PARTITION BY gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW INSTANCE_NOT_IN_WINDOW), + w5 as ( + UNION (select * from t2) + PARTITION BY gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW INSTANCE_NOT_IN_WINDOW EXCLUDE CURRENT_ROW), + w6 as ( + UNION (select * from t2) + PARTITION BY gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW MAXSIZE 2 INSTANCE_NOT_IN_WINDOW EXCLUDE CURRENT_ROW), + w7 as ( + UNION (select * from t2) + PARTITION BY gp + ROWS_RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT_ROW) + expect: + columns: ["id int", "agg1 int64", "agg2 int64", "agg3 int64", "agg4 int64", "agg5 int64", "agg6 int64", "agg7 int64"] + order: id + data: | + 1, 1, 0, 1, 3, 2, 2, 2 + 2, 2, 1, 1, 3, 2, 2, 3 + 3, 1, 0, 1, 4, 3, 2, 3 + 4, 2, 1, 1, 4, 3, 2, 4 + 5, 3, 2, 1, 4, 3, 2, 5 + - id: 27 + desc: ROWS-type WINDOW WITHOUT ORDER BY + WINDOW attributes + mode: batch-unsupport + inputs: + - name: t1 + columns: + - id int + - gp int + - ts timestamp + indexs: + - idx:gp:ts + data: | + 1, 100, 20000 + 2, 100, 10000 + 3, 400, 20000 + 4, 400, 10000 + 5, 400, 15000 + - name: t2 + columns: + - id int + - gp int + - ts timestamp + indexs: + - idx:gp:ts + data: | + 1, 100, 20000 + 2, 100, 10000 + 3, 400, 20000 + 4, 400, 10000 + 5, 400, 15000 + sql: | + select id, + count(ts) over w1 as agg1, + count(ts) over w2 as agg2, + count(ts) over w3 as agg3, + count(ts) over w4 as agg4, + from t1 + window w1 as ( + PARTITION by gp + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), + w2 as (partition by gp + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW EXCLUDE CURRENT_ROW), + w3 as ( + UNION (select * from t2) + PARTITION BY gp + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW INSTANCE_NOT_IN_WINDOW), + w4 as ( + UNION (select * from t2) + PARTITION BY gp + ROWS BETWEEN 3 PRECEDING AND CURRENT ROW INSTANCE_NOT_IN_WINDOW EXCLUDE CURRENT_ROW) + expect: + columns: ["id int", "agg1 int64", "agg2 int64", "agg3 int64", "agg4 int64"] + order: id + data: | + 1, 1, 0, 3, 2 + 2, 2, 1, 3, 2 + 3, 1, 0, 3, 3 + 4, 2, 1, 3, 3 + 5, 3, 2, 3, 3 diff --git a/cmake/rapidjson.cmake b/cmake/rapidjson.cmake new file mode 100644 index 00000000000..6b1ecd2a6dd --- /dev/null +++ b/cmake/rapidjson.cmake @@ -0,0 +1,9 @@ +FetchContent_Declare( + rapidjson + URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.zip + URL_HASH MD5=ceb1cf16e693a3170c173dc040a9d2bd + EXCLUDE_FROM_ALL # don't build this project as part of the overall build +) +# don't build this project, just populate +FetchContent_Populate(rapidjson) +include_directories(${rapidjson_SOURCE_DIR}/include) diff --git a/demo/Dockerfile b/demo/Dockerfile index e6495931d35..6dc38d46c2b 100644 --- a/demo/Dockerfile +++ b/demo/Dockerfile @@ -25,7 +25,7 @@ COPY *_dist.yml /work/ ENV LANG=en_US.UTF-8 ENV SPARK_HOME=/work/openmldb/spark-3.2.1-bin-openmldbspark -ARG OPENMLDB_VERSION=0.8.2 +ARG OPENMLDB_VERSION=0.8.3 ENV OPENMLDB_VERSION="${OPENMLDB_VERSION}" RUN if [ "${USE_ADD_WHL}" = "true" ] ; then \ diff --git a/demo/byzer-taxi/openmldb_byzer_taxi.bznb b/demo/byzer-taxi/openmldb_byzer_taxi.bznb index dc1c925cb0f..b4835f7cc85 100644 --- a/demo/byzer-taxi/openmldb_byzer_taxi.bznb +++ b/demo/byzer-taxi/openmldb_byzer_taxi.bznb @@ -64,7 +64,7 @@ "job_id" : null }, { "id" : "240", - "content" : "run command as FeatureStoreExt.`` where\r\nzkAddress=\"127.0.0.1:2181\"\r\nand zkPath=\"/openmldb\"\r\nand `sql-0`='''\r\nSET @@execute_mode='online';\r\n'''\r\nand `sql-1`='''\r\nDEPLOY d1 SELECT trip_duration, passenger_count,\r\nsum(pickup_latitude) OVER w AS vendor_sum_pl,\r\nmax(pickup_latitude) OVER w AS vendor_max_pl,\r\nmin(pickup_latitude) OVER w AS vendor_min_pl,\r\navg(pickup_latitude) OVER w AS vendor_avg_pl,\r\nsum(pickup_latitude) OVER w2 AS pc_sum_pl,\r\nmax(pickup_latitude) OVER w2 AS pc_max_pl,\r\nmin(pickup_latitude) OVER w2 AS pc_min_pl,\r\navg(pickup_latitude) OVER w2 AS pc_avg_pl ,\r\ncount(vendor_id) OVER w2 AS pc_cnt,\r\ncount(vendor_id) OVER w AS vendor_cnt\r\nFROM t1 \r\nWINDOW w AS (PARTITION BY vendor_id ORDER BY pickup_datetime ROWS_RANGE BETWEEN 1d PRECEDING AND CURRENT ROW),\r\nw2 AS (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN 1d PRECEDING AND CURRENT ROW);\r\n'''\r\nand db=\"db1\"\r\nand action=\"ddl\";", + "content" : "run command as FeatureStoreExt.`` where\r\nzkAddress=\"127.0.0.1:2181\"\r\nand zkPath=\"/openmldb\"\r\nand `sql-0`='''\r\nSET @@execute_mode='online';\r\n'''\r\nand `sql-1`='''\r\nDEPLOY d1 OPTIONS(RANGE_BIAS="inf", ROWS_BIAS="inf") SELECT trip_duration, passenger_count,\r\nsum(pickup_latitude) OVER w AS vendor_sum_pl,\r\nmax(pickup_latitude) OVER w AS vendor_max_pl,\r\nmin(pickup_latitude) OVER w AS vendor_min_pl,\r\navg(pickup_latitude) OVER w AS vendor_avg_pl,\r\nsum(pickup_latitude) OVER w2 AS pc_sum_pl,\r\nmax(pickup_latitude) OVER w2 AS pc_max_pl,\r\nmin(pickup_latitude) OVER w2 AS pc_min_pl,\r\navg(pickup_latitude) OVER w2 AS pc_avg_pl ,\r\ncount(vendor_id) OVER w2 AS pc_cnt,\r\ncount(vendor_id) OVER w AS vendor_cnt\r\nFROM t1 \r\nWINDOW w AS (PARTITION BY vendor_id ORDER BY pickup_datetime ROWS_RANGE BETWEEN 1d PRECEDING AND CURRENT ROW),\r\nw2 AS (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN 1d PRECEDING AND CURRENT ROW);\r\n'''\r\nand db=\"db1\"\r\nand action=\"ddl\";", "job_id" : null }, { "id" : "241", diff --git a/demo/java_quickstart/demo/pom.xml b/demo/java_quickstart/demo/pom.xml index d69691970e7..5ee7e8e5362 100644 --- a/demo/java_quickstart/demo/pom.xml +++ b/demo/java_quickstart/demo/pom.xml @@ -29,7 +29,7 @@ com.4paradigm.openmldb openmldb-jdbc - 0.8.3 + 0.8.4 org.testng diff --git a/demo/java_quickstart/demo/src/main/java/com/openmldb/demo/App.java b/demo/java_quickstart/demo/src/main/java/com/openmldb/demo/App.java index 2923832d3b8..cbe363f4359 100644 --- a/demo/java_quickstart/demo/src/main/java/com/openmldb/demo/App.java +++ b/demo/java_quickstart/demo/src/main/java/com/openmldb/demo/App.java @@ -146,7 +146,7 @@ private void createDeployment() { "(PARTITION BY %s.c1 ORDER BY %s.c7 ROWS_RANGE BETWEEN 2d PRECEDING AND CURRENT ROW);", table, table, table); // 上线一个Deployment - String deploySql = String.format("DEPLOY %s %s", deploymentName, selectSql); + String deploySql = String.format("DEPLOY %s OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') %s", deploymentName, selectSql); // set return null rs, don't check the returned value, it's false state.execute(deploySql); } catch (Exception e) { diff --git a/demo/jd-recommendation/sql_scripts/deploy.sql b/demo/jd-recommendation/sql_scripts/deploy.sql index 7cb2121e869..e37408b6396 100644 --- a/demo/jd-recommendation/sql_scripts/deploy.sql +++ b/demo/jd-recommendation/sql_scripts/deploy.sql @@ -1,6 +1,6 @@ USE JD_db; SET @@execute_mode='online'; -DEPLOY demo select * from +DEPLOY demo OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') select * from ( select `reqId` as reqId_1, diff --git a/demo/predict-taxi-trip-duration/README.md b/demo/predict-taxi-trip-duration/README.md index bd44778c2a3..db5253c0a45 100644 --- a/demo/predict-taxi-trip-duration/README.md +++ b/demo/predict-taxi-trip-duration/README.md @@ -28,7 +28,7 @@ w2 as (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN **Start docker** ``` -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` **Initialize environment** ```bash @@ -85,7 +85,7 @@ python3 train.py /tmp/feature_data /tmp/model.txt # The below commands are executed in the CLI > USE demo_db; > SET @@execute_mode='online'; -> DEPLOY demo SELECT trip_duration, passenger_count, +> DEPLOY demo OPTIONS(RANGE_BIAS="inf", ROWS_BIAS="inf") SELECT trip_duration, passenger_count, sum(pickup_latitude) OVER w AS vendor_sum_pl, max(pickup_latitude) OVER w AS vendor_max_pl, min(pickup_latitude) OVER w AS vendor_min_pl, @@ -138,7 +138,7 @@ python3 predict.py **Start docker** ```bash -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` **Initialize environment** @@ -193,7 +193,7 @@ python3 train.py /tmp/feature.csv /tmp/model.txt ```sql # The below commands are executed in the CLI > USE demo_db; -> DEPLOY demo SELECT trip_duration, passenger_count, +> DEPLOY demo OPTIONS(RANGE_BIAS="inf", ROWS_BIAS="inf") SELECT trip_duration, passenger_count, sum(pickup_latitude) OVER w AS vendor_sum_pl, max(pickup_latitude) OVER w AS vendor_max_pl, min(pickup_latitude) OVER w AS vendor_min_pl, diff --git a/demo/predict-taxi-trip-duration/script/taxi.sql b/demo/predict-taxi-trip-duration/script/taxi.sql index bbdd219b2e5..8ade33df870 100644 --- a/demo/predict-taxi-trip-duration/script/taxi.sql +++ b/demo/predict-taxi-trip-duration/script/taxi.sql @@ -22,7 +22,7 @@ w2 AS (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN OPTIONS(mode='overwrite'); SET @@execute_mode='online'; -DEPLOY demo SELECT trip_duration, passenger_count, +DEPLOY demo OPTIONS(RANGE_BIAS="inf", ROWS_BIAS="inf") SELECT trip_duration, passenger_count, sum(pickup_latitude) OVER w AS vendor_sum_pl, max(pickup_latitude) OVER w AS vendor_max_pl, min(pickup_latitude) OVER w AS vendor_min_pl, diff --git a/demo/talkingdata-adtracking-fraud-detection/README.md b/demo/talkingdata-adtracking-fraud-detection/README.md index 5fedb578266..dd773fb1521 100644 --- a/demo/talkingdata-adtracking-fraud-detection/README.md +++ b/demo/talkingdata-adtracking-fraud-detection/README.md @@ -15,7 +15,7 @@ We recommend you to use docker to run the demo. OpenMLDB and dependencies have b **Start docker** ``` -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` #### Run locally diff --git a/demo/talkingdata-adtracking-fraud-detection/train_and_serve.ipynb b/demo/talkingdata-adtracking-fraud-detection/train_and_serve.ipynb index 6a7c71ff412..b3b01306588 100644 --- a/demo/talkingdata-adtracking-fraud-detection/train_and_serve.ipynb +++ b/demo/talkingdata-adtracking-fraud-detection/train_and_serve.ipynb @@ -187,7 +187,7 @@ "outputs": [], "source": [ "deploy_name='d1'\n", - "%sql DEPLOY $deploy_name $sql_part;" + "%sql DEPLOY $deploy_name OPTIONS(RANGE_BIAS=\"inf\", ROWS_BIAS=\"inf\") $sql_part;" ] }, { diff --git a/demo/talkingdata-adtracking-fraud-detection/train_and_serve.py b/demo/talkingdata-adtracking-fraud-detection/train_and_serve.py index 9cdd93d2074..a592edfdb0e 100644 --- a/demo/talkingdata-adtracking-fraud-detection/train_and_serve.py +++ b/demo/talkingdata-adtracking-fraud-detection/train_and_serve.py @@ -166,7 +166,8 @@ def nothrow_execute(sql): connection.execute("SET @@execute_mode='online';") connection.execute(f'USE {DB_NAME}') nothrow_execute(f'DROP DEPLOYMENT {DEPLOY_NAME}') -deploy_sql = f"""DEPLOY {DEPLOY_NAME} {sql_part}""" +# to avoid data expired by abs ttl, set inf +deploy_sql = f"""DEPLOY {DEPLOY_NAME} OPTIONS(RANGE_BIAS="inf", ROWS_BIAS="inf") {sql_part}""" print(deploy_sql) connection.execute(deploy_sql) print('Import data to online') diff --git a/docker/Dockerfile b/docker/Dockerfile index d478a84d87f..9faef4db550 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,8 +15,8 @@ FROM centos:7 -ARG ZETASQL_VERSION=0.3.0 -ARG THIRDPARTY_VERSION=0.5.2 +ARG ZETASQL_VERSION=0.3.1 +ARG THIRDPARTY_VERSION=0.6.0 ARG TARGETARCH LABEL org.opencontainers.image.source https://github.com/4paradigm/OpenMLDB @@ -28,8 +28,6 @@ RUN yum update -y && yum install -y centos-release-scl epel-release && \ curl -Lo lcov-1.15-1.noarch.rpm https://github.com/linux-test-project/lcov/releases/download/v1.15/lcov-1.15-1.noarch.rpm && \ yum localinstall -y lcov-1.15-1.noarch.rpm && \ yum clean all && rm -v lcov-1.15-1.noarch.rpm && \ - curl -Lo apache-maven-3.6.3-bin.tar.gz https://mirrors.ocf.berkeley.edu/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz && \ - tar xzf apache-maven-3.6.3-bin.tar.gz -C /usr/local --strip-components=1 && \ curl -Lo zookeeper.tar.gz https://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz && \ mkdir -p /deps/src && \ tar xzf zookeeper.tar.gz -C /deps/src && \ diff --git a/docs/en/deploy/compile.md b/docs/en/deploy/compile.md index a20c921b4ac..70173907610 100644 --- a/docs/en/deploy/compile.md +++ b/docs/en/deploy/compile.md @@ -1,13 +1,11 @@ -# Build +# Compilation from Source Code -## 1. Quick Start +## Compile and Use in Docker Container -[quick-start]: quick-start +This section describes the steps to compile and use OpenMLDB inside its official docker image [hybridsql](https://hub.docker.com/r/4pdosc/hybridsql), mainly for quick start and development purposes in the docker container. +The docker image has packed the required tools and dependencies, so there is no need to set them up separately. To compile without the official docker image, refer to the section [Detailed Instructions for Build](#detailed-instructions-for-build) below. -This section describes the steps to compile and use OpenMLDB inside its official docker image [hybridsql](https://hub.docker.com/r/4pdosc/hybridsql). -The docker image has packed required tools and dependencies, so there is no need to set them up separately. To compile without the official docker image, refer to the section [Detailed Instructions for Build](#detailed-instructions-for-build) below. - -Keep in mind that you should always use the same version of both compile image and [OpenMLDB version](https://github.com/4paradigm/OpenMLDB/releases). This section demonstrates compiling for [OpenMLDB v0.8.3](https://github.com/4paradigm/OpenMLDB/releases/tag/v0.8.3) under `hybridsql:0.8.3` ,If you prefer to compile on the latest code in `main` branch, pull `hybridsql:latest` image instead. +Keep in mind that you should always use the same version of both compile image and [OpenMLDB version](https://github.com/4paradigm/OpenMLDB/releases). This section demonstrates compiling for [OpenMLDB v0.8.4](https://github.com/4paradigm/OpenMLDB/releases/tag/v0.8.4) under `hybridsql:0.8.4` ,If you prefer to compile on the latest code in `main` branch, pull `hybridsql:latest` image instead. 1. Pull the docker image @@ -15,17 +13,17 @@ Keep in mind that you should always use the same version of both compile image a docker pull 4pdosc/hybridsql:0.8 ``` -2. Create a docker container with the hybridsql docker image +2. Create a docker container ```bash docker run -it 4pdosc/hybridsql:0.8 bash ``` -3. Download the OpenMLDB source code inside the docker container, and setting the branch into v0.8.3 +3. Download the OpenMLDB source code inside the docker container, and set the branch into v0.8.4 ```bash cd ~ - git clone -b v0.8.3 https://github.com/4paradigm/OpenMLDB.git + git clone -b v0.8.4 https://github.com/4paradigm/OpenMLDB.git ``` 4. Compile OpenMLDB @@ -41,52 +39,49 @@ Keep in mind that you should always use the same version of both compile image a make install ``` -Now you've finished the compilation job, and you may try run OpenMLDB inside the docker container. +Now you've finished the compilation job, you may try running OpenMLDB inside the docker container. -## 2. Detailed Instructions for Build +## Detailed Instructions for Build -[build]: build +This chapter discusses compiling source code without relying on pre-built container environments. -### 2.1. Hardware Requirements +### Hardware Requirements - **Memory**: 8GB+ recommended. - **Disk Space**: >=25GB of free disk space for full compilation. - **Operating System**: CentOS 7, Ubuntu 20.04 or macOS >= 10.15, other systems are not carefully tested but issue/PR welcome +- **CPU Architecture**: Currently, only x86 architecture is supported, and other architectures like ARM are not supported at the moment (please note that running x86 images on heterogeneous systems like M1 Mac is also not supported at this time). -Note: By default, the parallel build is disabled, and it usually takes an hour to finish all the compile jobs. You can enable the parallel build by tweaking the `NPROC` option if your machine's resource is enough. This will reduce the compile time but also consume more memory. For example, the following command set the number of concurrent build jobs to 4: +💡 Note: By default, the parallel build is disabled, and it usually takes an hour to finish all the compile jobs. You can enable the parallel build by tweaking the `NPROC` option if your machine's resource is enough. This will reduce the compile time but also consume more memory. For example, the following command sets the number of concurrent build jobs to 4: ```bash make NPROC=4 ``` -### 2.2. Prerequisites - -Make sure those tools are installed - +### Dependencies - gcc >= 8 or AppleClang >= 12.0.0 -- cmake 3.20 or later ( < cmake 3.24 is better) +- cmake 3.20 or later ( recommended < cmake 3.24) - jdk 8 - python3, python setuptools, python wheel - If you'd like to compile thirdparty from source, checkout the [third-party's requirement](../../third-party/README.md) for extra dependencies -### 2.3. Build and Install OpenMLDB +### Build and Install OpenMLDB Building OpenMLDB requires certain thirdparty dependencies. Hence a Makefile is provided as a convenience to setup thirdparty dependencies automatically and run CMake project in a single command `make`. The `make` command offers three methods to compile, each manages thirdparty differently: -- **Method One: Build and Run Inside Docker:** Using [hybridsql](https://hub.docker.com/r/4pdosc/hybridsql) docker image, the thirdparty is already bundled inside the image and no extra steps are required, refer to above section [Quick Start](#quick-start) -- **Method Two: Download Pre-Compiled Thirdparty:** Command is `make && make install`. It downloads necessary prebuild libraries from [hybridsql-assert](https://github.com/4paradigm/hybridsql-asserts/releases) and [zetasql](https://github.com/4paradigm/zetasql/releases). Currently it supports CentOS 7, Ubuntu 20.04 and macOS. -- **Method Three: Compile Thirdparty from Source:** This is the suggested way if the host system is not in the supported list for pre-compiled thirdparty (CentOS 7, Ubuntu 20.04 and macOS). Note that when compiling thirdparty for the first time requires extra time to finish, approximately 1 hour on a 2 core & 7 GB machine. To compile thirdparty from source, please pass `BUILD_BUNDLED=ON` to `make`: +- **Method One: Download Pre-Compiled Thirdparty:** Command is `make && make install`. It downloads necessary prebuild libraries from [hybridsql-assert](https://github.com/4paradigm/hybridsql-asserts/releases) and [zetasql](https://github.com/4paradigm/zetasql/releases). Currently it supports CentOS 7, Ubuntu 20.04 and macOS. +- **Method Two: Compile Thirdparty from Source:** This is the suggested way if the host system is not in the supported list for pre-compiled thirdparty (CentOS 7, Ubuntu 20.04 and macOS). Note that when compiling thirdparty for the first time requires extra time to finish, approximately 1 hour on a 2 core & 8 GB machine. To compile thirdparty from source, please pass `BUILD_BUNDLED=ON` to `make`: ```bash make BUILD_BUNDLED=ON make install ``` -All of the three methods above will install OpenMLDB binaries into `${PROJECT_ROOT}/openmldb` by default, you may tweak the installation directory with the option `CMAKE_INSTALL_PREFIX` (refer the following section [Extra options for `make`](#24-extra-options-for-make)). +All of the three methods above will install OpenMLDB binaries into `${PROJECT_ROOT}/openmldb` by default, you may tweak the installation directory with the option `CMAKE_INSTALL_PREFIX` (refer to the following section [Extra Parameters for `make`](#extra-parameters-for-make) ). -### 2.4. Extra Options for `make` +### Extra Parameters for `make` -You can customize the `make` behavior by passing following arguments, e.g., changing the build mode to `Debug` instead of `Release`: +You can customize the `make` behavior by passing the following arguments, e.g., changing the build mode to `Debug` instead of `Release`: ```bash make CMAKE_BUILD_TYPE=Debug @@ -132,10 +127,14 @@ make CMAKE_BUILD_TYPE=Debug Default: ON -- OPENMLDB_BUILD_TARGET: If you only want to build some targets, not all, e.g. only build a test `ddl_parser_test`, you can set it to `ddl_parser_test`. Multiple targets may be given, separated by spaces. It can reduce the build time, reduce the build output, save the storage space. +- OPENMLDB_BUILD_TARGET: If you only want to build some targets, not all, e.g. only build a test `ddl_parser_test`, you can set it to `ddl_parser_test`. Multiple targets may be given, separated by spaces. It can reduce build time, reduce build output, and save storage space. Default: all +- THIRD_PARTY_CMAKE_FLAGS: You can use this to configure additional parameters when compiling third-party dependencies. For instance, to specify concurrent compilation for each third-party project, you can set` THIRD_PARTY_CMAKE_FLAGS` to `-DMAKEOPTS=-j8`. Please note that NPROC does not affect third-party compilation; multiple third-party projects will be executed sequentially. + + Default: '' + ### Build Java SDK with Multi Processes ``` @@ -144,14 +143,14 @@ make SQL_JAVASDK_ENABLE=ON NPROC=4 The built jar packages are in the `target` path of each submodule. If you want to use the jar packages built by yourself, please DO NOT add them by systemPath(may get `ClassNotFoundException` about Protobuf and so on, requires a little work in compile and runtime phase). The better way is, use `mvn install -DskipTests=true -Dscalatest.skip=true -Dwagon.skip=true -Dmaven.test.skip=true -Dgpg.skip` to install them in local m2 repository, your project will use them. -## 3. Optimized Spark Distribution for OpenMLDB +## Optimized Spark Distribution for OpenMLDB [OpenMLDB Spark Distribution](https://github.com/4paradigm/spark) is the fork of [Apache Spark](https://github.com/apache/spark). It adopts specific optimization techniques for OpenMLDB. It provides native `LastJoin` implementation and achieves 10x~100x performance improvement compared with the original Spark distribution. The Java/Scala/Python/SQL APIs of the OpenMLDB Spark distribution are fully compatible with the standard Spark distribution. 1. Downloading the pre-built OpenMLDB Spark distribution: ```bash -wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.3/spark-3.2.1-bin-openmldbspark.tgz +wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.4/spark-3.2.1-bin-openmldbspark.tgz ``` Alternatively, you can also download the source code and compile from scratch: @@ -171,3 +170,55 @@ export SPARK_HOME=`pwd` ``` 3. Now you are all set to run OpenMLDB by enjoying the performance speedup from this optimized Spark distribution. + + +## Build for Other OS +As previously mentioned, if you want to run OpenMLDB or the SDK on a different OS, you will need to compile from the source code. We provide quick compilation solutions for several operating systems. For other OS, you'll need to perform source code compilation on your own. + +### Centos 6 or other glibc Linux OS +#### Local Compilation +To compile a version compatible with CentOS 6, you can use Docker and the `steps/centos6_build.sh` script. As shown below, we use the current directory as the mount directory and place the compilation output locally. + +```bash +git clone https://github.com/4paradigm/OpenMLDB.git +cd OpenMLDB +docker run -it -v`pwd`:/root/OpenMLDB ghcr.io/4paradigm/centos6_gcc7_hybridsql bash +``` +Execute the compilation script within the container, and the output will be in the "build" directory. If there are failures while downloading `bazel` or `icu4c` during compilation, you can use the image sources provided by OpenMLDB by configuring the environment variable `OPENMLDB_SOURCE=true`. Various environment variables that can be used with "make" will also work, as shown below. + +```bash +cd OpenMLDB +bash steps/centos6_build.sh +# THIRD_PARTY_CMAKE_FLAGS=-DMAKEOPTS=-j8 bash steps/centos6_build.sh # run fast when build single project +# OPENMLDB_SOURCE=true bash steps/centos6_build.sh +# SQL_JAVASDK_ENABLE=ON SQL_PYSDK_ENABLE=ON NPROC=8 bash steps/centos6_build.sh # NPROC will build openmldb in parallel, thirdparty should use THIRD_PARTY_CMAKE_FLAGS +``` + +For a local compilation with a 2.20GHz CPU, SSD hard drive, and 32 threads to build both third-party libraries and the OpenMLDB core, the approximate timeframes are as follows: +`THIRD_PARTY_CMAKE_FLAGS=-DMAKEOPTS=-j32 SQL_JAVASDK_ENABLE=ON SQL_PYSDK_ENABLE=ON NPROC=32 bash steps/centos6_build.sh` +- third-party (excluding source code download time): Approximately 40 minutes: + - Zetasql patch: 13 minutes + - Compilation of all third-party dependencies: 30 minutes +- OpenMLDB core, including Python and Java native components: Approximately 12 minutes + +Please note that these times can vary depending on your specific hardware and system performance. The provided compilation commands and environment variables are optimized for multi-threaded compilation, which can significantly reduce build times. + +#### Cloud Compilation + +After forking the OpenMLDB repository, you can trigger the `Other OS Build` workflow in `Actions`, and the output will be available in the `Actions` `Artifacts`. Here's how to configure the workflow: + +- Do not change the `Use workflow from` setting to a specific tag; it can be another branch. +- Choose the desired `OS name`, which in this case is `centos6`. +- If you are not compiling the main branch, provide the name of the branch, tag (e.g., v0.8.4), or SHA you want to compile in the `The branch, tag, or SHA to checkout, otherwise use the branch` field. +- The compilation output will be accessible in "runs", as shown in an example [here](https://github.com/4paradigm/OpenMLDB/actions/runs/6044951902). + - The workflow will definitely produce the OpenMLDB binary file. + - If you don't need the Java or Python SDK, you can configure `java sdk enable` or `python sdk enable` to be "OFF" to save compilation time. + +Please note that this compilation process involves building third-party dependencies from source code, and it may take a while to complete due to limited resources. The approximate time for this process is around 3 hours and 5 minutes (2 hours for third-party dependencies and 1 hour for OpenMLDB). However, the workflow caches the compilation output for third-party dependencies, so the second compilation will be much faster, taking approximately 1 hour and 15 minutes for OpenMLDB. + +### Macos 10.15, 11 + +MacOS doesn't require compiling third-party dependencies from source code, so compilation is relatively faster, taking about 1 hour and 15 minutes. Local compilation is similar to the steps outlined in the [Detailed Instructions for Build](#detailed-instructions-for-build) and does not require compiling third-party dependencies (`BUILD_BUNDLED=OFF`). For cloud compilation on macOS, trigger the `Other OS Build` workflow in `Actions` with the specified macOS version (`os name` as `macos10` or `macos11`). You can also disable Java or Python SDK compilation if they are not needed, by setting `java sdk enable` or `python sdk enable` to `OFF`. + + + diff --git a/docs/en/deploy/conf.md b/docs/en/deploy/conf.md index 11667427247..138a414fa3d 100644 --- a/docs/en/deploy/conf.md +++ b/docs/en/deploy/conf.md @@ -9,6 +9,8 @@ # If you are deploying the standalone version, you do not need to configure zk_cluster and zk_root_path, just comment these two configurations. Deploying the cluster version needs to configure these two items, and the two configurations of all nodes in a cluster must be consistent #--zk_cluster=127.0.0.1:7181 #--zk_root_path=/openmldb_cluster +# set the username and password of zookeeper if authentication is enabled +#--zk_cert=user:passwd # The address of the tablet needs to be specified in the standalone version, and this configuration can be ignored in the cluster version --tablet=127.0.0.1:9921 # Configure log directory @@ -76,6 +78,8 @@ # If you start the cluster version, you need to specify the address of zk and the node path of the cluster in zk #--zk_cluster=127.0.0.1:7181 #--zk_root_path=/openmldb_cluster +# set the username and password of zookeeper if authentication is enabled +#--zk_cert=user:passwd # Configure the thread pool size, it is recommended to be consistent with the number of CPU cores --thread_pool_size=24 @@ -218,6 +222,8 @@ # If the deployed openmldb is a cluster version, you need to specify the zk address and the cluster zk node directory #--zk_cluster=127.0.0.1:7181 #--zk_root_path=/openmldb_cluster +# set the username and password of zookeeper if authentication is enabled +#--zk_cert=user:passwd # configure log path --openmldb_log_dir=./logs @@ -249,6 +255,7 @@ zookeeper.connection_timeout=5000 zookeeper.max_retries=10 zookeeper.base_sleep_time=1000 zookeeper.max_connect_waitTime=30000 +#zookeeper.cert=user:passwd # Spark Config spark.home= diff --git a/docs/en/deploy/install_deploy.md b/docs/en/deploy/install_deploy.md index cdaf06a5d6a..332e681bbdf 100644 --- a/docs/en/deploy/install_deploy.md +++ b/docs/en/deploy/install_deploy.md @@ -52,17 +52,17 @@ If your operating system is not mentioned above or if you want to compile from s ### Linux Platform Compatibility pre-test -Due to the variations among Linux platforms, the distribution package may not be entirely compatible with your machine. Therefore, it's recommended to conduct a preliminary compatibility test. Download the pre-compiled package `openmldb-0.8.3-linux.tar.gz`, and execute: +Due to the variations among Linux platforms, the distribution package may not be entirely compatible with your machine. Therefore, it's recommended to conduct a preliminary compatibility test. Download the pre-compiled package `openmldb-0.8.4-linux.tar.gz`, and execute: ``` -tar -zxvf openmldb-0.8.3-linux.tar.gz -./openmldb-0.8.3-linux/bin/openmldb --version +tar -zxvf openmldb-0.8.4-linux.tar.gz +./openmldb-0.8.4-linux/bin/openmldb --version ``` The result should display the version number of the program, as shown below: ``` -openmldb version 0.8.3-xxxx +openmldb version 0.8.4-xxxx Debug build (NDEBUG not #defined) ``` @@ -177,9 +177,9 @@ DataCollector and SyncTool currently do not support one-click deployment. Please ### Download OpenMLDB ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -cd openmldb-0.8.3-linux +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +cd openmldb-0.8.4-linux ``` ### Environment Configuration @@ -188,7 +188,7 @@ The environment variables are defined in `conf/openmldb-env.sh`, as shown in the | Environment Variable | Default Value | Note | | --------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | -| OPENMLDB_VERSION | 0.8.3 | OpenMLDB version | +| OPENMLDB_VERSION | 0.8.4 | OpenMLDB version | | OPENMLDB_MODE | standalone | standalone or cluster | | OPENMLDB_HOME | root directory of the release folder | openmldb root directory | | SPARK_HOME | $OPENMLDB_HOME/spark | openmldb spark root directory,If the directory does not exist, it will be downloaded automatically.| @@ -361,10 +361,10 @@ Note that at least two TabletServer need to be deployed, otherwise errors may oc **1. Download the OpenMLDB deployment package** ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-tablet-0.8.3 -cd openmldb-tablet-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-tablet-0.8.4 +cd openmldb-tablet-0.8.4 ``` **2. Modify the configuration file `conf/tablet.flags`** @@ -427,12 +427,12 @@ For clustered versions, the number of TabletServers must be 2 or more. If there' To start the next TabletServer on a different machine, simply repeat the aforementioned steps on that machine. If starting the next TabletServer on the same machine, ensure it's in a different directory, and do not reuse a directory where the TabletServer is already running. -For instance, you can decompress the package again (avoid using a directory where TabletServer is already running, as files generated after startup may be affected), and name the directory `openmldb-tablet-0.8.3-2`. +For instance, you can decompress the package again (avoid using a directory where TabletServer is already running, as files generated after startup may be affected), and name the directory `openmldb-tablet-0.8.4-2`. ``` -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-tablet-0.8.3-2 -cd openmldb-tablet-0.8.3-2 +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-tablet-0.8.4-2 +cd openmldb-tablet-0.8.4-2 ``` Modify the configuration again and start the TabletServer. Note that if all TabletServers are on the same machine, use different port numbers to avoid "Fail to listen" error in the log (`logs/tablet.WARNING`). @@ -450,10 +450,10 @@ Please ensure that all TabletServer have been successfully started before deploy **1. Download the OpenMLDB deployment package** ```` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-ns-0.8.3 -cd openmldb-ns-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-ns-0.8.4 +cd openmldb-ns-0.8.4 ```` **2. Modify the configuration file conf/nameserver.flags** @@ -498,12 +498,12 @@ You can have only one NameServer, but if you need high availability, you can dep To start the next NameServer on another machine, simply repeat the above steps on that machine. If starting the next NameServer on the same machine, ensure it's in a different directory and do not reuse the directory where NameServer has already been started. -For instance, you can decompress the package again (avoid using the directory where NameServer is already running, as files generated after startup may be affected) and name the directory `openmldb-ns-0.8.3-2`. +For instance, you can decompress the package again (avoid using the directory where NameServer is already running, as files generated after startup may be affected) and name the directory `openmldb-ns-0.8.4-2`. ``` -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-ns-0.8.3-2 -cd openmldb-ns-0.8.3-2 +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-ns-0.8.4-2 +cd openmldb-ns-0.8.4-2 ``` Then modify the configuration and start. @@ -544,10 +544,10 @@ Before running APIServer, ensure that the TabletServer and NameServer processes **1. Download the OpenMLDB deployment package** ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-apiserver-0.8.3 -cd openmldb-apiserver-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-apiserver-0.8.4 +cd openmldb-apiserver-0.8.4 ``` **2. Modify the configuration file conf/apiserver.flags** @@ -607,18 +607,18 @@ You can have only one TaskManager, but if you require high availability, you can Spark distribution: ```shell -wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.3/spark-3.2.1-bin-openmldbspark.tgz -# Image address (China):http://43.138.115.238/download/v0.8.3/spark-3.2.1-bin-openmldbspark.tgz +wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.4/spark-3.2.1-bin-openmldbspark.tgz +# Image address (China):http://43.138.115.238/download/v0.8.4/spark-3.2.1-bin-openmldbspark.tgz tar -zxvf spark-3.2.1-bin-openmldbspark.tgz export SPARK_HOME=`pwd`/spark-3.2.1-bin-openmldbspark/ ``` OpenMLDB deployment package: ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-taskmanager-0.8.3 -cd openmldb-taskmanager-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-taskmanager-0.8.4 +cd openmldb-taskmanager-0.8.4 ``` **2. Modify the configuration file conf/taskmanager.properties** diff --git a/docs/en/developer/built_in_function_develop_guide.md b/docs/en/developer/built_in_function_develop_guide.md index 3e6eaa2852a..97d00076f87 100644 --- a/docs/en/developer/built_in_function_develop_guide.md +++ b/docs/en/developer/built_in_function_develop_guide.md @@ -792,7 +792,7 @@ select date(timestamp(1590115420000)) as dt; ## 5. Document Management -Documents for all built-in functions can be found in [Built-in Functions](http://4paradigm.github.io/OpenMLDB/zh/main/reference/sql/functions_and_operators/Files/udfs_8h.html). It is a markdown file automatically generated from source, so please do not edit it directly. +Documents for all built-in functions can be found in [Built-in Functions](http://4paradigm.github.io/OpenMLDB/zh/main/reference/sql/udfs_8h.html). It is a markdown file automatically generated from source, so please do not edit it directly. - If you are adding a document for a new function, please refer to [2.2.4 Documenting Function](#224-documenting-function). - If you are trying to revise a document of an existing function, you can find source code in the files of `hybridse/src/udf/default_udf_library.cc` or `hybridse/src/udf/default_defs/*_def.cc` . diff --git a/docs/en/developer/udf_develop_guide.md b/docs/en/developer/udf_develop_guide.md index 63530ae0f1c..4c5aff6d2e1 100644 --- a/docs/en/developer/udf_develop_guide.md +++ b/docs/en/developer/udf_develop_guide.md @@ -9,7 +9,7 @@ SQL functions can be categorised into scalar functions and aggregate functions. #### 2.1.1 Naming Specification of C++ Built-in Function - The naming of C++ built-in function should follow the [snake_case](https://en.wikipedia.org/wiki/Snake_case) style. - The name should clearly express the function's purpose. -- The name of a function should not be the same as the name of a built-in function or other custom functions. The list of all built-in functions can be seen [here](../reference/sql/functions_and_operators/Files/udfs_8h.md). +- The name of a function should not be the same as the name of a built-in function or other custom functions. The list of all built-in functions can be seen [here](../reference/sql/udfs_8h.md). #### 2.1.2 The types of the built-in C++ functions' parameters should be BOOL, NUMBER, TIMESTAMP, DATE, or STRING. diff --git a/docs/en/integration/deploy_integration/airflow_provider_demo.md b/docs/en/integration/deploy_integration/airflow_provider_demo.md new file mode 100644 index 00000000000..984911a646c --- /dev/null +++ b/docs/en/integration/deploy_integration/airflow_provider_demo.md @@ -0,0 +1,145 @@ +# Airflow +We provide [Airflow OpenMLDB Provider](https://github.com/4paradigm/OpenMLDB/tree/main/extensions/airflow-provider-openmldb), which facilitates the integration of OpenMLDB with Airflow DAG. + +This specific case will undergo training and execution with Airflow's [TalkingData](https://chat.openai.com/talkingdata_demo). + +## TalkingData DAG + +To implement this workflow in Airflow, a DAG (Directed Acyclic Graph) file needs to be written. Here we use an example DAG file in [example_openmldb_complex.py](https://github.com/4paradigm/OpenMLDB/blob/main/extensions/airflow-provider-openmldb/openmldb_provider/example_dags/example_openmldb_complex.py). + +![airflow dag](images/airflow_dag.png) + +The diagram above illustrates the work process in DAG. It begins by creating a table, followed by offline data loading, feature extraction, and model training. If the model trained performs well (AUC >= 99.0), the workflow proceeds to execute deploy SQL and model serving online. Otherwise, a failure report is generated. + +In the following demonstration, you can directly import this DAG and run in Airflow. + + +## Demonstration + +We import the above DAG to perform feature computation and deployment for the TalkingData Demo, then perform real-time inference using the predict server of TalkingData Demo. + +### Preparation + +#### Download DAG + +Along with the DAG files, training scripts are also required. For convenience, we provide the [code package](https://openmldb.ai/download/airflow_demo/airflow_demo_files.tar.gz) for direct download. If you prefer to use the latest version, you can obtain it from [github example_dags](https://github.com/4paradigm/OpenMLDB/tree/main/extensions/airflow-provider-openmldb/openmldb_provider/example_dags). + +``` +wget https://openmldb.ai/download/airflow_demo/airflow_demo_files.tar.gz +tar zxf airflow_demo_files.tar.gz +ls airflow_demo_files +``` +#### Start Docker Image + +For smooth function, we recommend starting OpenMLDB using the docker image and installing Airflow within the docker container. + +Since Airflow Web requires an external port for login, the container's port must be exposed. Then map the downloaded file from the previous step to the `/work/airflow/dags` directory. This step is crucial for Airflow to load the DAGs from this folder correctly. + +``` +docker run -p 8080:8080 -v `pwd`/airflow_demo_files:/work/airflow_demo_files -it 4pdosc/openmldb:0.8.4 bash +``` + +#### Download and Install Airflow and Airflow OpenMLDB Provider +In the docker container, execute: +``` +pip3 install airflow-provider-openmldb +``` +Airflow will be downloaded as a dependency. + +#### Source Data and DAG Preparation +Copy the sample data file, named `/tmp/train_sample.csv`, to the tmp directory. Airflow DAG files and training scripts used in the DAG must also be copied to the Airflow directory. + +``` +cp /work/airflow_demo_files/train_sample.csv /tmp/ +mkdir -p /work/airflow/dags +cp /work/airflow_demo_files/example_openmldb_complex.py /work/airflow_demo_files/xgboost_train_sample.py /work/airflow/dags +``` + +### Step 1: Start OpenMLDB and Airflow +The command provided below will initiate the OpenMLDB cluster, enabling support for predict server and Airflow standalone. +``` +/work/init.sh +python3 /work/airflow_demo_files/predict_server.py --no-init > predict.log 2>&1 & +export AIRFLOW_HOME=/work/airflow +cd $AIRFLOW_HOME +airflow standalone +``` + +Airflow standalone will show username and password as shown below. + +![airflow login](images/airflow_login.png) + +In Airflow Web interface at `http://localhost:8080`, enter username and password. + +```{caution} +`airflow standalone` is a front-end program that exits with Airflow. You can exit Airflow after DAG completion to run [Step 3-Testing](#3-Testing), or place the Airflow process in the background. +``` + +### Step 2: Running DAG + +To check the status of the DAG "example_openmldb_complex" in Airflow Web, click on the DAG and select the `Code` tab, as shown below. + +![dag home](images/dag_home.png) + +In this code, you will notice the usage of `openmldb_conn_id`, as depicted in the following figure. The DAG doesn't directly employ the address of OpenMLDB; instead, it uses a connection, so you need to create a new connection with the same name. + +![dag code](images/dag_code.png) + +#### Create Connection +Click on connections in the Admin tab. +![connection](images/connection.png) + +Add the connection. +![add connection](images/add_connection.png) + +The Airflow OpenMLDB Provider is linked to the OpenMLDB API Server. Therefore, you should provide the address of the OpenMLDB API Server in this configuration, rather than the Zookeeper address. + +![connection settings](images/connection_settings.png) + +The completed connection is shown in the figure below. +![display](images/connection_display.png) + +#### Running DAG +Run the DAG to complete the training of the model, SQL deployment, and model deployment. A successful operation will yield results similar to the figure below. +![dag run](images/dag_run.png) + +### Step 3: Test + +If Airflow is currently running in the foreground within the container, you may exit the process now. The upcoming tests will not be dependent on Airflow. + +#### Online Data Import +The SQL and model deployment have been successfully executed in the Airflow DAG. However, there is currently no data in the online storage, necessitating an online data import. + +``` +curl -X POST http://127.0.0.1:9080/dbs/example_db -d'{"mode":"online", "sql":"load data infile \"file:///tmp/train_sample.csv\" into table example_table options(mode=\"append\");"}' +``` + +This import process is asynchronous, but since the data volume is small, it will be completed quickly. You can monitor the status of the import operations by using the `SHOW JOBS` command. +``` +curl -X POST http://127.0.0.1:9080/dbs/example_db -d'{"mode":"online", "sql":"show jobs"}' +``` + +#### Prediction +Execute the prediction script to make a prediction using the newly deployed SQL and model. +``` +python3 /work/airflow_demo_files/predict.py +``` +The result is as shown. +![result](images/airflow_test_result.png) + + +### Non-Interactive Testing + +Check if DAG has been successfully loaded: +``` +airflow dags list | grep openmldb +``` +Add required connection: +``` +airflow connections add openmldb_conn_id --conn-uri http://127.0.0.1:9080 +airflow connections list --conn-id openmldb_conn_id +``` +DAG test: +``` +airflow dags test example_openmldb_complex 2022-08-25 +``` diff --git a/docs/en/integration/deploy_integration/dolphinscheduler_task_demo.md b/docs/en/integration/deploy_integration/dolphinscheduler_task_demo.md new file mode 100644 index 00000000000..54ec0fbda33 --- /dev/null +++ b/docs/en/integration/deploy_integration/dolphinscheduler_task_demo.md @@ -0,0 +1,211 @@ +# DolphinScheduler + +## Introduction +In the whole process of machine learning from development to deployment, tasks including data processing, feature development, and model training demand significant time and effort. To streamline the development and deployment of AI models and simplify the overall machine-learning process, we introduce the DolphinScheduler OpenMLDB Task. It seamlessly integrates the capabilities of the feature platform into DolphinScheduler's workflow, effectively bridging feature engineering with scheduling, resulting in a comprehensive end-to-end MLOps workflow. In this article, we present a concise introduction and practical demonstration of the procedures for using the DolphinScheduler OpenMLDB Task. + +```{seealso} +For detailed information on the OpenMLDB Task, please refer to the [DolphinScheduler OpenMLDB Task Official Documentation](https://dolphinscheduler.apache.org/zh-cn/docs/3.1.5/guide/task/openmldb). +``` + +## Scenarios and Functions +### Why Develop DolphinScheduler OpenMLDB Task + +![eco](images/ecosystem.png) + +As an open-source machine learning database providing a comprehensive solution for production-level data and feature development, the key to enhancing OpenMLDB's usability and reducing usage barriers lies in upstream and downstream connectivity. As depicted in the diagram above, the ability to access the data source allows seamless data flow from DataOps into OpenMLDB. Then the generated features by OpenMLDB need to smoothly integrate with ModelOps for training. To alleviate the significant workload resulting from manual integration by developers using OpenMLDB, we have also developed the function for OpenMLDB integration into Deployment and Monitoring. In this article, we introduce the framework for integrating OpenMLDB into the DolphinScheduler workflow. The DolphinScheduler OpenMLDB Task simplifies the usage of OpenMLDB. In the meantime, OpenMLDB tasks are efficiently managed by Workflow, enabling greater automation. + +### What Can DolphinScheduler OpenMLDB Task Do + +OpenMLDB aims to expedite development launch, enabling developers to focus on the essence of their work rather than expending excessive effort on engineering implementation. By writing OpenMLDB Tasks, we can fulfill the offline import, feature extraction, SQL deployment, and online import requirements of OpenMLDB. Furthermore, we can also implement complete training and online processes using OpenMLDB in DolphinScheduler. + +![task func](images/task_func.png) + +For instance, the most straightforward user operation process we envision, as illustrated in the diagram above, involves steps 1-4: offline data import, offline feature extraction, SQL deployment, and online data import. All of these steps can be achieved by utilizing the DolphinScheduler OpenMLDB Task. + +In addition to SQL execution in OpenMLDB, real-time prediction also requires model deployment. Therefore in the following sections, we will demonstrate how to utilize the DolphinScheduler OpenMLDB Task to coordinate a comprehensive machine learning training and online deployment process, based on the TalkingData adtracking fraud detection challenge from Kaggle. Further information about the TalkingData competition can be found at [talkingdata-adtracking-fraud-detection](https://www.kaggle.com/competitions/talkingdata-adtracking-fraud-detection/discussion). + +## Demonstration +### Environment Configuration + +**Run OpenMLDB Docker Image** + +The test can be executed on macOS or Linux, and we recommend running this demo within the provided OpenMLDB docker image. In this setup, both OpenMLDB and DolphinScheduler will be launched inside the container, with the port of DolphinScheduler exposed. +``` +docker run -it -p 12345:12345 4pdosc/openmldb:0.8.4 bash +``` +```{attention} +For proper configuration of DolphinScheduler, the tenant should be set up as a user of the operating system, and this user must have sudo permissions. It is advised to download and initiate DolphinScheduler within the OpenMLDB container. Otherwise, please ensure that the user has sudo permissions. +``` + +As our current docker image does not have sudo installed, and DolphinScheduler requires sudo for running workflows, please install sudo in the container first: +``` +apt update && apt install sudo +``` + +The DolphinScheduler runs the task using sh, but in the docker, sh is `dash` as default. Thus modify it to `bash` with the following command: +``` +dpkg-reconfigure dash +``` +Enter `no`. + +**Data Preparation** + +The workflow loads data from `/tmp/train_Sample.csv ` to OpenMLDB. Thus, first download the source data to this address: +``` +curl -SLo /tmp/train_sample.csv https://openmldb.ai/download/dolphinschduler-task/train_sample.csv +``` + +**Run OpenMLDB Cluster and Predict Server** + +Run the following command in the container to start a OpenMLDB cluster: +``` +/work/init.sh +``` + +We will run a workflow that includes data import, offline training, and model deployment. The deployment of the model is done by sending the model address to the predict server. Let's begin by downloading and running the predict server in the background: +``` +cd /work +curl -SLo predict_server.py https://openmldb.ai/download/dolphinschduler-task/predict_server.py +python3 predict_server.py --no-init > predict.log 2>&1 & +``` +```{tip} +If an error occurred in the 'Online Prediction Test', please check `/work/predict.log`. +``` + +**Download and Run DolphinScheduler** + +Please note that DolphinScheduler supports OpenMLDB Task versions 3.1.3 and above. In this article, we will be using version 3.1.5, which can be downloaded from the [Official Website](https://dolphinscheduler.apache.org/zh-cn/download/3.1.5) or from a mirrored website. + +To start the DolphinScheduler standalone, follow the steps outlined in the [Official Documentation](https://dolphinscheduler.apache.org/zh-cn/docs/3.1.5/guide/installation/standalone) for more information. + +``` +# Official +curl -SLO https://dlcdn.apache.org/dolphinscheduler/3.1.5/apache-dolphinscheduler-3.1.5-bin.tar.gz +# Image curl -SLO http://openmldb.ai/download/dolphinschduler-task/apache-dolphinscheduler-dev-3.1.5-bin.tar.gz +tar -xvzf apache-dolphinscheduler-*-bin.tar.gz +cd apache-dolphinscheduler-*-bin +sed -i s#/opt/soft/python#/usr/bin/python3#g bin/env/dolphinscheduler_env.sh +./bin/dolphinscheduler-daemon.sh start standalone-server +``` + +```{hint} +In the official release version of DolphinScheduler, there is an issue with OpenMLDB Task in versions older than 3.1.3, which cannot be used directly. If you are using an older version, you can contact us to obtain a corresponding version. This problem has been resolved in versions 3.1.3 and later, making them suitable for use with the official release version. + +In other versions of DolphinScheduler, there may be a change in `bin/env/dolphinscheduler_env.sh`. If `PYTHON_HOME` does not exist in `bin/env/dolphinscheduler_env.sh`, additional configuration is required. You can modify it using the command `echo "export PYTHON_HOME=/usr/bin/python3" >>bin/env/dolphinscheduler_env.sh`. +``` + +To access the system UI, open your browser and go to the address http://localhost:12345/dolphinscheduler/ui (the default configuration allows cross-host access, but you need to ensure a smooth IP connection). The default username and password are admin/dolphinscheduler123. + +```{note} +The DolphinScheduler worker server requires the OpenMLDB Python SDK. For the DolphinScheduler standalone worker, you only need to install the OpenMLDB Python SDK locally. We have already installed it in our OpenMLDB docker image. If you are in a different environment, please install the openmldb SDK using the command `pip3 install openmldb`. +``` + +**Download Workflow Configuration** + +Workflow can be manually created, but for the purpose of simplifying the demonstration, we have provided a JSON workflow file directly, which you can download from the following link: [Click to Download](http://openmldb.ai/download/dolphinschduler-task/workflow_openmldb_demo.json). You can upload this file directly to the DolphinScheduler environment and make simple modifications (as shown in the demonstration below) to complete the entire workflow. + +Please note that the download will not be saved within the container but to the browser host you are using. The upload will be done on the web page later. + +### Run Demo + +#### Step 1: Initial Configuration + +To create a tenant in DolphinScheduler web, navigate to the tenant management interface, and fill in the required fields. Make sure to fill in **user with sudo permission**. You can use the default settings for the queue. You can use root in the docker container. + +![create tenant](images/ds_create_tenant.png) + +Bind the tenant to the user again. For simplicity, we directly bind to the admin user. Enter User Management page and click Edit Admin User. + +![bind tenant](images/ds_bind_tenant.png) + +After binding, the user status is similar as shown below. +![bind status](images/ds_bind_status.png) + +#### Step 2: Create Workflow +In DolphinScheduler, you need to create a project first, and then create a workflow within that project. + +To begin, create a test project. As shown in the following figure, click on "Create Project" and enter the project name. + +![create project](images/ds_create_project.png) + +![project](images/ds_project.png) + +Once inside the project page, you can import the downloaded workflow file. In the workflow definition tab, click on "Import Workflow". + +![import workflow](images/ds_import_workflow.png) + +After importing, the workflow table will show as follows. + +![workflow list](images/ds_workflow_list.png) + +Click on the workflow name to view the detailed content of the workflow, as shown in the following figure. + +![workflow detail](images/ds_workflow_detail.png) + +**Note**: A minor modification is required here since the task ID will change after importing the workflow. Specifically, the upstream and downstream IDs in the switch task will not exist and need to be manually modified. + +![switch](images/ds_switch.png) + +As depicted in the above figure, there are non-existent IDs in the settings of the switch task. Please modify the "branch flow" and "pre-check conditions" for successful and failed workflows to match the tasks of the current workflow. + +The correct results are shown in the following figure: + +![right](images/ds_switch_right.png) + +Once the modifications are completed, save the workflow directly. The default tenant in the imported workflow is "default," which is also **executable**. If you want to specify your own tenant, please select the tenant when saving the workflow, as shown in the following figure. + +![set tenant](images/ds_set_tenant.png) + +#### Step 3: Deploy Online Workflow + +After saving the workflow, it needs to be launched before running. Once it goes online, the run button will be activated. As illustrated in the following figure. + +![run](images/ds_run.png) + +After clicking "Run," wait for the workflow to complete. You can view the details of the workflow operation in the Workflow Instance page, as shown in the following figure. +![run status](images/ds_run_status.png) + +To demonstrate the process of a successful product launch, validation was not actually validated but returned a successful validation and flowed into the deploy branch. After running the deploy branch and successfully deploying SQL and subsequent tasks, the predict server receives the latest model. + +```{note} +If the `Failed` appears on the workflow instance, please click on the instance name and go to the detailed page to see which task execution error occurred. Double-click on the task and click on "View Log" in the upper right corner to view detailed error information. + +`load offline data`, `feature extraction`, and `load online` may display successful task execution in the DolphinScheduler, but actual task execution fails in OpenMLDB. This may lead to errors in the `train` task, where there is no source feature data to concatenate (Traceback `pd.concat`). + +When such problems occur, please query the true status of each task in OpenMLDB and run it directly using the command: `echo "show jobs;" | /work/openmldb/bin/openmldb --zk_cluster=127.0.1:2181 --zk_root_path=/openmldb --role=SQL_client`. If the status of a task is `FAILED`, please query the log of that task. The method can be found in [Task Log](../../quickstart/beginninger_mustread.md#offline). +``` + +#### Step 4: Test Online Prediction +The predict server also provides online prediction services, through `curl/predict`. You can construct a real-time request and send it to the predict server. +``` +curl -X POST 127.0.0.1:8881/predict -d '{"ip": 114904, + "app": 11, + "device": 1, + "os": 15, + "channel": 319, + "click_time": 1509960088000, + "is_attributed": 0}' +``` +The return result is as follows: + +![predict](images/ds_predict.png) + +#### Note + +If the workflow is run repeatedly, the `deploy SQL` task may fail because the deployment `demo` already exists. Please delete the deployment in the docker container before running the workflow again: +``` +/work/openmldb/bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --role=sql_client --database=demo_db --interactive=false --cmd="drop deployment demo;" +``` + +You can confirm whether the deployment has been deleted by using the following command: +``` +/work/openmldb/bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --role=sql_client --database=demo_db --interactive=false --cmd="show deployment demo;" +``` + +Restart the DolphinScheduler server (Note that restarting this will clear the metadata and require reconfiguring the environment and creating workflows): +``` +./bin/dolphinscheduler-daemon.sh stop standalone-server +./bin/dolphinscheduler-daemon.sh start standalone-server +``` + +If you want to preserve metadata, please refer to [Pseudo Cluster Deployment](https://dolphinscheduler.apache.org/zh-cn/docs/3.1.5/guide/installation/pseudo-cluster) to configure the database. diff --git a/docs/en/integration/deploy_integration/images/add_connection.png b/docs/en/integration/deploy_integration/images/add_connection.png new file mode 100644 index 00000000000..50cd41d16ff Binary files /dev/null and b/docs/en/integration/deploy_integration/images/add_connection.png differ diff --git a/docs/en/integration/deploy_integration/images/airflow_dag.png b/docs/en/integration/deploy_integration/images/airflow_dag.png new file mode 100644 index 00000000000..ad2bd6193e2 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/airflow_dag.png differ diff --git a/docs/en/integration/deploy_integration/images/airflow_login.png b/docs/en/integration/deploy_integration/images/airflow_login.png new file mode 100644 index 00000000000..03d58db49a9 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/airflow_login.png differ diff --git a/docs/en/integration/deploy_integration/images/airflow_test_result.png b/docs/en/integration/deploy_integration/images/airflow_test_result.png new file mode 100644 index 00000000000..75d4efc9c66 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/airflow_test_result.png differ diff --git a/docs/en/integration/deploy_integration/images/connection.png b/docs/en/integration/deploy_integration/images/connection.png new file mode 100644 index 00000000000..d0383aef2dc Binary files /dev/null and b/docs/en/integration/deploy_integration/images/connection.png differ diff --git a/docs/en/integration/deploy_integration/images/connection_display.png b/docs/en/integration/deploy_integration/images/connection_display.png new file mode 100644 index 00000000000..05726e821a4 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/connection_display.png differ diff --git a/docs/en/integration/deploy_integration/images/connection_settings.png b/docs/en/integration/deploy_integration/images/connection_settings.png new file mode 100644 index 00000000000..c739c61f71e Binary files /dev/null and b/docs/en/integration/deploy_integration/images/connection_settings.png differ diff --git a/docs/en/integration/deploy_integration/images/dag_code.png b/docs/en/integration/deploy_integration/images/dag_code.png new file mode 100644 index 00000000000..86f2289a0a5 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/dag_code.png differ diff --git a/docs/en/integration/deploy_integration/images/dag_home.png b/docs/en/integration/deploy_integration/images/dag_home.png new file mode 100644 index 00000000000..00a6ed33c53 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/dag_home.png differ diff --git a/docs/en/integration/deploy_integration/images/dag_run.png b/docs/en/integration/deploy_integration/images/dag_run.png new file mode 100644 index 00000000000..d072e4f8792 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/dag_run.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_bind_status.png b/docs/en/integration/deploy_integration/images/ds_bind_status.png new file mode 100644 index 00000000000..42ebeea6c90 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_bind_status.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_bind_tenant.png b/docs/en/integration/deploy_integration/images/ds_bind_tenant.png new file mode 100644 index 00000000000..74ef857d6a8 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_bind_tenant.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_create_project.png b/docs/en/integration/deploy_integration/images/ds_create_project.png new file mode 100644 index 00000000000..37920851ad3 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_create_project.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_create_tenant.png b/docs/en/integration/deploy_integration/images/ds_create_tenant.png new file mode 100644 index 00000000000..88a56fd58c0 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_create_tenant.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_import_workflow.png b/docs/en/integration/deploy_integration/images/ds_import_workflow.png new file mode 100644 index 00000000000..2d6e257143d Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_import_workflow.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_predict.png b/docs/en/integration/deploy_integration/images/ds_predict.png new file mode 100644 index 00000000000..7d2dba0f161 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_predict.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_project.png b/docs/en/integration/deploy_integration/images/ds_project.png new file mode 100644 index 00000000000..e24ea8876b3 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_project.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_run.png b/docs/en/integration/deploy_integration/images/ds_run.png new file mode 100644 index 00000000000..cd17c629b87 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_run.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_run_status.png b/docs/en/integration/deploy_integration/images/ds_run_status.png new file mode 100644 index 00000000000..68c9ff1e460 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_run_status.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_set_tenant.png b/docs/en/integration/deploy_integration/images/ds_set_tenant.png new file mode 100644 index 00000000000..d6f94bd6b08 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_set_tenant.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_switch.png b/docs/en/integration/deploy_integration/images/ds_switch.png new file mode 100644 index 00000000000..75dbb327c07 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_switch.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_switch_right.png b/docs/en/integration/deploy_integration/images/ds_switch_right.png new file mode 100644 index 00000000000..7fbbab6963d Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_switch_right.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_workflow_detail.png b/docs/en/integration/deploy_integration/images/ds_workflow_detail.png new file mode 100644 index 00000000000..b3a4c169cd1 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_workflow_detail.png differ diff --git a/docs/en/integration/deploy_integration/images/ds_workflow_list.png b/docs/en/integration/deploy_integration/images/ds_workflow_list.png new file mode 100644 index 00000000000..856bdf895be Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ds_workflow_list.png differ diff --git a/docs/en/integration/deploy_integration/images/ecosystem.png b/docs/en/integration/deploy_integration/images/ecosystem.png new file mode 100644 index 00000000000..6e767d3a2d7 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/ecosystem.png differ diff --git a/docs/en/integration/deploy_integration/images/task_func.png b/docs/en/integration/deploy_integration/images/task_func.png new file mode 100644 index 00000000000..46b20ea9591 Binary files /dev/null and b/docs/en/integration/deploy_integration/images/task_func.png differ diff --git a/docs/en/integration/deploy_integration/index.rst b/docs/en/integration/deploy_integration/index.rst new file mode 100644 index 00000000000..15bff333619 --- /dev/null +++ b/docs/en/integration/deploy_integration/index.rst @@ -0,0 +1,14 @@ +============================= +dispatch +============================= + +.. toctree:: + :maxdepth: 1 + + airflow_provider_demo + dolphinscheduler_task_demo + OpenMLDB_Byzer_taxi + + + + diff --git a/docs/en/integration/develop/images/muti.png b/docs/en/integration/develop/images/muti.png new file mode 100644 index 00000000000..a07578729d1 Binary files /dev/null and b/docs/en/integration/develop/images/muti.png differ diff --git a/docs/en/integration/develop/images/single.png b/docs/en/integration/develop/images/single.png new file mode 100644 index 00000000000..dff314ba236 Binary files /dev/null and b/docs/en/integration/develop/images/single.png differ diff --git a/docs/en/integration/develop/images/support_function.png b/docs/en/integration/develop/images/support_function.png new file mode 100644 index 00000000000..2e51dd3e119 Binary files /dev/null and b/docs/en/integration/develop/images/support_function.png differ diff --git a/docs/en/integration/develop/index.rst b/docs/en/integration/develop/index.rst new file mode 100644 index 00000000000..13e8fad3619 --- /dev/null +++ b/docs/en/integration/develop/index.rst @@ -0,0 +1,8 @@ +============================= +Develop +============================= + +.. toctree:: + :maxdepth: 1 + + jupyter_notebook \ No newline at end of file diff --git a/docs/en/integration/develop/jupyter_notebook.md b/docs/en/integration/develop/jupyter_notebook.md new file mode 100644 index 00000000000..b15488958a8 --- /dev/null +++ b/docs/en/integration/develop/jupyter_notebook.md @@ -0,0 +1,64 @@ +# Jupyter Notebook + +Jupyter Notebook offers various functionalities, such as data computation, code development, document editing, code execution, and result display, through a browser-based web page. It is currently one of the most popular and user-friendly development environments. This article introduces the seamless integration of OpenMLDB and Notebook, harnessing the functional features of OpenMLDB and the convenience of Notebook to create a fast and user-friendly machine-learning development environment. + +## Integration of the Magic Function + +The SQL magic function is an extension of Notebook that allows users to execute SQL statements directly in a Notebook cell without writing complex Python code. It also supports customized output. OpenMLDB provides a standard SQL magic function that allows users to write and run OpenMLDB-supported SQL statements directly in the Notebook. These statements are submitted to OpenMLDB for execution, and the results are previewed and displayed in the Notebook. + +### Register OpenMLDB SQL Magic Function + +To support OpenMLDB magic function in Notebook, register as follows: + + ```Python + import openmldb + db = openmldb.dbapi.connect(database='demo_db',zk='0.0.0.0:2181',zkPath='/openmldb') + openmldb.sql_magic.register(db) + ``` + +### Execute a Single SQL Statement + +Developers can use the prompt `%` to execute a single-line SQL statement, as shown in the following figure. + +![img](images/single.png) + +### Execute multiple SQL statement + +Developers can also use the prompt `%%` to write multi-line SQL statements, as shown in the following figure. + +![img](images/muti.png) + +Please note that currently, executing multiple SQL statements simultaneously within a Notebook cell is not supported. Each SQL statement needs to be executed separately in different cells. + +### Magic Function + +The SQL magic function provided by OpenMLDB can execute all supported SQL syntax, including the unique offline mode of OpenMLDB, which allows for asynchronously submitting complex big data SQL statements to the offline execution engine, as shown in the following figure. + +![img](images/support_function.png) + +For more detailed instructions on using the OpenMLDB magic function, please refer to [The Use of Notebook Magic Function](https://openmldb.ai/docs/en/main/quickstart/sdk/python_sdk.html#notebook-magic-function). + +## Integration of OpenMLDB Python SDK with Notebook + +Notebook supports the Python runtime kernel, enabling the import and usage of various Python libraries through import statements. OpenMLDB provides a fully functional Python SDK that can be called within Notebook. OpenMLDB Python SDK not only offers a DBAPI based on the Python PEP249 standard but also supports the mainstream SQLAlchemy interface, which enables connecting to existing OpenMLDB clusters with just one line of code. + +### The Use of OpenMLDB DBAPI + +Using the DBAPI interface is straightforward. You only need to specify the ZooKeeper address and node path for connection. Upon successful connection, corresponding log information will be displayed. You can call the DBAPI interface of the OpenMLDB Python SDK within Notebook for development, as detailed in [The Use of OpenMLDB DBAPI](https://openmldb.ai/docs/en/main/quickstart/sdk/python_sdk.html#openmldb-dbapi). + +```Python +import openmldb.dbapi +db = openmldb.dbapi.connect('demo_db','0.0.0.0:2181','/openmldb') +``` + +### Using OpenMLDB SQLAlchemy + +Using SQLAlchemy is also simple. You can establish the connection by specifying the URI of OpenMLDB through the SQLAlchemy library. Alternatively, you can connect to a standalone OpenMLDB database using IP and port as parameters, as shown below. + +```Python +import sqlalchemy as db +engine = db.create_engine('openmldb://demo_db?zk=127.0.0.1:2181&zkPath=/openmldb') +connection = engine.connect() +``` + +After a successful connection, development can be carried out through the SQLAlchemy interface of the OpenMLDB Python SDK, as detailed in [Using OpenMLDB SQLAlchemy](https://openmldb.ai/docs/en/main/quickstart/sdk/python_sdk.html#openmldb-sqlalchemy). diff --git a/docs/en/integration/index.rst b/docs/en/integration/index.rst new file mode 100644 index 00000000000..023bd3c9ab9 --- /dev/null +++ b/docs/en/integration/index.rst @@ -0,0 +1,13 @@ +============================= +Upstream and downstream ecology +============================= + +.. toctree:: + :maxdepth: 1 + + online_datasources/index + offline_data_sources/index + deploy_integration/index + develop/index + + diff --git a/docs/en/integration/online_datasources/index.rst b/docs/en/integration/online_datasources/index.rst new file mode 100644 index 00000000000..7b2232ef05b --- /dev/null +++ b/docs/en/integration/online_datasources/index.rst @@ -0,0 +1,13 @@ +============================= +online data source +============================= + +.. toctree:: + :maxdepth: 1 + + kafka_connector_demo + pulsar_connector_demo + rocketmq_connector + + + diff --git a/docs/en/maintain/data_export.md b/docs/en/maintain/data_export.md deleted file mode 100644 index 0916a8bc553..00000000000 --- a/docs/en/maintain/data_export.md +++ /dev/null @@ -1,58 +0,0 @@ -# Data Export Tool - -Data Export Tool locates in [src/tools](https://github.com/4paradigm/OpenMLDB/tree/main/src/tools)。It supports exporting data from remote machines in standalone mode or cluster mode. - -## 1. Build - -Generate the Unix Executable file:`make` under src folder. - -## 2. Data Export Usage - -### 2.1 Command Line Arguments - -All configurations are showed as follows, * indicates required configurations. - -``` -Usage: ./data_exporter [--delimiter=] --db_name= - [--user_name=] --table_name= - --config_path= - -* --db_name= openmldb database name -* --table_name= openmldb table name of the selected database -* --config_path= absolute or relative path of the config file - --delimiter= delimiter for the output csv, default is ',' - --user_name= user name of the remote machine -``` - -### 2.2 Important Configurations Instructions - -Descriptions of the important configurations: - -- `--db_name=`: OpenMLDB database name. The database must exist, otherwise would return an error message: database not found. -- `--table_name=`: table name. The table must exist in the selected database, otherwise would return an error message: table not found. -- `--config_path= kDoing => kDone: Indicates successful command execution. +2. kInited => kDoing => kFailed: Denotes a failed command execution. +3. kInited => kCancelled: This state may arise after manually executing the `cancelop` command. + +Once the command running status changes to `kDone`, it signifies the successful execution of relevant commands. You can then proceed to the subsequent steps and use the `showtablestatus` command to inspect the status of tables. + +## Step 2: View Table Status with `showtablestatus` + +After successfully executing the relevant maintenance commands, it's crucial to perform an additional verification to identify any anomalies in the table status. This verification can be conducted using the `showtablestatus` command within the [OpenMLDB Operations and Maintenance Tool](./openmldb_ops.md). For instance: + +```bash +python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --cmd=showtablestatus +``` + +Upon execution, this command will generate a series of table-related information. Of particular importance is the column labeled `Partition_unalive`. If this column's value is 0, it signifies that the table is in a normal state. Conversely, any non-zero value indicates the presence of an anomaly, as illustrated in the following example: + +![image-20230113144942187](images/showtablestatus.png) diff --git a/docs/en/quickstart/beginner_must_read.md b/docs/en/quickstart/beginner_must_read.md new file mode 100644 index 00000000000..759d423b32d --- /dev/null +++ b/docs/en/quickstart/beginner_must_read.md @@ -0,0 +1,144 @@ +# Essential Reading for Getting Started + +As OpenMLDB is a distributed system with various modes and extensive client functionality, users may encounter numerous questions and operational challenges, especially when using it for the first time. This article aims to guide beginners on diagnosing and debugging issues and providing effective information when seeking technical assistance. + +## Create OpenMLDB and Connection + +To begin, we recommend that users who are not well-versed in distributed multi-process management use Docker to set up OpenMLDB. This approach offers convenience and expedites the initial learning process. Once you have become acquainted with the various components of OpenMLDB, you can explore distributed deployment options. + +You can create an OpenMLDB instance using Docker by following the instructions in the [Quickstart guide](./openmldb_quickstart.md). Please note that the guide presents two versions: standalone and cluster. Ensure clarity regarding the version you intend to create and avoid mixed usage. + +A successful startup is indicated by your ability to connect to the OpenMLDB server using the CLI (Command Line Interface). In both standalone and cluster setups, you can use `/work/openmldb/bin/openmldb` to connect to OpenMLDB and execute `show components;` to check the running status of OpenMLDB server components. + +If you encounter difficulties connecting via the CLI, first verify whether the processes are running as expected. You can confirm the presence of nameserver and tablet server processes using `ps f | grep bin/openmldb`. In the case of the cluster setup, ensure that the ZooKeeper service is running by using `ps f | grep zoo.cfg`, and confirm the existence of the taskmanager process with `ps f | grep TaskManagerServer`. + +If all service processes are running correctly, but the CLI still cannot connect to the server, double-check the parameters for CLI operation. If issues persist, don't hesitate to contact us and provide the error information from the CLI. + +```{seealso} +If further configuration and server logs from OpenMLDB are required, you can use diagnostic tools to obtain them, as detailed in the [section below](#provide-configuration-and-logs-for-technical-support). +``` + +## Source Data + +### LOAD DATA + +When importing data from a file into OpenMLDB, the typical command used is `LOAD DATA`. For detailed information, please refer to [LOAD DATA INFILE](../openmldb_sql/dml/LOAD_DATA_STATEMENT.md). The data sources and formats that can be employed with `LOAD DATA` are contingent on several factors, including the OpenMLDB version (standalone or cluster), execution mode, and import mode (i.e., the `LOAD DATA` configuration item, `load_mode`). Specifically: + +In cluster version, the default `load_mode` is "cluster", and it can be set to "local". In standalone version, the default `load_mode` is "local," and **"cluster " is not supported**. Consequently, we discuss these scenarios in three distinct contexts: + +| LOAD DATA Type | Support execution mode (import to destination) | Support asynchronous/ synchronous | Support data sources | Support data formats | +| :-------------------------------- | :--------------------------------------------- | :--------------------------------- | :----------------------------------------------------------- | :--------------------------------------------- | +| Cluster version load_mode=cluster | Online, offline | Asynchronous, synchronous | File (with conditional restrictions, please refer to specific documentation) /hdfs/ hive | CSV/ parquet (HIVE source unrestricted format) | +| Cluster version load_mode=local | Online | Synchronous | Client local file | csv | +| Standalone version(only local) | Online | Synchronous | Client local file | csv | + +When the source data for `LOAD DATA` is in CSV format, it's essential to pay special attention to the timestamp column's format. Timestamps can be in "int64" format (referred to as int64 type) or "yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]" format (referred to as date type). + +| LOAD DATA Type | Support int64 type | Support date type | +| :-------------------------------- | :------------ | :----------- | +| Cluster version load_mode=cluster | **``✓``** | **``✓``** | +| Cluster version load_mode=local | **``✓``** | **``X``** | +| Standalone version(only local) | **``✓``** | **``X``** | + +```{hint} +The CSV file format can be inconvenient in certain situations, and we recommend considering the use of the Parquet format. This approach requires the OpenMLDB cluster version to be in use and necessitates the taskmanager component to be up and running. +``` + +## SQL Restriction + +OpenMLDB does not offer full compatibility with standard SQL, which means that certain SQL queries may not yield the expected results. If you encounter a situation where the SQL execution does not align with your expectations, it's advisable to initially verify whether the SQL adheres to the [Functional Boundary](./function_boundary.md) guidelines. + +## SQL Execution + +All commands within OpenMLDB are SQL-based. If you experience SQL execution failures or other issues (where it's unclear whether the command was executed successfully), consider the following checks: + +1. **SQL Accuracy**: Examine whether there are errors in the SQL syntax. Syntax errors can lead to unsuccessful SQL execution. You can refer to the [SQL Reference](../../openmldb_sql/) to correct any errors. +2. **Execution Status**: Determine if the command has progressed to the execution phase or if it failed to execute. This distinction is crucial for troubleshooting. + +For instance, if you encounter a syntax error prompt, it indicates a problem with the SQL writing, and you should consult the [SQL Reference](../../openmldb_sql/) for guidance on correcting it. + +``` +127.0.0.1:7527/db> create table t1(c1 int; +Error: Syntax error: Expected ")" or "," but got ";" [at 1:23] +create table t1(c1 int; + ^ +``` + +If the command has entered the execution phase but fails or experiences interaction issues, you should clarify the following details: + +- **OpenMLDB Version**: Is OpenMLDB being used in standalone mode or clustered mode? +- **Execution Mode**: What is the execution mode? You can use the `show variable` command in the CLI to retrieve this information. Note that the execution mode of the standalone version is not meaningful. + +Special attention to usage logic is required when working with the cluster version of OpenMLDB. + +### Cluster Version SQL Execution + +#### Offline + +For cluster offline commands, when operating in the default asynchronous mode, sending the command will yield a job ID as a return value. You can utilize `show job ` to inquire about the execution status of the job. + +If the offline job is an asynchronous SELECT query (without saving results), the results will not be displayed on the client (synchronous SELECT queries do display results). Instead, you can retrieve the results through `show joblog `, which comprises two sections: `stdout` and `stderr`. `stdout` contains the query results, while `stderr` contains the job's runtime log. If you discover that the job has failed or its status doesn't align with your expectations, it's essential to carefully review the job run log. + +```{note} +The location of these logs is determined by the `job.log.path` configuration in taskmanager.properties. If you have altered this configuration, you will need to search in the specified destination for the logs. By default, the stdout log can be found at `/work/openmldb/taskmanager/bin/logs/job_x.log`, while the job execution log is situated at `/work/openmldb/taskmanager/bin/logs/job_x_error.log` (note the "error" suffix). + +If the task manager operates in YARN mode rather than local mode, the information in job_x_error.log may be more limited, and detailed error information about the job might not be available. In such instances, you will need to employ the YARN app ID recorded in job_x_error.log to access the actual error details within the YARN system. +``` + +#### Online + +In the online mode of the cluster version, we typically recommend using the `DEPLOY` command to create a deployment, and access to APIServer through HTTP for real-time feature computations. Performing a SELECT query directly online, with CLI or other clients is referred to as "online preview." It's essential to be aware that online preview comes with several limitations, which are outlined in detail in the [Functional Boundary - Cluster Version Online Preview Mode](../function_boundary.md#cluster-version-online-preview-mode) document. Please avoid executing unsupported SQL queries in this context. + +### Provide Replication Scripts + +If you find yourself unable to resolve an issue through self-diagnosis, please provide us with a replication script. A comprehensive replication script should include the following components: + +``` +create database db; +use db; +-- create your table +create table xx (); + +-- offline or online +set @@execute_mode=''; + +-- load data or online insert +-- load data infile '' into table xx; +-- insert into xx values (),(); + +-- query / deploy ... + +``` + +If your question necessitates data for replication, please include the data. For offline data that doesn't support inserting offline, kindly provide a CSV or Parquet data file. If it pertains to online data, you can either provide a data file or directly insert it within the script. + +These data scripts should be capable of executing SQL commands in bulk by using redirection symbols. + +``` +/work/openmldb/bin/openmldb --host 127.0.0.1 --port 6527 < reproduce.sql +/work/openmldb/bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --role=sql_client < reproduce.sql +``` + +Ensure that the replication script is functional locally to reproduce the issue, and then document the issue or forward it to us for further assistance. + +```{caution} +Please be aware that offline jobs default to asynchronous mode. If you intend to import and query offline, remember to set it to synchronous mode. For additional information, consult [Offline Command Configuration Details](../openmldb_sql/ddl/SET_STATEMENT.md#offline-command-configuration-details). Without this adjustment, querying before the import is completed will not yield meaningful results. +``` + +## Provide Configuration and Logs for Technical Support + +If your SQL execution issue cannot be replicated through replication scripts or if it's not related to SQL execution but rather a cluster management problem, we kindly request that you provide us with configuration details and logs from both the client and server for further investigation. + +Whether you are using Docker or a local cluster setup (where all processes are on the same server), you can swiftly gather configuration, log files, and other information using diagnostic tools. + +You can initiate the OpenMLDB server using either the `init.sh`/`start-all.sh` command for clustered versions or the `init.sh standalone`/`start-standalone.sh` command for standalone versions. After starting the server, you can employ the following commands, which correspond to clustered and standalone versions, respectively. + +``` +openmldb_tool --env=onebox --dist_conf=cluster_dist.yml +openmldb_tool --env=onebox --dist_conf=standalone_dist.yml +``` +`cluster_dist.yml` and `stadnalone_dist.yml` can be found in the `/work/` directory within the Docker container. Alternatively, you can copy the yml file from the [GitHub directory](https://github.com/4paradigm/OpenMLDB/tree/main/demo) for your use. + +If you are working with a distributed cluster, it's essential to have SSH password-free configuration in place for smooth usage of the diagnostic tools. Please refer to the [Diagnostic Tool documentation](../maintain/diagnose.md) for guidance on setting this up. + +If your environment doesn't allow for SSH password-free configuration, please manually collect the configuration details and logs as needed. diff --git a/docs/en/quickstart/cli_tutorial.md b/docs/en/quickstart/cli.md similarity index 100% rename from docs/en/quickstart/cli_tutorial.md rename to docs/en/quickstart/cli.md diff --git a/docs/en/quickstart/concepts/images/modes-flow.png b/docs/en/quickstart/concepts/images/modes-flow.png new file mode 100644 index 00000000000..361353de760 Binary files /dev/null and b/docs/en/quickstart/concepts/images/modes-flow.png differ diff --git a/docs/en/quickstart/concepts/images/modes-request.png b/docs/en/quickstart/concepts/images/modes-request.png new file mode 100644 index 00000000000..f7dd94e5759 Binary files /dev/null and b/docs/en/quickstart/concepts/images/modes-request.png differ diff --git a/docs/en/quickstart/concepts/index.rst b/docs/en/quickstart/concepts/index.rst index d02cca2378f..27542f7f2f7 100644 --- a/docs/en/quickstart/concepts/index.rst +++ b/docs/en/quickstart/concepts/index.rst @@ -5,4 +5,4 @@ Concept .. toctree:: :maxdepth: 1 - workflow + modes diff --git a/docs/en/quickstart/concepts/workflow.md b/docs/en/quickstart/concepts/modes.md similarity index 79% rename from docs/en/quickstart/concepts/workflow.md rename to docs/en/quickstart/concepts/modes.md index 2ce5c58ff19..d27f33ab001 100644 --- a/docs/en/quickstart/concepts/workflow.md +++ b/docs/en/quickstart/concepts/modes.md @@ -6,7 +6,7 @@ OpenMLDB supports different execution modes at different stages of the feature e The following diagram illustrates the typical process of using OpenMLDB for feature engineering development and deployment, as well as the execution modes used in the process: -![image-20220310170024349](https://openmldb.ai/docs/zh/main/_images/modes-flow.png) +![image-20220310170024349](images/modes-flow.png) 1. Offline Data Import: Import offline data for offline feature engineering development and debugging. 2. Offline Feature Development: Develop feature engineering scripts and debug them until satisfactory results are achieved. This step involves joint debugging of machine learning models (such as XGBoost, LightGBM, etc.), but this article mainly focuses on feature engineering development related to OpenMLDB. @@ -16,57 +16,54 @@ The following diagram illustrates the typical process of using OpenMLDB for feat 6. Online Data Preview (optional): Preview and check online data using supported SQL commands. This step is not mandatory. 7. Real-time Feature Calculation: After the feature scheme is deployed and the data is correctly accessed, a real-time feature calculation service that can respond to online requests will be obtained. -## Overview of execution mode +## Overview of Execution Mode -As the data objects for offline and online scenarios are different, their underlying storage and computing nodes are also different. Therefore, OpenMLDB provides several built-in execution modes to support completing the above steps. The following table summarizes the execution modes and development tools used for each step, and three execution modes will be discussed in detail later. +As the data objects for offline and online scenarios are different, their underlying storage and computing nodes are also different. Therefore, OpenMLDB provides several built-in execution modes to support the above steps. The following table summarizes the execution modes and development tools used for each step, and three execution modes will be discussed in detail later. | Steps | Execution Mode | Development Tool | | ------------------------------ | ------------------- | ------------------------------------------------------------ | | 1. Offline Data Import | Offline Mode | OpenMLDB CLI, SDKs | -| Offline Feature Development | Offline Mode | OpenMLDB CLI, SDKs | -| Feature Deployment | Offline Mode | OpenMLDB CLI, SDKs | -| Cold Start Online Data Import | Online Preview Mode | OpenMLDB CLI, SDKs, [Data Import Tool](https://openmldb.ai/docs/zh/main/tutorial/data_import.html) | -| Real-time Data Integration | Online Preview Mode | Connectors, SDKs | -| Online Data Preview (optional) | Online Preview Mode | OpenMLDB CLI, SDKs, [Data Export Tool](https://openmldb.ai/docs/zh/main/tutorial/data_export.html) | -| Real-time Feature Calculation | Online Request Mode | CLI (REST APIs), SDKs | +| 2. Offline Feature Development | Offline Mode | OpenMLDB CLI, SDKs | +| 3. Feature Deployment | Offline Mode | OpenMLDB CLI, SDKs | +| 4. Cold Start Online Data Import | Online Preview Mode | OpenMLDB CLI, SDKs, [Data Import Tool](../../tutorial/data_import.md) | +| 5. Real-time Data Integration | Online Preview Mode | Connectors, SDKs | +| 6. Online Data Preview (optional) | Online Preview Mode | OpenMLDB CLI, SDKs, [Data Export Tool](../../tutorial/data_export.md) | +| 7. Real-time Feature Calculation | Online Request Mode | CLI (REST APIs), SDKs | ### Offline Mode -After starting OpenMLDB CLI, the **default mode is offline mode**. Offline data import, offline feature development, and feature deployment are all executed in offline mode. The purpose of offline mode is to manage and compute offline data. The computing nodes involved are supported by OpenMLDB Spark optimized for feature engineering, and the storage nodes support commonly used storage systems such as HDFS. +After starting OpenMLDB CLI, the **default mode is offline mode**. Offline data import, offline feature development, and feature deployment are all executed in offline mode. The purpose of offline mode is to manage and compute offline data. The computing nodes involved are supported by [OpenMLDB Spark Distribution](../../tutorial/openmldbspark_distribution.md) optimized for feature engineering, and the storage nodes support commonly used storage systems such as HDFS. Offline mode has the following main features: -- The offline mode supports most of the SQL syntax provided by OpenMLDB, including complex SQL syntaxes such as `LAST JOIN` and `WINDOW UNION`, which are optimized for feature engineering. - -- In offline mode, some SQL commands are executed asynchronously, such as `LOAD DATA`, `SELECT`, and `SELECT INTO` commands. Other SQL commands are executed synchronously. - +- The offline mode supports most of the SQL syntax provided by OpenMLDB, including complex SQL syntax such as `LAST JOIN` and `WINDOW UNION`. +- In offline mode, some SQL commands are executed asynchronously, such as `LOAD DATA`, `SELECT`, and `SELECT INTO`. Other SQL commands are executed synchronously. - The asynchronous SQL is managed by the internal TaskManager and can be viewed and managed through commands such as `SHOW JOBS`, `SHOW JOB`, and `STOP JOB`. -```{tip} -::: +:::{tip} Unlike many relational database systems, the `SELECT` command in offline mode is executed asynchronously by default. If you need to set it to synchronous execution, refer to setting the command to run synchronously in offline mode. During offline feature development, if asynchronous execution is used, it is strongly recommended to use the `SELECT INTO` statement for development and debugging, which can export the results to a file for easy viewing. ::: -``` -The `DEPLOY` command for feature deployment is also executed in offline mode. Its specification can refer to the OpenMLDB SQL online specification and requirements. + +The `DEPLOY` command for feature deployment is also executed in offline mode. Its specification can refer to the [OpenMLDB SQL online specification and requirements](../../openmldb_sql/deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md). Offline mode setting command (OpenMLDB CLI): `SET @@execute_mode='offline'`. -### Online preview mode +### Online Preview Mode Cold start online data import, real-time data access, and online data preview are executed in online preview mode. The purpose of the online preview mode is to manage and preview online data. Storage and computation of online data are supported by the tablet component. The main features of the online preview mode are: - `LOAD DATA`, used for online data import, can be done either locally (load_mode='local') or on the cluster (load_mode='cluster'). Local import is synchronous, while cluster import is asynchronous (same as in offline mode). Other operations are synchronous. -- Online preview mode is mainly used for previewing limited data. Selecting and viewing data directly through SELECT in OpenMLDB CLI or SDKs may result in data truncation. If the data volume is large, it is recommended to use an [export tool](https://openmldb.ai/docs/zh/main/tutorial/data_export.html) to view the complete data. -- SELECT statements in online preview mode currently do not support more complex queries such as `LAST JOIN` and `ORDER BY`. Refer to [SELECT](https://openmldb.ai/docs/zh/main/openmldb_sql/dql/SELECT_STATEMENT.html). +- Online preview mode is mainly used for previewing limited data. Selecting and viewing data directly through SELECT in OpenMLDB CLI or SDKs may result in data truncation. If the data volume is large, it is recommended to use an [export tool](../../tutorial/data_export.html) to view the complete data. +- SELECT statements in online preview mode currently do not support more complex queries such as `LAST JOIN` and `ORDER BY`. Refer to [SELECT](../../openmldb_sql/dql/SELECT_STATEMENT.html). - The server in the online preview mode executes SQL statements on a single thread. For large data processing, it may be slow and may trigger a timeout. To increase the timeout period, the `--request_timeout` can be configured on the client. -- To prevent impact on online services, online preview mode limits the maximum number of accessed records and the number of different keys. This can be configured using `--max_traverse_cnt` and `--max_traverse_key_cnt`. Similarly, the maximum result size can be set using `--scan_max_bytes_size`. For detailed configuration, refer to the configuration file. +- To prevent impact on online services, online preview mode limits the maximum number of accessed records and the number of different keys. This can be configured using `--max_traverse_cnt` and `--max_traverse_key_cnt`. Similarly, the maximum result size can be set using `--scan_max_bytes_size`. For detailed configuration, refer to the [configuration file](../../deploy/conf.md). The command for setting online preview mode in OpenMLDB CLI: `SET @@execute_mode='online'` -### Online request mode +### Online Request Mode After deploying feature scripts and accessing online data, the real-time feature computing service is ready to use, and real-time feature extraction can be performed through the online request mode. REST APIs and SDKs support the online request mode. The online request mode is a unique mode in OpenMLDB that supports real-time online computing and is very different from common SQL queries in databases. @@ -78,7 +75,7 @@ The online request mode requires three inputs: Based on the above inputs, for each real-time request row, the online request mode will return a feature extraction result. The computing logic is as follows: The request row is virtually inserted into the correct position of the online data table based on the logic in the SQL script (such as `PARTITION BY`, `ORDER BY`, etc.), and then only the feature aggregation computing is performed on that row, returning the unique corresponding extraction result. The following diagram intuitively explains the operation process of the online request mode. -![modes-request](https://openmldb.ai/docs/zh/main/_images/modes-request.png) +![modes-request](images/modes-request.png) Online request mode is supported in the following ways: diff --git a/docs/en/quickstart/function_boundary.md b/docs/en/quickstart/function_boundary.md new file mode 100644 index 00000000000..9c2c0b7ae14 --- /dev/null +++ b/docs/en/quickstart/function_boundary.md @@ -0,0 +1,165 @@ +# Functional Boundary + +This article will introduce the functional boundary of OpenMLDB SQL. + +```{note} +If you have any questions about SQL statements, please refer to OpenMLDB SQL or directly use the search function to search. +``` + +## System Configuration - TaskManager + +You can configure the TaskManager to define various settings, including the offline storage address (`offline.data.prefix`) and the Spark mode required for offline job computation (`spark.master`), among others. + +- `offline.data.prefix`: This can be configured as either a file path or an HDFS path. It is recommended to use an HDFS path for production environments, while a local file path can be configured for testing environments (specifically for onebox, such as running within a Docker container). Note that using a file path as offline storage will not support distributed deployment with multiple Task Managers (data won't be transferred between Task Managers). If you plan to deploy Task Managers on multiple hosts, please use storage media like HDFS that can be accessed simultaneously by multiple hosts. If you intend to test the collaboration of multiple Task Managers, you can deploy multiple Task Managers on a single host and use a file path as offline storage. +- `spark.master=local[*]`: The default Spark configuration is in `local[*]` mode, which automatically binds CPU cores. If offline tasks are found to be slow, it is recommended to use the Yarn mode. After changing the configuration, you need to restart the Task Manager for the changes to take effect. For more configurations, please refer to [master-urls](https://spark.apache.org/docs/3.1.2/submitting-applications.html#master-urls). + +### spark.default.conf + +More optional configurations can be written in the `spark.default.conf` parameter in the format of `k1=v1;k2=v2`. For example: + +```Plain +spark.default.conf=spark.port.maxRetries=32;foo=bar +``` + +`spark.port.maxRetries`: The default is set to 16, and you can refer to [Spark Configuration](https://spark.apache.org/docs/3.1.2/configuration.html). Each offline job is associated with a Spark UI, corresponding to a port. Each port starts from the default initial port and increments by one for retry attempts. If the number of concurrently running jobs exceeds `spark.port.maxRetries`, the number of retries will also exceed `spark.port.maxRetries`, causing job startup failures. If you need to support a larger job concurrency, configure a higher value for `spark.port.maxRetries` and restart the Task Manager to apply the changes. + +## DDL Boundary - DEPLOY Statement + +You can deploy an online SQL solution using the `DEPLOY ` command. This operation automatically parses the SQL statement and helps create indexes (you can view index details using `DESC `). For more information, please refer to the [DEPLOY STATEMENT](../openmldb_sql/deployment_manage/DEPLOY_STATEMENT.md) documentation. + +The success of the deployment operation is dependent on the presence of online data in the table. + +### Long Window SQL + +Long Window SQL: This refers to the `DEPLOY` statement with the `OPTIONS(long_windows=...)` configuration item. For syntax details, please refer to [Long Window](../openmldb_sql/deployment_manage/DEPLOY_STATEMENT.md#long-window-optimazation). Deployment conditions for long-window SQL are relatively strict, and it's essential to ensure that the tables used in the SQL statements do not contain online data. Otherwise, even if deploying SQL that matches the previous one, the operation will still fail. + +### Normal SQL + +- If the relevant index already exists before deployment, the `DEPLOY` operation will not create the index. The `DEPLOY` operation will succeed regardless of whether there is online data in the table. +- If a new index needs to be created during deployment, and there is already online data in the table, the `DEPLOY` operation will fail. + +There are two solutions: + +1. Strictly perform `DEPLOY` before importing online data, and avoid executing `DEPLOY` after online data is present in the table. +2. The `CREATE INDEX` statement can automatically import existing online data (data from existing indexes) when creating a new index. If it is necessary to execute `DEPLOY` when the table already has online data, you can manually execute a `CREATE INDEX` to create the required index (the new index will already have data), and then execute `DEPLOY` (in this case, `DEPLOY` will not create a new index, and the manually created indexes will be used directly for computation). + +```{note} +How can you determine which indexes to create? + +Currently, only the Java SDK supports this feature, and all the required indexes can be obtained through `SqlClusterExecutor.genDDL`. However, you will need to manually convert them into `CREATE INDE`X statements as `genDD`L provides table creation statements. In the future, it will support ** directly obtaining index creation statements** or **automatically importing data into a new index** with `DEPLOY`. +``` + +## DML Boundary + +### Offline Information + +There are two types of paths in the offline information of a table: `offline_path` and `symbolic_paths`. `offline_path` is the actual storage path for offline data, while `symbolic_paths` are soft link paths for offline data. Both paths can be modified using the `LOAD DATA` command, and `symbolic_paths` can also be modified using the `ALTER` statement. + +The key difference between `offline_path` and `symbolic_paths` is that `offline_path` is the path owned by the OpenMLDB cluster. If a hard copy is implemented, data will be written to this path. On the other hand, `symbolic_paths` are paths outside the OpenMLDB cluster, and soft copies will add a path to this information. When querying offline, data from both paths will be loaded. Both paths use the same format and read options and do not support paths with different configurations. + +Therefore, if `offline_path` already exists offline, the `LOAD DATA` command can only modify `symbolic_paths`. If `symbolic_paths` already exist offline, the `LOAD DATA` command can be used to modify both `offline_path` and `symbolic_paths`. + +The `errorifexists` option will raise an error if there is offline information in the table. It will raise errors if performing hard copy when there's soft links, or performing soft copy when a hard copy exisits. + +### LOAD DATA + +Regardless of whether data is imported online or offline using the `LOAD DATA` command, it is considered an offline job. The format rules for source data are the same for both offline and online scenarios. + +It is recommended to use HDFS files as source data. This approach allows for successful import whether TaskManager is in local mode, Yarn mode, or running on another host. However, if the source data is a local file, the ability to import it smoothly depends on the mode of TaskManager and the host where it is running: + +- In local mode, TaskManager can successfully import source data only if the source data is placed on the same host as the TaskManager process. +- When TaskManager is in Yarn mode (both client and cluster), a file path cannot be used as the source data address because it is not known on which host the container is running. + +### DELETE + +In tables with multiple indexes in the online storage, a `DELETE` operation may not delete corresponding data in all indexes. Consequently, there may be situations where data has been deleted, but the deleted data can still be found. + +For example: + +```SQL +create database db; +use db; +create table t1(c1 int, c2 int,index(key=c1),index(key=c2)); +desc t1; +set @@execute_mode='online'; +insert into t1 values (1,1),(2,2); +delete from t1 where c2=2; +select * from t1; +select * from t1 where c2=2; +``` + +The results are as follows: + +```Plain + --- ------- ------ ------ --------- + Field Type Null Default + --- ------- ------ ------ --------- + 1 c1 Int YES + 2 c2 Int YES + --- ------- ------ ------ --------- + --- -------------------- ------ ---- ------ --------------- + name keys ts ttl ttl_type + --- -------------------- ------ ---- ------ --------------- + 1 INDEX_0_1668504212 c1 - 0min kAbsoluteTime + 2 INDEX_1_1668504212 c2 - 0min kAbsoluteTime + --- -------------------- ------ ---- ------ --------------- + -------------- + storage_mode + -------------- + Memory + -------------- + ---- ---- + c1 c2 + ---- ---- + 1 1 + 2 2 + ---- ---- + +2 rows in set + ---- ---- + c1 c2 + ---- ---- + +0 rows in set +``` + +Explanation: + +Table `t1` has multiple indexes (which may be automatically created during `DEPLOY`). If you run `delete from t1 where c2=2`, it only deletes data in the second index, while the data in the first index remains unaffected. Therefore, if you subsequently run `select * from t1` and it uses the first index, there are two pieces of data that haven't been deleted. `select * from t1 where c2=2` uses the second index, and the result is empty, with data being successfully deleted. + +## DQL Boundary + +The supported query modes (i.e. `SELECT` statements) vary depending on the execution mode: + +| Execution Mode | Query Statement | +| -------------- | ------------------------------------------------------------ | +| Offline Mode | Batch query | +| Online Mode | Batch query (also known as online preview mode, only supports partial SQL) and request query (also known as online request mode) | + +### Online Preview Mode + +In OpenMLDB CLI, executing SQL in online mode puts it in online preview mode. Please note that online preview mode has limited support; you can refer to the [SELECT STATEMENT](../openmldb_sql/dql/SELECT_STATEMENT) documentation for more details. + +Online preview mode is primarily for previewing query results. If you need to run complex SQL queries, it's recommended to use offline mode. To query complete online data, consider using a data export tool such as the `SELECT INTO` command. Keep in mind that if the online table contains a large volume of data, it might trigger data truncation, and executing `SELECT * FROM table` could result in some data not being returned. + +Online data is usually distributed across multiple locations, and when you run `SELECT * FROM table`, it retrieves results from various Tablet Servers without performing global sorting. As a result, the order of data will be different with each execution of `SELECT * FROM table`. + +### Offline Mode and Online Request Mode + +In the [full process](./concepts/modes.md) of feature engineering development and deployment, offline mode and online request mode play prominent roles: + +- Offline Mode Batch Query: Used for offline feature generation. +- Query in Online Request Mode: Employed for real-time feature computation. + +While these two modes share the same SQL statements and produce consistent computation results, due to the use of two different execution engines (offline and online), not all SQL statements that work offline can be deployed online. SQL that can be executed in online request mode is a subset of offline executable SQL. Therefore, it's essential to test whether SQL can be deployed using `DEPLOY` after completing offline SQL development. + +## Offline Command Synchronization Mode + +All offline commands can be executed in synchronous mode using `set @@sync_job=true;`. In this mode, the command will only return after completion, whereas in asynchronous mode, job info is immediately returned, requires usage of `SHOW JOB ` to check the execution status of the job. In synchronous mode, the return values differ depending on the command. + +- DML commands like `LOAD DATA` and DQL commands like `SELECT INTO` return the ResultSet of Job Info. These results are identical to those in asynchronous mode, with the only difference being the return time. +- Normal `SELECT` queries in DQL return Job Info in asynchronous mode and query results in synchronous mode. However, support for this feature is currently incomplete, as explained in [Offline Sync Mode-select](../openmldb_sql/dql/SELECT_STATEMENT.md#offline-sync-mode-select). The results are in CSV format, but data integrity is not guaranteed, so it's not recommended to use as accurate query results. + - In the CLI interactive mode, the results are printed directly. + - In the SDK, ResultSet is returned, the query result as a string. Consequently, it's not recommended to use synchronous mode queries in the SDK and process their results. + +Synchronous mode comes with timeout considerations, which are detailed in [Configuration](../openmldb_sql/ddl/SET_STATEMENT.md#offline-command-configuaration-details). diff --git a/docs/en/quickstart/images/cli_cluster.png b/docs/en/quickstart/images/cli_cluster.png new file mode 100644 index 00000000000..8a90127e3b2 Binary files /dev/null and b/docs/en/quickstart/images/cli_cluster.png differ diff --git a/docs/en/quickstart/images/state_finished.png b/docs/en/quickstart/images/state_finished.png new file mode 100644 index 00000000000..75a6d8cc093 Binary files /dev/null and b/docs/en/quickstart/images/state_finished.png differ diff --git a/docs/en/quickstart/index.rst b/docs/en/quickstart/index.rst index aefceb8f206..aac5878eede 100644 --- a/docs/en/quickstart/index.rst +++ b/docs/en/quickstart/index.rst @@ -7,5 +7,8 @@ Quickstart openmldb_quickstart concepts/index - cli_tutorial + cli sdk/index + beginner_must_read + function_boundary + diff --git a/docs/en/quickstart/openmldb_quickstart.md b/docs/en/quickstart/openmldb_quickstart.md index 57c3c0d2e75..626d83debf5 100644 --- a/docs/en/quickstart/openmldb_quickstart.md +++ b/docs/en/quickstart/openmldb_quickstart.md @@ -1,40 +1,31 @@ # OpenMLDB Quickstart -## Basic concepts +## Basic Concepts The main use case of OpenMLDB is as a real-time feature platform for machine learning. The basic usage process is shown in the following diagram: +![modes-flow](concepts/images/modes-flow.png) -![modes-flow](https://openmldb.ai/docs/zh/main/_images/modes-flow.png) +As shown, OpenMLDB covers the feature computing process in machine learning, from offline development to real-time serving online, providing a complete process. Please refer to the documentation for [The Usage Process and Execution Mode](./concepts/modes.html) in detail. This article will demonstrate a quickstart step by step, showing the process for basic usage. -As can be seen, OpenMLDB covers the feature computing process of machine learning, from offline development to real-time request service online, providing a complete process. Please refer to the documentation for [the usage process and execution mode](https://openmldb.ai/docs/zh/main/quickstart/concepts/modes.html) in detail. This article will demonstrate a quick start and understanding of OpenMLDB step by step, following the basic usage process. +## Preparation -## The preparation - -This article is developed and deployed based on OpenMLDB CLI, and it is necessary to download the sample data and start OpenMLDB CLI first. It is recommended to use Docker image for a quick experience (Note: due to some known issues of Docker on macOS, the sample program in this article may encounter problems in completing the operation smoothly on macOS. It is recommended to run it on **Linux or Windows**). +This sample program is developed and deployed based on OpenMLDB CLI, so you need to download the sample data and start OpenMLDB CLI first. It is recommended to use Docker image for a quick experience (Note: due to some known issues of Docker on macOS, the sample program in this article may encounter problems on macOS. It is recommended to run it on **Linux or Windows**). - Docker Version: >= 18.03 -### Pulls the image +### Pull the Image Execute the following command in the command line to pull the OpenMLDB image and start the Docker container: ```bash -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` ``` {note} -After successfully starting the container, all subsequent commands in this tutorial are executed inside the container by default. If you need to access the OpenMLDB server inside the container from outside the container, please refer to the [CLI/SDK-container onebox documentation](https://openmldb.ai/docs/zh/main/reference/ip_tips.html#id3). -``` - -### Download sample data - -Execute the following command inside the container to download the sample data used in the subsequent process (**this step can be skipped for versions 0.7.0 and later**, as the data is already stored in the image): - -```bash -curl https://openmldb.ai/demo/data.parquet --output /work/taxi-trip/data/data.parquet +After successfully starting the container, all subsequent commands in this tutorial are executed inside the container by default. If you need to access the OpenMLDB server inside the container from outside the container, please refer to the [CLI/SDK-container onebox documentation](../reference/ip_tips.md#clisdk-containeronebox). ``` -### Start the server and client +### Start the Server and Client Start the OpenMLDB server: @@ -48,19 +39,19 @@ Start the OpenMLDB CLI client: /work/openmldb/bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --role=sql_client ``` -After successfully starting OpenMLDB CLI, it will be displayed as shown in the following figure: +Successful started OpenMLDB CLI will look as shown in the following figure: -![image](https://openmldb.ai/docs/zh/main/_images/cli_cluster.png) +![image](./images/cli_cluster.png) -## Use process +## OpenMLDB Process -Referring to the core concepts, the process of using OpenMLDB generally includes six steps: creating databases and tables, importing offline data, offline feature computing, deploying SQL solutions, importing online data, and online real-time feature computing. +Referring to the core concepts, the process of using OpenMLDB generally includes six steps: create database and table, import offline data, compute offline feature, deploy SQL plan, import online data, and online real-time feature compute. ```{note} Unless otherwise specified, the commands demonstrated below are executed by default in OpenMLDB CLI. ``` -### Step 1: Create database and table +### Step 1: Create Database and Table Create `demo_db` and table `demo_table1`: @@ -71,7 +62,7 @@ USE demo_db; CREATE TABLE demo_table1(c1 string, c2 int, c3 bigint, c4 float, c5 double, c6 timestamp, c7 date); ``` -### Step 2: Importing offline data +### Step 2: Import Offline Data Switch to the offline execution mode, and import the sample data as offline data for offline feature calculation. @@ -90,17 +81,21 @@ Note that the `LOAD DATA` command is an asynchronous command by default. You can - To show the task logs: SHOW JOBLOG job_id -Here, we use `SHOW JOBS` to check the task status. Please wait for the task to be successfully completed (the `state` is changed to `FINISHED`), and then proceed to the next step. +Here, we use `SHOW JOBS` to check the task status. Please wait for the task to be successfully completed ( `state` changes to `FINISHED`), and then proceed to the next step. + +![image-20220111141358808](./images/state_finished.png) + +After the task is completed, if you wish to preview the data, you can execute the `SELECT * FROM demo_table1` statement in synchronous mode by setting `SET @@sync_job=true`. However, this approach has certain limitations, which are detailed in the [Offline Command Synchronous Mode](./function_boundary.md#offline-command-synchronous-mode) section. -![image-20220111141358808](https://openmldb.ai/docs/zh/main/_images/state_finished.png) +In the default asynchronous mode, executing `SELECT * FROM demo_table1` will initiate an asynchronous task, and the results will be stored in the log files of the Spark job, making them less convenient to access. If TaskManager is in local mode, you can use `SHOW JOBLOG ` to view the query print results in the stdout section. -After the task is completed, if you want to preview the data, you can use the `SELECT * FROM demo_table1` statement. It is recommended to first set the offline command to synchronous mode (`SET @@sync_job=true`); otherwise, the command will submit an asynchronous task, and the result will be saved in the log file of the Spark task, which is less convenient to view. +The most reliable way to access the data is to use the `SELECT INTO` command to export the data to a specified directory or directly examine the storage location after importing it. ```{note} -OpenMLDB also supports importing offline data through linked soft copies, without the need for hard data copying. Please refer to the parameter `deep_copy` in the [LOAD DATA INFILE documentation](https://openmldb.ai/docs/zh/main/openmldb_sql/dml/LOAD_DATA_STATEMENT.html) for more information. +OpenMLDB also supports importing offline data through linked soft copies, without the need for hard data copying. Please refer to the parameter `deep_copy` in the [LOAD DATA INFILE Documentation](../openmldb_sql/dml/LOAD_DATA_STATEMENT.md) for more information. ``` -### Step 3: Offline feature computing +### Step 3: Compute Offline Feature Assuming that we have determined the SQL script (`SELECT` statement) to be used for feature computation, we can use the following command for offline feature computation: @@ -120,7 +115,7 @@ Note: - The `SELECT` statement is used to perform SQL-based feature extraction and store the generated features in the directory specified by the `OUTFILE` parameter as `feature_data`, which can be used for subsequent machine learning model training. -### Step 4: Deploying SQL solutions +### Step 4: Deploy SQL plan Switch to online preview mode, and deploy the explored SQL plan to online. The SQL plan is named `demo_data_service`, and the online SQL used for feature extraction needs to be consistent with the corresponding offline feature calculation SQL. @@ -131,11 +126,11 @@ USE demo_db; DEPLOY demo_data_service SELECT c1, c2, sum(c3) OVER w1 AS w1_c3_sum FROM demo_table1 WINDOW w1 AS (PARTITION BY demo_table1.c1 ORDER BY demo_table1.c6 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW); ``` -After the deployment, you can use the command `SHOW DEPLOYMENTS` to view the deployed SQL solutions. +After the deployment, you can use the command `SHOW DEPLOYMENTS` to view the deployed SQL. -### Step 5: Importing online data +### Step 5: Import Online Data -Import the downloaded sample data as online data for online feature computation in online preview mode. +Import the downloaded sample data as online data for online feature computation in online mode. ```sql -- OpenMLDB CLI @@ -161,9 +156,9 @@ Note that currently, it is required to successfully deploy the SQL plan before i The tutorial skips the step of real-time data access after importing data. In practical scenarios, as time progresses, the latest real-time data needs to be updated in the online database. This can be achieved through the OpenMLDB SDK or online data source connectors such as Kafka, Pulsar, etc. ``` -### Step 6: Online real-time feature computing +### Step 6: Online Real-Time Feature Computing -The development and deployment work based on OpenMLDB CLI is completed. Next, you can make real-time feature calculation requests in real-time request mode. First, exit OpenMLDB CLI and return to the command line of the operating system. +The development and deployment work is completed. Next, you can make real-time feature calculation requests in real-time request mode. First, exit OpenMLDB CLI and return to the command line of the operating system. ```sql -- OpenMLDB CLI @@ -176,10 +171,10 @@ According to the default deployment configuration, the http port for APIServer i http://127.0.0.1:9080/dbs/demo_db/deployments/demo_data_service \___________/ \____/ \_____________/ | | | - APIServer地址 Database名字 Deployment名字 + APIServerAddress Database Name Deployment Name ``` -Real-time requests accept input data in JSON format. Here are two examples: putting a row of data in the `input` field of the request. +Real-time requests accept input data in JSON format. Here are two examples: putting data in the `input` field of the request. **Example 1:** @@ -187,7 +182,7 @@ Real-time requests accept input data in JSON format. Here are two examples: putt curl http://127.0.0.1:9080/dbs/demo_db/deployments/demo_data_service -X POST -d'{"input": [["aaa", 11, 22, 1.2, 1.3, 1635247427000, "2021-05-20"]]}' ``` -Query the expected return result (the calculated features are stored in the `data` field): +Expected query result (the calculated features are stored in the `data` field): ```json {"code":0,"msg":"ok","data":{"data":[["aaa",11,22]]}} @@ -205,7 +200,7 @@ Expected query result: {"code":0,"msg":"ok","data":{"data":[["aaa",11,66]]}} ``` -### Description of real-time feature computing results +### Explanation of Real-Time Feature Computing Results The SQL execution for online real-time requests is different from batch processing mode. The request mode only performs SQL calculations on the data of the request row. In the previous example, it is the input of the POST request that serves as the request row. The specific process is as follows: Assuming that this row of data exists in the table `demo_table1`, and the following feature calculation SQL is executed on it: @@ -213,7 +208,7 @@ The SQL execution for online real-time requests is different from batch processi SELECT c1, c2, sum(c3) OVER w1 AS w1_c3_sum FROM demo_table1 WINDOW w1 AS (PARTITION BY demo_table1.c1 ORDER BY demo_table1.c6 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW); ``` -**The calculation logic for Example 1 is as follows:** +**The Calculation Logic for Example 1 is as Follows:** 1. Filter rows in column c1 with the value "aaa" based on the `PARTITION BY` partition of the request row and window, and sort them in ascending order by column c6. Therefore, in theory, the intermediate data table sorted by partition should be as follows. The request row is the first row after sorting. @@ -227,7 +222,7 @@ aaa 12 22 2.200000 12.300000 1636097890000 1970-01-01 ----- ---- ---- ---------- ----------- --------------- ------------ ``` -2. The window range is `2 PRECEDING AND CURRENT ROW`, so in the above table, the actual window is extracted, and the request row is the smallest row with no preceding two rows, but the window includes the current row, so the window only contains the request row. +2. The window range is `2 PRECEDING AND CURRENT ROW`. In the above table, when the actual window is extracted, the request row is the smallest row with no preceding 2 rows. Therefore the window only contains the request row. 3. For window aggregation, the sum of column c3 for the data within the window (only one row) is calculated, resulting in 22. Therefore, the output result is: ```sql @@ -238,7 +233,7 @@ aaa 11 22 ----- ---- ----------- ``` -**The calculation logic for Example 2 is as follows:** +**The Calculation Logic for Example 2 is as Follows:** 1. According to the partition of the request line and window by `PARTITION BY`, select the rows where column c1 is "aaa" and sort them in ascending order by column c6. Therefore, theoretically, the intermediate data table after partition and sorting should be as shown in the table below. The request row is the last row after sorting. @@ -252,7 +247,7 @@ aaa 11 22 1.2 1.3 1637000000000 2021-11-16 ----- ---- ---- ---------- ----------- --------------- ------------ ``` -2. The window range is `2 PRECEDING AND CURRENT ROW`, so the actual window is extracted from the above table, and the two preceding rows of the request row exist, and the current row is also included. Therefore, there are three rows of data in the window. +2. The window range is `2 PRECEDING AND CURRENT ROW`. When the actual window is extracted from the above table, the two preceding 2 rows of the request row exist, together with the current row. Therefore, there are three rows of data in the window. 3. For window aggregation, the sum of column c3 for the data within the window (three rows) is calculated, resulting in 22 + 22 + 22 = 66. Therefore, the output result is: ```sql diff --git a/docs/en/quickstart/sdk/cpp_sdk.md b/docs/en/quickstart/sdk/cpp_sdk.md deleted file mode 100644 index 59f4a284a63..00000000000 --- a/docs/en/quickstart/sdk/cpp_sdk.md +++ /dev/null @@ -1,117 +0,0 @@ -# C++ SDK - -## C++SDK package compilation and installation - -```plain -git clone git@github.com:4paradigm/OpenMLDB.git -cd OpenMLDB -make && make install -``` - -## Write user code - -The following code demonstrates the basic use of C++ SDK. openmldb_api.h and sdk/result_set.h is the header file that must be included. - -```c++ -#include -#include -#include - -#include "openmldb_api.h" -#include "sdk/result_set.h" - -int main() -{ - //Create and initialize the OpenmldbHandler object - //Stand-alone version: parameter (ip, port), such as: OpenmldbHandler handler ("127.0.0.1", 6527); - //Cluster version: parameters (ip: port, path), such as: OpenmldbHandler handler ("127.0.0.1:6527", "/openmldb"); - //Take the stand-alone version as an example. - OpenmldbHandler handler("127.0.0.1", 6527); - - // Define database name - std::time_t t = std::time(0); - std::string db = "test_db" + std::to_string(t); - - // Create SQL statement and database - std::string sql = "create database " + db + ";"; - // Execute the SQL statement. The execute() function returns the bool value. A value of true indicates correct execution - std::cout << execute(handler, sql); - - // Create SQL statement and use database - sql = "use " + db + ";"; - std::cout << execute(handler, sql); - - // Create SQL statement and create table - sql = "create table test_table (" - "col1 string, col2 bigint," - "index(key=col1, ts=col2));"; - std::cout << execute(handler, sql); - - // Create SQL statements and insert rows into the table - sql = "insert test_table values(\"hello\", 1)"; - std::cout << execute(handler, sql); - sql = "insert test_table values(\"Hi~\", 2)"; - std::cout << execute(handler, sql); - - // Basic mode - sql = "select * from test_table;"; - std::cout << execute(handler, sql); - - // Get the latest SQL execution result - auto res = get_resultset(); - // Output SQL execution results - print_resultset(res); - // The output in this example should be: - // +-------+--------+ - // | col1 | col2 | - // +-------+--------+ - // | hello | 1 | - // | Hi~ | 2 | - // +-------+---------+ - - - - // Band-parameter mode - //The position of the parameters to be filled in the SQL statement is set to "?" to express - sql = "select * from test_table where col1 = ? ;"; - // Create a ParameterRow object for filling parameters - ParameterRow para(&handler); - // Fill in parameters - para << "Hi~"; - // Execute SQL statement execute_parameterized() function returns the bool value. A value of true indicates correct execution - execute_parameterized(handler, db, sql, para); - res = get_resultset(); - print_resultset(res); - // The output in this example should be: - // +------+--------+ - // | col1 | col2 | - // +------+-------+ - // | Hi~ | 2 | - // +------+--------+ - - - // Request mode - sql = "select col1, sum(col2) over w as w_col2_sum from test_table " - "window w as (partition by test_table.col1 order by test_table.col2 " - "rows between 2 preceding and current row);"; - RequestRow req(&handler, db, sql); - req << "Hi~" << 3l; - execute_request(req); - res = get_resultset(); - print_resultset(res); - // The output in this example should be: - // +------+--------------------+ - // | col1 | w_col2_sum | - // +------+--------------------+ - // | Hi~ | 5 | - // +------+--------------------+ -} -``` - -## Compile and run - -```plain -gcc .cxx -o -lstdc++ -std=c++17 -I/include -L/lib -lopenmldbsdk -lpthread -./ -``` - diff --git a/docs/en/quickstart/sdk/cxx_sdk.md b/docs/en/quickstart/sdk/cxx_sdk.md new file mode 100644 index 00000000000..77041df9b52 --- /dev/null +++ b/docs/en/quickstart/sdk/cxx_sdk.md @@ -0,0 +1,138 @@ +# [Alpha] C++ SDK +```plain +The current functionality support of the C++ SDK is not yet complete. It is currently only recommended for development, testing, or specific use cases. It is not recommended for use in a production environment. For production use, we recommend using the Java SDK, which has the most comprehensive feature coverage and has undergone extensive testing for both functionality and performance. +``` +## C++ SDK Compilation and Installation +```plain +The C++ SDK static library is only supported on Linux systems and is not included in the standard release. If you need to use the C++ SDK library, you should compile the source code and enable the compilation option `INSTALL_CXXSDK=ON`. +``` +To compile, you need to meet the [hardware requirements](../../deploy/compile.md#hardware-requirements) and install the necessary [dependencies](../../deploy/compile.md#dependencies). +```plain +git clone git@github.com:4paradigm/OpenMLDB.git +cd OpenMLDB +make INSTALL_CXXSDK=ON && make install +``` + +## User Code + +The following code demonstrates the basic use of C++ SDK. `openmldb_api.h` and `sdk/result_set.h` is the header file that must be included. + +```c++ +#include +#include +#include + +#include "openmldb_api.h" +#include "sdk/result_set.h" + +int main() +{ + //Create and initialize the OpenmldbHandler object + //Stand-alone version: parameter (ip, port), such as: OpenmldbHandler handler ("127.0.0.1", 6527); + //Cluster version: parameters (ip: port, path), such as: OpenmldbHandler handler ("127.0.0.1:6527", "/openmldb"); + //Take the stand-alone version as an example. + OpenmldbHandler handler("127.0.0.1", 6527); + + // Define database name + std::time_t t = std::time(0); + std::string db = "test_db" + std::to_string(t); + + // Create SQL statement and database + std::string sql = "create database " + db + ";"; + // Execute the SQL statement. The execute() function returns bool. true indicates correct execution + std::cout << execute(handler, sql); + + // Create SQL statement to use database + sql = "use " + db + ";"; + std::cout << execute(handler, sql); + + // Create SQL statement to create table + sql = "create table test_table (" + "col1 string, col2 bigint," + "index(key=col1, ts=col2));"; + std::cout << execute(handler, sql); + + // Create SQL statements to insert rows into the table + sql = "insert test_table values(\"hello\", 1)"; + std::cout << execute(handler, sql); + sql = "insert test_table values(\"Hi~\", 2)"; + std::cout << execute(handler, sql); + + // Basic mode + sql = "select * from test_table;"; + std::cout << execute(handler, sql); + + // Get the latest SQL execution result + auto res = get_resultset(); + // Output SQL execution results + print_resultset(res); + // The output in this example should be: + // +-------+--------+ + // | col1 | col2 | + // +-------+--------+ + // | hello | 1 | + // | Hi~ | 2 | + // +-------+---------+ + + + + // Parameter mode + //The parameters to be filled in the SQL statement is marked as "?" + sql = "select * from test_table where col1 = ? ;"; + // Create a ParameterRow object for filling parameters + ParameterRow para(&handler); + // Fill in parameters + para << "Hi~"; + // Execute SQL statement, execute_parameterized() function returns bool. true indicates correct execution + execute_parameterized(handler, db, sql, para); + res = get_resultset(); + print_resultset(res); + // The output in this example should be: + // +------+--------+ + // | col1 | col2 | + // +------+-------+ + // | Hi~ | 2 | + // +------+--------+ + + + // Request mode + sql = "select col1, sum(col2) over w as w_col2_sum from test_table " + "window w as (partition by test_table.col1 order by test_table.col2 " + "rows between 2 preceding and current row);"; + RequestRow req(&handler, db, sql); + req << "Hi~" << 3l; + execute_request(req); + res = get_resultset(); + print_resultset(res); + // The output in this example should be: + // +------+--------------------+ + // | col1 | w_col2_sum | + // +------+--------------------+ + // | Hi~ | 5 | + // +------+--------------------+ +} +``` +## Multi-Thread +The `OpenMLDBHandler` object is not thread-safe, but the internal connection to the `SQLClusterRouter` can be used multi-threaded. You can achieve multi-threading by sharing the Router within the Handler object, which is more efficient than creating multiple independent Handler instances (each with its independent Router). However, in a multi-threaded mode, you should be cautious because interfaces without db depend on the Router's internal cache of used db, which might be modified by other threads. It's advisable to use the db interface in such cases. The following code demonstrates a method for multi-threaded usage: + +```c++ +OpenmldbHandler h1("127.0.0.1:2181", "/openmldb"); +OpenmldbHandler h2(h1.get_router()); + +std::thread t1([&](){ h1.execute("show components;"); print_resultset(h1.get_resultset());}); + +std::thread t2([&](){ h2.execute("show table status;"); print_resultset(h2.get_resultset());}); + +t1.join(); +t2.join(); +``` + +## Compile and run +You can refer to [Makefile](https://github.com/4paradigm/OpenMLDB/blob/main/demo/cxx_quickstart/Makefile) or use the command below to compile and run the sample code. + +```bash +gcc .cxx -o -lstdc++ -std=c++17 -I/include -L/lib -lopenmldbsdk -lpthread -lm -ldl -lstdc++fs + +./ +``` + diff --git a/docs/en/quickstart/sdk/go_sdk.md b/docs/en/quickstart/sdk/go_sdk.md index c30cbb2e502..4c07120a932 100644 --- a/docs/en/quickstart/sdk/go_sdk.md +++ b/docs/en/quickstart/sdk/go_sdk.md @@ -1,12 +1,14 @@ -# Go SDK - +# [Alpha] Go SDK +```plain +The current functionality support of the Go SDK is not yet complete. It is currently only recommended for development, testing, or specific use cases. It is not recommended for use in a production environment. For production use, we recommend using the Java SDK, which has the most comprehensive feature coverage and has undergone extensive testing for both functionality and performance. +``` ## Requirement - OpenMLDB version: >= v0.6.2 -- Deploy and run APIServer (refer to [APIServer deployment](https://openmldb.ai/docs/zh/main/deploy/install_deploy.html#apiserver) document) +- Deploy and run APIServer (refer to [APIServer deployment](../../main/deploy/install_deploy.html#apiserver) document) -## Go SDK package installment +## Go SDK installation ```bash go get github.com/4paradigm/OpenMLDB/go @@ -76,7 +78,7 @@ import ( "context" "database/sql" - // 加载 OpenMLDB SDK + // Load OpenMLDB SDK _ "github.com/4paradigm/OpenMLDB/go" ) diff --git a/docs/en/quickstart/sdk/index.rst b/docs/en/quickstart/sdk/index.rst index 2eec974bee0..d932b7f5442 100644 --- a/docs/en/quickstart/sdk/index.rst +++ b/docs/en/quickstart/sdk/index.rst @@ -7,6 +7,6 @@ SDK java_sdk python_sdk - rest_api go_sdk - cpp_sdk + cxx_sdk + rest_api \ No newline at end of file diff --git a/docs/en/quickstart/sdk/java_sdk.md b/docs/en/quickstart/sdk/java_sdk.md index a74f4c98f3c..ea06bc671db 100644 --- a/docs/en/quickstart/sdk/java_sdk.md +++ b/docs/en/quickstart/sdk/java_sdk.md @@ -1,8 +1,10 @@ # Java SDK -## Java SDK package installation +In Java SDK, the default execution mode for JDBC Statements is online, while the default execution mode for SqlClusterExecutor is offline. Please keep this in mind. -- Installing Java SDK package on Linux +## Java SDK Installation + +- Install Java SDK on Linux Configure the maven pom: @@ -10,16 +12,16 @@ com.4paradigm.openmldb openmldb-jdbc - 0.8.3 + 0.8.4 com.4paradigm.openmldb openmldb-native - 0.8.3 + 0.8.4 ``` -- Installing Java SDK package on Mac +- Install Java SDK on Mac Configure the maven pom @@ -27,25 +29,23 @@ com.4paradigm.openmldb openmldb-jdbc - 0.8.3 + 0.8.4 com.4paradigm.openmldb openmldb-native - 0.8.3-macos + 0.8.4-macos ``` -Note: Since the openmldb-native package contains the C++ static library compiled for OpenMLDB, it is defaults to the Linux static library. For macOS, the version of openmldb-native should be changed to `0.8.3-macos`, while the version of openmldb-jdbc should remain unchanged. - -The macOS version of openmldb-native only supports macOS 12. To run it on macOS 11 or macOS 10.15, the openmldb-native package needs to be compiled from source code on the corresponding OS. For detailed compilation methods, please refer to [Concurrent Compilation of Java SDK](https://openmldb.ai/docs/zh/main/deploy/compile.html#java-sdk). - -To connect to the OpenMLDB service using the Java SDK, you can use JDBC (recommended) or connect directly through SqlClusterExecutor. The following will demonstrate both connection methods in order. +Note: Since the openmldb-native package contains the C++ static library compiled for OpenMLDB, it defaults to the Linux static library. For macOS, the version of openmldb-native should be changed to `0.8.4-macos`, while the version of openmldb-jdbc remains unchanged. -## JDBC method +The macOS version of openmldb-native only supports macOS 12. To run it on macOS 11 or macOS 10.15, the openmldb-native package needs to be compiled from the source code on the corresponding OS. For detailed compilation methods, please refer to [Java SDK](../../deploy/compile.md#Build-java-sdk-with-multi-processes). +When using a self-compiled openmldb-native package, it is recommended to install it into your local Maven repository using `mvn install`. After that, you can reference it in your project's pom.xml file. It's not advisable to reference it using `scope=system`. -The connection method using JDBC is as follows: +To connect to the OpenMLDB service using the Java SDK, you can use JDBC (recommended) or connect directly through SqlClusterExecutor. The following will demonstrate both connection methods. +## Connection with JDBC ```java Class.forName("com._4paradigm.openmldb.jdbc.SQLDriver"); // No database in jdbcUrl @@ -58,10 +58,10 @@ Connection connection1 = DriverManager.getConnection("jdbc:openmldb:///test_db?z The database specified in the Connection address must exist when creating the connection. ```{caution} -he default execution mode for JDBC Connection is `online`. +The default execution mode for JDBC Connection is `online`. ``` -### Usage overview +### Statement All SQL commands can be executed using `Statement`, both in online and offline modes. To switch between offline and online modes, use command `SET @@execute_mode='...';``. For example: @@ -77,17 +77,22 @@ res = stmt.executeQuery("SELECT * from t1"); // For online mode, select or execu The `LOAD DATA` command is an asynchronous command, and the returned ResultSet contains information such as the job ID and state. You can execute `show job ` to check if the job has been completed. Note that the ResultSet needs to execute `next()` method to move the cursor to the first row of data. -It is also possible to change it to a synchronous command: +In offline mode, the default behavior is asynchronous execution, and the ResultSet returned is a Job Info. You can change this behavior to synchronous execution using `SET @@sync_job=true;`. However, please note that the ResultSet returned can vary depending on the specific SQL command. For more details, please refer to the [Function Boundary](../function_boundary.md). Synchronous execution is recommended when using `LOAD DATA` or `SELECT INTO` commands. -```SQL -SET @@sync_job=true; -``` +If synchronous commands are timing out, you can adjust the configuration as described in the [Offline Command Configuration](../../openmldb_sql/ddl/SET_STATEMENT.md). -If the actual execution time of the synchronous command exceeds the default maximum idle wait time of 0.5 hours, please [adjust the configuration](https://openmldb.ai/docs/zh/main/openmldb_sql/ddl/SET_STATEMENT.html#id4). +```{caution} +When you execute `SET @@execute_mode='offline'` on a `Statement`, it not only affects the current `Statement` but also impacts all `Statement` objects created, both existing and yet to be created, within the same `Connection`. Therefore, it is not advisable to create multiple `Statement` objects and expect them to execute in different modes. If you need to execute SQL in different modes, it's recommended to create multiple `Connection`. +``` ### PreparedStatement -`PreparedStatement` supports `SELECT`, `INSERT`, and `DELETE` operations. Note that `INSERT` only supports online insertion. +`PreparedStatement` supports `SELECT`, `INSERT`, and `DELETE`. +```{warning} +Any `PreparedStatement` executes only in the **online mode** and is not affected by the state before the `PreparedStatement` is created. `PreparedStatement` does not support switching to the offline mode. If you need to execute SQL in the offline mode, you can use a `Statement`. + +There are three types of `PreparedStatement` created by a `Connection`, which correspond to `getPreparedStatement`, `getInsertPreparedStmt`, and `getDeletePreparedStm`t in SqlClusterExecutor. +``` ```java PreparedStatement selectStatement = connection.prepareStatement("SELECT * FROM t1 WHERE id=?"); @@ -95,9 +100,10 @@ PreparedStatement insertStatement = connection.prepareStatement("INSERT INTO t1 PreparedStatement insertStatement = connection.prepareStatement("DELETE FROM t1 WHERE id=?"); ``` -## SqlClusterExecutor method +## SqlClusterExecutor +`SqlClusterExecutor` is the most comprehensive Java SDK connection method. It not only provides the basic CRUD operations that you can use with JDBC but also offers additional features like request modes and more. -### Creating a SqlClusterExecutor +### Create a SqlClusterExecutor First, configure the OpenMLDB connection parameters. @@ -108,14 +114,13 @@ option.setZkPath("/openmldb"); option.setSessionTimeout(10000); option.setRequestTimeout(60000); ``` - Then, use SdkOption to create the Executor. ```java sqlExecutor = new SqlClusterExecutor(option); ``` -`SqlClusterExecutor` execution of SQL operations is thread-safe, and in actual environments, a single `SqlClusterExecutor` can be created. However, since the execution mode (execute_mode) is an internal variable of `SqlClusterExecutor`, if you want to execute an offline command and an online command at the same time, unexpected results may occur. In this case, please use multiple `SqlClusterExecutors`. +`SqlClusterExecutor` execution of SQL operations is thread-safe, and in actual environments, a single `SqlClusterExecutor` can be created. However, since the execution mode (`execute_mode`) is an internal variable of `SqlClusterExecutor`, if you want to execute an offline command and an online command at the same time, unexpected results may occur. In this case, please use multiple `SqlClusterExecutors`. ```{caution} The default execution mode for SqlClusterExecutor is offline, which is different from the default mode for JDBC. @@ -158,7 +163,7 @@ try { } ``` -#### Executing batch SQL queries with Statement +#### Execute Batch SQL Queries with Statement Use the `Statement::execute` interface to execute batch SQL queries: @@ -200,15 +205,15 @@ try { ### PreparedStatement -`SqlClusterExecutor` can also obtain `PreparedStatement`, but you need to specify which type of `PreparedStatement` to obtain. For example, when using InsertPreparedStmt for insertion operations, there are three ways to do it. +`SqlClusterExecutor` can also obtain `PreparedStatement`, but you need to specify which type of `PreparedStatement` to obtain. For example, when using `InsertPreparedStmt` for insertion operations, there are three ways to do it. ```{note} -Insert operation only supports online mode and is not affected by execution mode. The data will always be inserted into the online database. +Any `PreparedStatement` executes exclusively in the **online mode** and is not influenced by the state of the `SqlClusterExecutor` at the time of its creation. `PreparedStatement` does not support switching to the offline mode. If you need to execute SQL in the offline mode, you can use a `Statement`. ``` #### Common Insert -1. Use the `SqlClusterExecutor::getInsertPreparedStmt(db, insertSql)` method to get the InsertPrepareStatement. +1. Use the `SqlClusterExecutor::getInsertPreparedStmt(db, insertSql)` method to get the `InsertPrepareStatement`. 2. Use the `PreparedStatement::execute()` method to execute the insert statement. ```java @@ -232,14 +237,14 @@ try { } ``` -#### Insert With Placeholder +#### Insert with Placeholder -1. Get InsertPrepareStatement by calling `SqlClusterExecutor::getInsertPreparedStmt(db, insertSqlWithPlaceHolder)` interface. -2. Use `PreparedStatement::setType(index, value)` interface to fill in data to the InsertPrepareStatement. Note that the index starts from 1. +1. Get `InsertPrepareStatement` by calling `SqlClusterExecutor::getInsertPreparedStmt(db, insertSqlWithPlaceHolder)` interface. +2. Use `PreparedStatement::setType(index, value)` interface to fill in data to the `InsertPrepareStatement`. Note that the index starts from 1. 3. Use `PreparedStatement::execute()` interface to execute the insert statement. ```{note} -When the conditions of the PreparedStatement are the same, you can repeatedly call the set method of the same object to fill in data before executing execute(). There is no need to create a new PreparedStatement object. +When the conditions of the `PreparedStatement` are the same, you can repeatedly call the set method of the same object to fill in data before executing `execute`. There is no need to create a new `PreparedStatement` object. ``` ```java @@ -266,13 +271,13 @@ try { ``` ```{note} -After execute, the cached data will be cleared and it is not possible to retry execute. +After `execute`, the cached data will be cleared and it is not possible to rerun `execute`. ``` -#### Batch Insert With Placeholder +#### Batch Insert with Placeholder -1. To use batch insert, first obtain the InsertPrepareStatement using the `SqlClusterExecutor::getInsertPreparedStmt(db, insertSqlWithPlaceHolder)` interface. -2. Then use the `PreparedStatement::setType(index, value)` interface to fill data into the InsertPrepareStatement. +1. To use batch insert, first obtain the `InsertPrepareStatement` using the `SqlClusterExecutor::getInsertPreparedStmt(db, insertSqlWithPlaceHolder)` interface. +2. Then use the `PreparedStatement::setType(index, value)` interface to fill data into the `InsertPrepareStatement`. 3. Use the `PreparedStatement::addBatch()` interface to complete filling for one row. 4. Continue to use `setType(index, value)` and `addBatch()` to fill multiple rows. 5. Use the `PreparedStatement::executeBatch()` interface to complete the batch insertion. @@ -305,12 +310,12 @@ try { ``` ```{note} -After executeBatch(), all cached data will be cleared and it's not possible to retry executeBatch(). +After `executeBatch`, all cached data will be cleared and it's not possible to rerun `executeBatch`. ``` -### Execute SQL request query +### Execute SQL Query -`RequestPreparedStmt` is a unique query mode (not supported by JDBC). This mode requires both the selectSql and a request data, so you need to provide the SQL and set the request data using setType when calling `getRequestPreparedStmt`. +`RequestPreparedStmt` is a unique query mode (not supported by JDBC). This mode requires both the selectSql and a request data, so you need to provide the SQL and set the request data using `setType` when calling `getRequestPreparedStmt`. There are three steps to execute a SQL request query: @@ -359,7 +364,7 @@ try { Assert.assertEquals(resultSet.getInt(2), 24); Assert.assertEquals(resultSet.getLong(3), 34); - // The return result set of the ordinary request query contains only one row of results. Therefore, the result of the second call to resultSet. next() is false + // The return result set of the ordinary request query contains only one row of results. Therefore, the result of the second call to resultSet.next() is false Assert.assertFalse(resultSet.next()); } catch (SQLException e) { @@ -368,7 +373,7 @@ try { } finally { try { if (resultSet != null) { - // result用完之后需要close + // close result resultSet.close(); } if (pstmt != null) { @@ -379,16 +384,82 @@ try { } } ``` +### Execute Deployment +To execute a deployment, you can use the `SqlClusterExecutor::getCallablePreparedStmt(db, deploymentName)` interface to obtain a `CallablePreparedStatement`. In contrast to the SQL request-based queries mentioned earlier, deployments are already online on the server, which makes them faster compared to SQL request-based queries. + +The process of using a deployment consists of two steps: +- Online Deployment +```java +// Deploy online (use selectSql). In a real production environment, deployments are typically already online and operational. +java.sql.Statement state = sqlExecutor.getStatement(); +try { + String selectSql = String.format("SELECT c1, c3, sum(c4) OVER w1 as w1_c4_sum FROM %s WINDOW w1 AS " + + "(PARTITION BY %s.c1 ORDER BY %s.c7 ROWS_RANGE BETWEEN 2d PRECEDING AND CURRENT ROW);", table, + table, table); + // Deploy + String deploySql = String.format("DEPLOY %s OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') %s", deploymentName, selectSql); + // set return null rs, don't check the returned value, it's false + state.execute(deploySql); +} catch (Exception e) { + e.printStackTrace(); +} +``` +- Execute Deployment +When executing a deployment, recreating a `CallablePreparedStmt` can be time-consuming. It is recommended to reuse the `CallablePreparedStmt` whenever possible. The `executeQuery()` method will automatically clear the request row cache for `setXX` requests. + +```java +// Execute Deployment +PreparedStatement pstmt = null; +ResultSet resultSet = null; +try { + pstmt = sqlExecutor.getCallablePreparedStmt(db, deploymentName); + // Obtain preparedstatement with name + // pstmt = sqlExecutor.getCallablePreparedStmt(db, deploymentName); + ResultSetMetaData metaData = pstmt.getMetaData(); + // Execute request mode requires setting query data in RequestPreparedStatement + setData(pstmt, metaData); + // executeQuery will execute select sql, and put result in resultSet + resultSet = pstmt.executeQuery(); -### Delete all data of a key under the specified index + Assert.assertTrue(resultSet.next()); + Assert.assertEquals(resultSet.getMetaData().getColumnCount(), 3); + Assert.assertEquals(resultSet.getString(1), "bb"); + Assert.assertEquals(resultSet.getInt(2), 24); + Assert.assertEquals(resultSet.getLong(3), 34); + Assert.assertFalse(resultSet.next()); + + // reuse way + for (int i = 0; i < 5; i++) { + setData(pstmt, metaData); + pstmt.executeQuery(); + // skip result check + } +} catch (SQLException e) { + e.printStackTrace(); + Assert.fail(); +} finally { + try { + if (resultSet != null) { + // close result + resultSet.close(); + } + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } +} +``` + +### Delete All Data of a Key under the Specified Index There are two ways to delete data through the Java SDK: - Execute delete SQL directly - - Use delete PreparedStatement -Note that this can only delete data under one index, not all indexes. Refer to [DELETE function boundary](https://openmldb.ai/docs/zh/main/quickstart/function_boundary.html#delete) for details. +Note that this can only delete data under one index, not all indexes. Refer to [DELETE function boundary](../function_boundary.md#delete) for details. ```java java.sql.Statement state = router.getStatement(); @@ -412,7 +483,7 @@ try { } ``` -### A complete example of using SqlClusterExecutor +### A Complete Example of SqlClusterExecutor Refer to the [Java quickstart demo](https://github.com/4paradigm/OpenMLDB/tree/main/demo/java_quickstart/demo). If it is used on macOS, please use openmldb-native of macOS version and increase the dependency of openmldb-native. @@ -427,9 +498,9 @@ java -cp target/demo-1.0-SNAPSHOT.jar com.openmldb.demo.App You must fill in `zkCluster` and `zkPath` (set method or the configuration `foo=bar` after `?` in JDBC). -### Optional configuration +### Optional Configuration -| Optional configuration | Description | +| Optional Configuration | Description | | ---------------------- | ------------------------------------------------------------ | | enableDebug | The default is false. Enable the debug log of hybridse (note that it is not the global debug log). You can view more logs of sql compilation and operation. However, not all of these logs are collected by the client. You need to view the tablet server logs. | | requestTimeout | The default is 60000 ms. This timeout is the rpc timeout sent by the client, except for those sent to the taskmanager (the rpc timeout of the job is controlled by the variable `job_timeout`). | @@ -441,16 +512,18 @@ You must fill in `zkCluster` and `zkPath` (set method or the configuration `foo= | zkLogFile | The default is empty, which is printed to stdout. | | sparkConfPath | The default is empty. You can change the spark conf used by the job through this configuration without configuring the taskmanager to restart. | -## SQL verification +## SQL Validation -The Java client supports the correct verification of SQL to verify whether it is executable. It is divided into batch and request modes. +The Java client supports the verification of SQL to verify whether it is executable. It is divided into batch and request modes. -- `ValidateSQLInBatch` can verify whether SQL can be executed at the offline end. +- `ValidateSQLInBatch` can verify whether SQL can be executed offline. - `ValidateSQLInRequest` can verify whether SQL can be deployed online. -Both interfaces need to go through all table schemas required by SQL. Currently, only single db is supported. Please do not use `db.table` format in SQL statements. +Both interfaces require providing all the table schemas required by the SQL and support multiple databases. For backward compatibility, it's allowed not to specify the db (current database in use) in the parameters. In such cases, it's equivalent to using the first db listed in use schema. It's important to ensure that the `` format tables are from the first db, which doesn't affect SQL statements in the `.
` format. + +For example, verify SQL `select count (c1) over w1 from t3 window w1 as (partition by c1 order by c2 rows between unbounded preceding and current row);`, In addition to this statement, you need to go through in the schema of table `t3` as the second parameter schemaMaps. The format is Map, key is the name of the db, and value is all the table schemas (maps) of each db. In fact, only a single db is supported, so there is usually only one db here, as shown in db3 below. The table schema map key under db is table name, and the value is `com._ 4paradigm.openmldb.sdk.Schema`, consisting of the name and type of each column. -For example, verify SQL `select count (c1) over w1 from t3 window w1 as (partition by c1 order by c2 rows between unbounded preceding and current row);`, In addition to this statement, you need to go through in the schema of table `t3` as the second parameter schemaMaps. The format is Map, key is the name of the db, and value is all the table schemas (maps) of each db. In fact, only a single db is supported, so there is usually only one db here, as shown in db3 below. The table schema map key under db is table name, and the value is com._ 4paradigm.openmldb.sdk.Schema, consisting of the name and type of each column. +The return result is a `List`. If the validation is successful, it returns an empty list. If the validation fails, it returns a list of error messages, such as `[error_msg, error_trace]`. ```java Map> schemaMaps = new HashMap<>(); @@ -461,5 +534,66 @@ schemaMaps.put("db3", dbSchema); List ret = SqlClusterExecutor.validateSQLInRequest("select count(c1) over w1 from t3 window "+ "w1 as(partition by c1 order by c2 rows between unbounded preceding and current row);", schemaMaps); Assert.assertEquals(ret.size(), 0); + +Map> schemaMaps = new HashMap<>(); +Map dbSchema = new HashMap<>(); +dbSchema = new HashMap<>(); +dbSchema.put("t3", new Schema(Arrays.asList(new Column("c1", Types.VARCHAR), new Column("c2", Types.BIGINT)))); +schemaMaps.put("db3", dbSchema); +// Can use parameter format of no db. Make sure that there's only one db in schemaMaps,and only
format is used in sql. +// List ret = SqlClusterExecutor.validateSQLInRequest("select count(c1) over w1 from t3 window "+ +// "w1 as(partition by c1 order by c2 rows between unbounded preceding and current row);", schemaMaps); +List ret = SqlClusterExecutor.validateSQLInRequest("select count(c1) over w1 from t3 window "+ + "w1 as(partition by c1 order by c2 rows between unbounded preceding and current row);", "db3", schemaMaps); +Assert.assertEquals(ret.size(), 0); ``` +## DDL Generation + +The `public static List genDDL(String sql, Map> tableSchema)` method can help users generate table creation statements based on the SQL they want to deploy. It currently supports only a **single** database. The `sql` parameter should not be in the `.
` format. The `tableSchema` parameter should include the schemas of all tables that the SQL depends on. The format of `tableSchema` should be consistent with what was discussed earlier. Even if `tableSchema` contains multiple databases, the database information will be discarded, and all tables will be treated as if they belong to an unknown database. + +## SQL Output Schema + +The `public static Schema genOutputSchema(String sql, String usedDB, Map> tableSchema)` method allows you to obtain the Output Schema for SQL queries and supports multiple databases. If you specify the `usedDB`, you can use tables from that database within the SQL using the `
` format. For backward compatibility, there is also support for the` public static Schema genOutputSchema(String sql, Map> tableSchema)` method without specifying a database (usedDB). In this case, it is equivalent to using the first database listed as the used db. Therefore, you should ensure that tables in `
` format within the `SQ`L query are associated with this first database. + + +## SQL Table Lineage +The `public static List> getDependentTables(String sql, String usedDB, Map> tableSchema)` method allows you to retrieve all tables that the sql query depends on. Each `Pair` in the list corresponds to the database name and table name, with the first element being the primary table, and the rest `[1, end)` representing other dependent tables (excluding the primary table). If the input parameter `usedDB` is an empty string, it means the query is performed without specifying a database (use db) context, which is different from the compatibility rules mentioned earlier for methods like `genDDL`. + +## SQL Merge +The Java client supports merging multiple SQL statements and performs correctness validation in request mode using the `mergeSQL` interface. However, it's important to note that merging is only possible when all the input SQL statements have the same primary table. + +Input parameters: SQL group to be merged; the name of the current database being used; the join key(s) for the primary table (which can be multiple); the schema for all tables involved. + +For example, let's consider four SQL feature views: +``` +// Single-table direct feature +select c1 from main; +// Single-table aggregation feature +select sum(c1) over w1 of2 from main window w1 as (partition by c1 order by c2 rows between unbounded preceding and current row); +// Multi-table feature +select t1.c2 of4 from main last join t1 order by t1.c2 on main.c1==t1.c1; +// Multi-table aggregation feature +select sum(c2) over w1 from main window w1 as (union (select \"\" as id, * from t1) partition by c1 order by c2 rows between unbounded preceding and current row); +``` + +Since all of them have the same primary table, "main," they can be merged. The merging process is essentially a join operation. To perform this operation, you also need to specify a unique column in the "main" table that can be used to identify a unique row of data. For example, if the "id" column in the "main" table is not unique and there may be multiple rows with the same "id" values, you can use a combination of "id" and "c1" columns for the join. Similar to SQL validation, you would also provide a schema map for the tables involved in the merge. + + +```java +//To simplify the demonstration, we are using tables from a single database, so you only need to specify used db and table names if your SQL statements all use the
format. you can leave the used database parameter as an empty string. If your SQL statements use the .
format, you can leave the used db parameter as an empty string +String merged = SqlClusterExecutor.mergeSQL(sqls, "db", Arrays.asList("id", "c1"), schemaMaps); +``` + +The output is a single merged SQL statement, as shown below. The input SQL includes a total of four features, so the merged SQL will only output these four feature columns. (The join keys are automatically filtered.) + +``` +select `c1`, `of2`, `of4`, `sum(c2)over w1` from (select main.id as merge_id_0, c1 from main) as out0 last join (select main.id as merge_id_1, sum(c1) over w1 of2 from main window w1 as (partition by c1 order by c2 rows between unbounded preceding and current row)) as out1 on out0.merge_id_0 = out1.merge_id_1 last join (select main.id as merge_id_2, t1.c2 of4 from main last join t1 order by t1.c2 on main.c1==t1.c1) as out2 on out0.merge_id_0 = out2.merge_id_2 last join (select main.id as merge_id_3, sum(c2) over w1 from main window w1 as (union (select "" as id, * from t1) partition by c1 order by c2 rows between unbounded preceding and current row)) as out3 on out0.merge_id_0 = out3.merge_id_3; +``` + +```{note} +If you encounter an "Ambiguous column name" error during the merging process, it may be due to having the same column names in different feature groups. To resolve this, you should use aliases in your input SQL to distinguish between them. +``` + + + diff --git a/docs/en/quickstart/sdk/python_sdk.md b/docs/en/quickstart/sdk/python_sdk.md index 421f6b8ff93..6ae0e4705af 100644 --- a/docs/en/quickstart/sdk/python_sdk.md +++ b/docs/en/quickstart/sdk/python_sdk.md @@ -1,18 +1,20 @@ # Python SDK -## Python SDK package installation +The default execution mode is Online. -Execute the following command to install the Python SDK package: +## Python SDK Installation + +Execute the following command to install Python SDK: ```bash pip install openmldb ``` -## OpenMLDB DBAPI usage +## OpenMLDB DBAPI -This section demonstrates the basic use of the OpenMLDB DB API. +This section demonstrates the basic use of the OpenMLDB DB API. For all DBAPI interfaces, if an execution fails, it will raise a `DatabaseError` exception. Users can catch this exception and handle it as needed. The return value is a `Cursor`. For DDL SQL, you do not need to handle the return value. For other SQL statements, you can refer to the specific examples below for how to handle the return value. -### Create connection +### Create Connection Parameter `db_name` name must exist, and the database must be created before the connection is created. To continue, create a connection without a database and then use the database db through the `execute ("USE")` command. @@ -24,11 +26,11 @@ cursor = db.cursor() #### Configuration Details -Zk and zkPath configuration are required. +Zk and zkPath configurations are required. -The Python SDK can be used through OpenMLDB DBAPI/SQLAlchemy. The optional configurations are basically the same as those of the Java client. Please refer to the [Java SDK configuration](https://openmldb.ai/docs/zh/main/quickstart/sdk/java_sdk.html#sdk) for details. +The Python SDK can be used through OpenMLDB DBAPI/SQLAlchemy. The optional configurations are basically the same as those of the Java client. Please refer to the [Java SDK configuration](./java_sdk.md#sdk-configuration-details) for details. -### Create database +### Create Database Create database `db1`: @@ -37,7 +39,7 @@ cursor.execute("CREATE DATABASE db1") cursor.execute("USE db1") ``` -### Create table +### Create Table Create table `t1`: @@ -45,7 +47,7 @@ Create table `t1`: cursor.execute("CREATE TABLE t1 (col1 bigint, col2 date, col3 string, col4 string, col5 int, index(key=col3, ts=col1))") ``` -### Insert data into the table +### Insert Data into Table Insert one sentence of data into the table: @@ -53,7 +55,7 @@ Insert one sentence of data into the table: cursor.execute("INSERT INTO t1 VALUES(1000, '2020-12-25', 'guangdon', 'shenzhen', 1)") ``` -### Execute SQL query +### Execute SQL Query ```python result = cursor.execute("SELECT * FROM t1") @@ -62,15 +64,30 @@ print(result.fetchmany(10)) print(result.fetchall()) ``` -### SQL batch request query +### SQL Batch Query ```python -#In the Batch Request mode, the input parameters of the interface are“SQL”, “Common_Columns”, “Request_Columns” +#In the Batch Request mode, the input parameters of the interface are "SQL", "Common_Columns", "Request_Columns" result = cursor.batch_row_request("SELECT * FROM t1", ["col1","col2"], ({"col1": 2000, "col2": '2020-12-22', "col3": 'fujian', "col4":'xiamen', "col5": 2})) print(result.fetchone()) ``` +### Execute Deployment + +Please note that the execution of deployments is only supported by DBAPI, and there is no equivalent interface in OpenMLDB SQLAlchemy. Additionally, deployment execution supports single requests only and does not support batch requests. + +```python +cursor.execute("DEPLOY d1 SELECT col1 FROM t1") +# dict style +result = cursor.callproc("d1", {"col1": 1000, "col2": None, "col3": None, "col4": None, "col5": None}) +print(result.fetchall()) +# tuple style +result = cursor.callproc("d1", (1001, "2023-07-20", "abc", "def", 1)) +print(result.fetchall()) +# drop deployment before drop table +cursor.execute("DROP DEPLOYMENT d1") +``` -### Delete table +### Delete Table Delete table `t1`: @@ -78,7 +95,7 @@ Delete table `t1`: cursor.execute("DROP TABLE t1") ``` -### Delete database +### Delete Database Delete database `db1`: @@ -86,17 +103,17 @@ Delete database `db1`: cursor.execute("DROP DATABASE db1") ``` -### Close connection +### Close Connection ```python cursor.close() ``` -## OpenMLDB SQLAlchemy usage +## OpenMLDB SQLAlchemy -This section demonstrates using the Python SDK through OpenMLDB SQLAlchemy. +This section demonstrates the use of the Python SDK through OpenMLDB SQLAlchemy. Similarly, if any of the DBAPI interfaces fail, they will raise a `DatabaseError` exception. Users can catch and handle this exception as needed. The handling of return values should follow the SQLAlchemy standard. -### Create connection +### Create Connection ```python create_engine('openmldb:///db_name?zk=zkcluster&zkPath=zkpath') @@ -110,7 +127,7 @@ engine = db.create_engine('openmldb:///?zk=127.0.0.1:2181&zkPath=/openmldb') connection = engine.connect() ``` -### Create database +### Create Database Use the `connection.execute()` interface to create database `db1`: @@ -123,7 +140,7 @@ except Exception as e: connection.execute("USE db1") ``` -### Create table +### Create Table Use the `connection.execute()` interface to create table `t1`: @@ -134,7 +151,7 @@ except Exception as e: print(e) ``` -### Insert data into the table +### Insert Data into Table Use the `connection.execute (ddl)` interface to execute the SQL insert statement, and you can insert data into the table: @@ -156,7 +173,7 @@ except Exception as e: print(e) ``` -### Execute SQL batch query +### Execute SQL Batch Query Use the `connection.execute (sql)` interface to execute SQL batch query statements: @@ -171,7 +188,7 @@ except Exception as e: print(e) ``` -### Execute SQL request query +### Execute SQL Query Use the `connection.execute (sql, request)` interface to execute the SQL request query. You can put the input data into the second parameter of the execute function: @@ -182,7 +199,7 @@ except Exception as e: print(e) ``` -### Delete table +### Delete Table Use the `connection.execute (ddl)` interface to delete table `t1`: @@ -193,7 +210,7 @@ except Exception as e: print(e) ``` -### Delete database +### Delete Database Use the connection.execute(ddl)interface to delete database `db1`: @@ -204,7 +221,7 @@ except Exception as e: print(e) ``` -## Notebook Magic Function usage +## Notebook Magic Function The OpenMLDB Python SDK supports the expansion of Notebook magic function. Use the following statement to register the function. @@ -216,26 +233,24 @@ openmldb.sql_magic.register(db) Then you can use line magic function `%sql` and block magic function `%%sql` in Notebook. -![img](https://openmldb.ai/docs/zh/main/_images/openmldb_magic_function.png) - -## The complete usage example +![img](../images/openmldb_magic_function.png) -Refer to the [Python quickstart demo](https://github.com/4paradigm/OpenMLDB/tree/main/demo/python_quickstart/demo.py), including the above DBAPI and SQLAlchemy usage. +## A Complete Example -## common problem +Refer to the [Python quickstart demo](https://github.com/4paradigm/OpenMLDB/tree/main/demo/python_quickstart/demo.py), which includes the above DBAPI and SQLAlchemy usage. -- **What do I do when error** `ImportError:dlopen (.. _sql_router_sdk. so, 2): initializer function 0xnnnn not in mapped image for` **appears when using SQLAlchemy?** +## Q&A -In addition to import openmldb, you may also import other third-party libraries, which may cause confusion in the loading order. Due to the complexity of the system, you can try to use the virtual env environment (such as conda) to avoid interference. In addition, import openmldb before importing sqlalchemy, and ensure that the two imports are in the first place. +- **What do I do when error `ImportError:dlopen (.. _sql_router_sdk. so, 2): initializer function 0xnnnn not in mapped image for` appears when using SQLAlchemy?** -If the error still occur, it is recommended to connect to OpenMLDB by using request http to connect to apiserver. +In addition to importing OpenMLDB, you may also have imported other third-party libraries, which may cause confusion in the loading order. Due to the complexity of the system, you can try to use the virtual env environment (such as conda) to avoid interference. In addition, import OpenMLDB before importing SQLAlchemy, and ensure that the two imports are in the first place. -occur +If the error still occurs, it is recommended to connect to OpenMLDB by request http to connect to apiserver. -- **What do I do if Python SDK encountered the following problems?** +- **What do I do if Python SDK encounters the following problems?** ```plain [libprotobuf FATAL /Users/runner/work/crossbow/crossbow/vcpkg/buildtrees/protobuf/src/23fa7edd52-3ba2225d30.clean/src/google/protobuf/stubs/common.cc:87] This program was compiled against version 3.6.1 of the Protocol Buffer runtime library, which is not compatible with the installed version (3.15.8). Contact the program author for an update. ... ``` -This problem may be due to the introduction of other versions of protobuf in other libraries. You can try to use the virtual env environment (such as conda). +This problem may be due to the import of other versions of protobuf from other libraries. You can try to use the virtual env environment (such as conda). diff --git a/docs/en/quickstart/sdk/rest_api.md b/docs/en/quickstart/sdk/rest_api.md index 7d8f3c4a881..c2a6cc972ea 100644 --- a/docs/en/quickstart/sdk/rest_api.md +++ b/docs/en/quickstart/sdk/rest_api.md @@ -1,12 +1,12 @@ # REST API -## Important information +## Important -REST APIs interact with the services of APIServer and OpenMLDB, so the APIServer module must be properly deployed to be used effectively. APIServer is an optional module during installation and deployment. Refer to the APIServer deployment document. +REST APIs interact with the services of APIServer and OpenMLDB, so the APIServer module must be properly deployed to be used effectively. APIServer is an optional module during installation and deployment. Refer to [APIServer Deployment](../../deploy/install_deploy.md). At this stage, APIServer is mainly used for functional testing, not recommended for performance testing, nor recommended for the production environment. The default deployment of APIServer does not have a high availability mechanism at present and introduces additional network and codec overhead. -## Data insertion +## Data Insertion Request address: http://ip:port/dbs/{db_name}/tables/{table_name} @@ -23,7 +23,6 @@ The requestor: ``` - Currently, it only supports inserting one piece of data. - - The data should be arranged in strict accordance with the schema. Sample request data: @@ -44,7 +43,7 @@ Response: } ``` -## Real-time feature computing +## Real-Time Feature Computing Request address: http://ip:port/dbs/{db_name}/deployments/{deployment_name} @@ -81,11 +80,11 @@ Requestor - Input data in JSON format can have redundant columns. -**Sample request data** +**Sample Request Data** Example 1: Array format -```plain +```Plain curl http://127.0.0.1:8080/dbs/demo_db/deployments/demo_data_service -X POST -d'{ "input": [["aaa", 11, 22, 1.2, 1.3, 1635247427000, "2021-05-20"]] }' @@ -106,9 +105,7 @@ Response: Example 2: JSON format ```JSON -curl http://127.0.0.1:8080/dbs/demo_db/deployments/demo_data_service -X POST -d'{ - "input": [{"c1":"aaa", "c2":11, "c3":22, "c4":1.2, "c5":1.3, "c6":1635247427000, "c7":"2021-05-20", "foo":"bar"}] - }' +curl http://127.0.0.1:8080/dbs/demo_db/deployments/demo_data_service -X POST -d'{"input": [{"c1":"aaa", "c2":11, "c3":22, "c4":1.2, "c5":1.3, "c6":1635247427000, "c7":"2021-05-20", "foo":"bar"}]}' ``` Response: @@ -125,7 +122,7 @@ Response: ## Query -Request address: http://ip:port/dbs/ {db_name} +Request address: http://ip:port/dbs/{db_name} Request method: POST @@ -146,13 +143,13 @@ Request parameters: | Parameters | Type | Requirement | Description | | ---------- | ------ | ----------- | ------------------------------------------------------------ | -| mode | String | Yes | Available for `offsync` , `offasync`, `online` | +| mode | String | Yes | Set to `offsync` , `offasync`, `online` | | sql | String | Yes | | | input | Object | No | | | schema | Array | No | Support data types (case insensitive): `Bool`, `Int16`, `Int32`, `Int64`, `Float`, `Double`, `String`, `Date and Timestamp` | | data | Array | No | | -**Sample request data** +**Sample Request Data** Example 1: General query @@ -202,7 +199,7 @@ Response: } ``` -## Query deployment information +## Query Deployment Information Request address: http://ip:port/dbs/{db_name}/deployments/{deployment_name} @@ -239,7 +236,7 @@ Response: } ``` -## Acquire all library names +## Acquire All Library Names Request address: http://ip:port/dbs @@ -257,7 +254,7 @@ Response: } ``` -## Acquire all table names +## Acquire All Table Names Request address: http://ip:port/dbs/{db}/tables @@ -310,7 +307,7 @@ Response: } ``` -## Refresh APIServer metadata cache +## Refresh APIServer Metadata Cache Request address: http://ip:port/refresh diff --git a/docs/en/reference/ip_tips.md b/docs/en/reference/ip_tips.md index 2fc5b1c8805..aa608ca1d8c 100644 --- a/docs/en/reference/ip_tips.md +++ b/docs/en/reference/ip_tips.md @@ -38,12 +38,12 @@ Expose the port through `-p` when starting the container, and the client can acc The stand-alone version needs to expose the ports of three components (nameserver, tabletserver, apiserver): ``` -docker run -p 6527:6527 -p 9921:9921 -p 8080:8080 -it 4pdosc/openmldb:0.8.3 bash +docker run -p 6527:6527 -p 9921:9921 -p 8080:8080 -it 4pdosc/openmldb:0.8.4 bash ``` The cluster version needs to expose the zk port and the ports of all components: ``` -docker run -p 2181:2181 -p 7527:7527 -p 10921:10921 -p 10922:10922 -p 8080:8080 -p 9902:9902 -it 4pdosc/openmldb:0.8.3 bash +docker run -p 2181:2181 -p 7527:7527 -p 10921:10921 -p 10922:10922 -p 8080:8080 -p 9902:9902 -it 4pdosc/openmldb:0.8.4 bash ``` ```{tip} @@ -57,7 +57,7 @@ If the OpenMLDB service process is distributed, the "port number is occupied" ap #### Host Network Or more conveniently, use host networking without port isolation, for example: ``` -docker run --network host -it 4pdosc/openmldb:0.8.3 bash +docker run --network host -it 4pdosc/openmldb:0.8.4 bash ``` But in this case, it is easy to find that the port is occupied by other processes in the host. If occupancy occurs, change the port number carefully. diff --git a/docs/en/reference/sql/ddl/CREATE_TABLE_STATEMENT.md b/docs/en/reference/sql/ddl/CREATE_TABLE_STATEMENT.md index a0d11d90657..ba62cf55231 100644 --- a/docs/en/reference/sql/ddl/CREATE_TABLE_STATEMENT.md +++ b/docs/en/reference/sql/ddl/CREATE_TABLE_STATEMENT.md @@ -473,6 +473,11 @@ StorageMode ::= 'Memory' | 'HDD' | 'SSD' +CompressTypeOption + ::= 'COMPRESS_TYPE' '=' CompressType +CompressType + ::= 'NoCompress' + | 'Snappy ``` @@ -484,6 +489,7 @@ StorageMode | `REPLICANUM` | It defines the number of replicas for the table. Note that the number of replicas is only configurable in Cluster version. | `OPTIONS (REPLICANUM=3)` | | `DISTRIBUTION` | It defines the distributed node endpoint configuration. Generally, it contains a Leader node and several followers. `(leader, [follower1, follower2, ..])`. Without explicit configuration, OpenMLDB will automatically configure `DISTRIBUTION` according to the environment and nodes. | `DISTRIBUTION = [ ('127.0.0.1:6527', [ '127.0.0.1:6528','127.0.0.1:6529' ])]` | | `STORAGE_MODE` | It defines the storage mode of the table. The supported modes are `Memory`, `HDD` and `SSD`. When not explicitly configured, it defaults to `Memory`.
If you need to support a storage mode other than `Memory` mode, `tablet` requires additional configuration options. For details, please refer to [tablet configuration file **conf/tablet.flags**](../../../deploy/conf.md#the-configuration-file-for-apiserver:-conf/tablet.flags). | `OPTIONS (STORAGE_MODE='HDD')` | +| `COMPRESS_TYPE` | It defines the compress types of the table. The supported compress type are `NoCompress` and `Snappy`. The default value is `NoCompress` | `OPTIONS (COMPRESS_TYPE='Snappy')` #### The Difference between Disk Table and Memory Table @@ -515,11 +521,11 @@ DESC t1; --- -------------------- ------ ---------- ------ --------------- 1 INDEX_0_1651143735 col1 std_time 0min kAbsoluteTime --- -------------------- ------ ---------- ------ --------------- - -------------- - storage_mode - -------------- - HDD - -------------- + --------------- -------------- + compress_type storage_mode + --------------- -------------- + NoCompress HDD + --------------- -------------- ``` The following sql command create a table with specified distribution. ```sql diff --git a/docs/en/reference/sql/ddl/DESC_STATEMENT.md b/docs/en/reference/sql/ddl/DESC_STATEMENT.md index 8179c952c56..a7d288064bb 100644 --- a/docs/en/reference/sql/ddl/DESC_STATEMENT.md +++ b/docs/en/reference/sql/ddl/DESC_STATEMENT.md @@ -56,11 +56,11 @@ desc t1; --- -------------------- ------ ---------- ---------- --------------- 1 INDEX_0_1658136511 col1 std_time 43200min kAbsoluteTime --- -------------------- ------ ---------- ---------- --------------- - -------------- - storage_mode - -------------- - Memory - -------------- + --------------- -------------- + compress_type storage_mode + --------------- -------------- + NoCompress Memory + --------------- -------------- ``` diff --git a/docs/en/reference/sql/ddl/SHOW_CREATE_TABLE_STATEMENT.md b/docs/en/reference/sql/ddl/SHOW_CREATE_TABLE_STATEMENT.md new file mode 100644 index 00000000000..967ebce316a --- /dev/null +++ b/docs/en/reference/sql/ddl/SHOW_CREATE_TABLE_STATEMENT.md @@ -0,0 +1,28 @@ +# SHOW CREATE TABLE + +`SHOW CREATE TABLE` shows the `CREATE TABLE` statement that creates the named table + +**Syntax** + +```sql +SHOW CREATE TABLE table_name; +``` + +**Example** + +```sql +show create table t1; + ------- --------------------------------------------------------------- + Table Create Table + ------- --------------------------------------------------------------- + t1 CREATE TABLE `t1` ( + `c1` varchar, + `c2` int, + `c3` bigInt, + `c4` timestamp, + INDEX (KEY=`c1`, TS=`c4`, TTL_TYPE=ABSOLUTE, TTL=0m) + ) OPTIONS (PARTITIONNUM=8, REPLICANUM=2, STORAGE_MODE='HDD', COMPRESS_TYPE='NoCompress'); + ------- --------------------------------------------------------------- + +1 rows in set +``` \ No newline at end of file diff --git a/docs/en/reference/sql/ddl/TRUNCATE_TABLE_STATEMENT.md b/docs/en/reference/sql/ddl/TRUNCATE_TABLE_STATEMENT.md new file mode 100644 index 00000000000..3bd9360d920 --- /dev/null +++ b/docs/en/reference/sql/ddl/TRUNCATE_TABLE_STATEMENT.md @@ -0,0 +1,16 @@ +# TRUNCATE TABLE + +``` +TRUNCATE TABLE table_name +``` + +`TRUNCATE TABLE` statement is used to clear the specified table. + +## Example: clear t1 + +```sql +TRUNCATE TABLE t1; +-- Truncate table t1? yes/no +-- yes +-- SUCCEED +``` \ No newline at end of file diff --git a/docs/en/reference/sql/ddl/index.rst b/docs/en/reference/sql/ddl/index.rst index 09199ec27ba..bff9db48fb0 100644 --- a/docs/en/reference/sql/ddl/index.rst +++ b/docs/en/reference/sql/ddl/index.rst @@ -23,3 +23,5 @@ Data Definition Statement (DDL) CREATE_FUNCTION SHOW_FUNCTIONS DROP_FUNCTION + SHOW_CREATE_TABLE_STATEMENT + TRUNCATE_TABLE_STATEMENT diff --git a/docs/en/reference/sql/dql/WINDOW_CLAUSE.md b/docs/en/reference/sql/dql/WINDOW_CLAUSE.md index bbc71a4f222..f3add760280 100644 --- a/docs/en/reference/sql/dql/WINDOW_CLAUSE.md +++ b/docs/en/reference/sql/dql/WINDOW_CLAUSE.md @@ -320,5 +320,5 @@ WINDOW w1 AS (PARTITION BY col1 ORDER BY col5 ROWS_RANGE BETWEEN 10s PRECEDING A ``` ```{seealso} -Please refer to [Built-in Functions](../functions_and_operators/Files/udfs_8h.md) for aggregate functions that can be used in window computation. +Please refer to [Built-in Functions](../udfs_8h.md) for aggregate functions that can be used in window computation. ```` diff --git a/docs/en/reference/sql/index.rst b/docs/en/reference/sql/index.rst index ee57dbac297..58bcc3e5502 100644 --- a/docs/en/reference/sql/index.rst +++ b/docs/en/reference/sql/index.rst @@ -9,6 +9,7 @@ SQL language_structure/index data_types/index functions_and_operators/index + udfs_8h dql/index dml/index ddl/index diff --git a/docs/en/reference/sql/functions_and_operators/index.rst b/docs/en/reference/sql/operators/index.rst similarity index 65% rename from docs/en/reference/sql/functions_and_operators/index.rst rename to docs/en/reference/sql/operators/index.rst index b889a6e8a87..db068373e46 100644 --- a/docs/en/reference/sql/functions_and_operators/index.rst +++ b/docs/en/reference/sql/operators/index.rst @@ -1,5 +1,5 @@ ============================= -Expressions, Functions, and Operations +Expressions and Operations ============================= @@ -7,4 +7,3 @@ Expressions, Functions, and Operations :maxdepth: 1 operators - Files/udfs_8h diff --git a/docs/en/reference/sql/functions_and_operators/operators.md b/docs/en/reference/sql/operators/operators.md similarity index 100% rename from docs/en/reference/sql/functions_and_operators/operators.md rename to docs/en/reference/sql/operators/operators.md diff --git a/docs/zh/openmldb_sql/functions_and_operators/Files/udfs_8h.md b/docs/en/reference/sql/udfs_8h.md similarity index 68% rename from docs/zh/openmldb_sql/functions_and_operators/Files/udfs_8h.md rename to docs/en/reference/sql/udfs_8h.md index ac96c6bfc3f..9cfab05977f 100644 --- a/docs/zh/openmldb_sql/functions_and_operators/Files/udfs_8h.md +++ b/docs/en/reference/sql/udfs_8h.md @@ -10,158 +10,158 @@ title: udfs/udfs.h | Name | Description | | -------------- | -------------- | -| **[abs](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-abs)**()|
Return the absolute value of expr. | -| **[acos](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-acos)**()|
Return the arc cosine of expr. | -| **[add](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-add)**()|
Compute sum of two arguments. | -| **[add_months](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-add-months)**()|
adds an integer months to a given date, returning the resulting date. | -| **[array_contains](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-array-contains)**()|
array_contains(array, value) - Returns true if the array contains the value. | -| **[asin](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-asin)**()|
Return the arc sine of expr. | -| **[at](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-at)**()| | -| **[atan](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-atan)**()|
Return the arc tangent of expr If called with one parameter, this function returns the arc tangent of expr. If called with two parameters X and Y, this function returns the arc tangent of Y / X. | -| **[atan2](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-atan2)**()|
Return the arc tangent of Y / X.. | -| **[avg](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg)**()|
Compute average of values. | -| **[avg_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg-cate)**()|
Compute average of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[avg_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V', separated by comma, and sorted by key in ascend order. | -| **[avg_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg-where)**()|
Compute average of values match specified condition. | -| **[bigint](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-bigint)**()| | -| **[bool](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-bool)**()|
Cast string expression to bool. | -| **[ceil](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ceil)**()|
Return the smallest integer value not less than the expr. | -| **[ceiling](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ceiling)**()| | -| **[char](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-char)**()|
Returns the ASCII character having the binary equivalent to expr. If n >= 256 the result is equivalent to char(n % 256). | -| **[char_length](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-char-length)**()|
Returns the length of the string. It is measured in characters and multibyte character string is not supported. | -| **[character_length](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-character-length)**()| | -| **[concat](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-concat)**()|
This function returns a string resulting from the joining of two or more string values in an end-to-end manner. (To add a separating value during joining, see concat_ws.) | -| **[concat_ws](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-concat-ws)**()|
Returns a string resulting from the joining of two or more string value in an end-to-end manner. It separates those concatenated string values with the delimiter specified in the first function argument. | -| **[cos](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-cos)**()|
Return the cosine of expr. | -| **[cot](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-cot)**()|
Return the cotangent of expr. | -| **[count](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count)**()|
Compute number of values. | -| **[count_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count-cate)**()|
Compute count of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[count_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[count_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count-where)**()|
Compute number of values match specified condition. | -| **[date](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-date)**()|
Cast timestamp or string expression to date (date >= 1900-01-01) | -| **[date_format](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-date-format)**()|
Formats the date value according to the format string. | -| **[datediff](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-datediff)**()|
days difference from date1 to date2 | -| **[day](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-day)**()| | -| **[dayofmonth](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-dayofmonth)**()|
Return the day of the month for a timestamp or date. | -| **[dayofweek](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-dayofweek)**()|
Return the day of week for a timestamp or date. | -| **[dayofyear](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-dayofyear)**()|
Return the day of year for a timestamp or date. Returns 0 given an invalid date. | -| **[degrees](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-degrees)**()|
Convert radians to degrees. | -| **[distinct_count](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-distinct-count)**()|
Compute number of distinct values. | -| **[double](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-double)**()|
Cast string expression to double. | -| **[drawdown](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-drawdown)**()|
Compute drawdown of values. | -| **[earth_distance](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-earth-distance)**()|
Returns the great circle distance between two points on the surface of the Earth. Km as return unit. add a minus (-) sign if heading west (W) or south (S). | -| **[entropy](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-entropy)**()|
Calculate Shannon entropy of a column of values. Null values are skipped. | -| **[ew_avg](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ew-avg)**()|
Compute exponentially-weighted average of values. It's equivalent to pandas ewm(alpha={alpha}, adjust=True, ignore_na=True, com=None, span=None, halflife=None, min_periods=0) | -| **[exp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-exp)**()|
Return the value of e (the base of natural logarithms) raised to the power of expr. | -| **[farm_fingerprint](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-farm-fingerprint)**()| | -| **[first_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-first-value)**()|
Returns the value of expr from the latest row (last row) of the window frame. | -| **[float](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-float)**()|
Cast string expression to float. | -| **[floor](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-floor)**()|
Return the largest integer value not less than the expr. | -| **[get_json_object](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-get-json-object)**()|
Extracts a JSON object from [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901)| -| **[hash64](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-hash64)**()|
Returns a hash value of the arguments. It is not a cryptographic hash function and should not be used as such. | -| **[hex](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-hex)**()|
Convert integer to hexadecimal. | -| **[hour](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-hour)**()|
Return the hour for a timestamp. | -| **[identity](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-identity)**()|
Return value. | -| **[if_null](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-if-null)**()|
If input is not null, return input value; else return default value. | -| **[ifnull](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ifnull)**()| | -| **[ilike_match](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ilike-match)**()|
pattern match same as ILIKE predicate | -| **[inc](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-inc)**()|
Return expression + 1. | -| **[int](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int)**()| | -| **[int16](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int16)**()|
Cast string expression to int16. | -| **[int32](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int32)**()|
Cast string expression to int32. | -| **[int64](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int64)**()|
Cast string expression to int64. | -| **[is_null](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-is-null)**()|
Check if input value is null, return bool. | -| **[isnull](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-isnull)**()| | -| **[join](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-join)**()|
For each string value from specified column of window, join by delimeter. Null values are skipped. | -| **[json_array_length](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-json-array-length)**()|
Returns the number of elements in the outermost JSON array. | -| **[lag](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lag)**()|
Returns value evaluated at the row that is offset rows before the current row within the partition. Offset is evaluated with respect to the current row. | -| **[last_day](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-last-day)**()|
Return the last day of the month to which the date belongs to. | -| **[lcase](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lcase)**()|
Convert all the characters to lowercase. Note that characters with values > 127 are simply returned. | -| **[like_match](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-like-match)**()|
pattern match same as LIKE predicate | -| **[list_except_by_key](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-list-except-by-key)**()|
Return list of elements in list1 but keys not in except_str. | -| **[list_except_by_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-list-except-by-value)**()|
Return list of elements in list1 but values not in except_str. | -| **[ln](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ln)**()|
Return the natural logarithm of expr. | -| **[log](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-log)**()|
log(base, expr) If called with one parameter, this function returns the natural logarithm of expr. If called with two parameters, this function returns the logarithm of expr to the base. | -| **[log10](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-log10)**()|
Return the base-10 logarithm of expr. | -| **[log2](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-log2)**()|
Return the base-2 logarithm of expr. | -| **[lower](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lower)**()| | -| **[make_tuple](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-make-tuple)**()| | -| **[max](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max)**()|
Compute maximum of values. | -| **[max_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max-cate)**()|
Compute maximum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[max_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[max_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max-where)**()|
Compute maximum of values match specified condition. | -| **[maximum](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-maximum)**()|
Compute maximum of two arguments. | -| **[median](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-median)**()|
Compute the median of values. | -| **[min](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min)**()|
Compute minimum of values. | -| **[min_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min-cate)**()|
Compute minimum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[min_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[min_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min-where)**()|
Compute minimum of values match specified condition. | -| **[minimum](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-minimum)**()|
Compute minimum of two arguments. | -| **[minute](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-minute)**()|
Return the minute for a timestamp. | -| **[month](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-month)**()|
Return the month part of a timestamp or date. | -| **[nth_value_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-nth-value-where)**()|
Returns the value of expr from the idx th row matches the condition. | -| **[nvl](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-nvl)**()| | -| **[nvl2](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-nvl2)**()|
nvl2(expr1, expr2, expr3) - Returns expr2 if expr1 is not null, or expr3 otherwise. | -| **[pmod](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-pmod)**()|
Compute pmod of two arguments. If any param is NULL, output NULL. If divisor is 0, output NULL. | -| **[pow](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-pow)**()|
Return the value of expr1 to the power of expr2. | -| **[power](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-power)**()| | -| **[radians](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-radians)**()|
Returns the argument X, converted from degrees to radians. (Note that π radians equals 180 degrees.) | -| **[regexp_like](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-regexp-like)**()|
pattern match same as RLIKE predicate (based on RE2) | -| **[replace](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-replace)**()|
replace(str, search[, replace]) - Replaces all occurrences of `search` with `replace`| -| **[reverse](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-reverse)**()|
Returns the reversed given string. | -| **[round](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-round)**()|
Returns expr rounded to d decimal places using HALF_UP rounding mode. | -| **[second](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-second)**()|
Return the second for a timestamp. | -| **[sin](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sin)**()|
Return the sine of expr. | -| **[size](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-size)**()|
Get the size of a List (e.g., result of split) | -| **[smallint](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-smallint)**()| | -| **[split](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split)**()|
Split string to list by delimeter. Null values are skipped. | -| **[split_array](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split-array)**()|
Split string to array of string by delimeter. | -| **[split_by_key](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split-by-key)**()|
Split string by delimeter and split each segment as kv pair, then add each key to output list. Null or illegal segments are skipped. | -| **[split_by_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split-by-value)**()|
Split string by delimeter and split each segment as kv pair, then add each value to output list. Null or illegal segments are skipped. | -| **[sqrt](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sqrt)**()|
Return square root of expr. | -| **[std](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-std)**()| | -| **[stddev](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-stddev)**()|
Compute sample standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / (n-1) )`| -| **[stddev_pop](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-stddev-pop)**()|
Compute population standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / n )`| -| **[stddev_samp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-stddev-samp)**()| | -| **[strcmp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-strcmp)**()|
Returns 0 if the strings are the same, -1 if the first argument is smaller than the second according to the current sort order, and 1 otherwise. | -| **[string](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-string)**()|
Return string converted from timestamp expression. | -| **[substr](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-substr)**()| | -| **[substring](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-substring)**()|
Return a substring `len` characters long from string str, starting at position `pos`. Alias function: `substr`| -| **[sum](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum)**()|
Compute sum of values. | -| **[sum_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum-cate)**()|
Compute sum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[sum_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[sum_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum-where)**()|
Compute sum of values match specified condition. | -| **[tan](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-tan)**()|
Return the tangent of expr. | -| **[timestamp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-timestamp)**()|
Cast int64, date or string expression to timestamp. | -| **[top](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top)**()|
Compute top k of values and output string separated by comma. The outputs are sorted in desc order. | -| **[top1_ratio](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top1-ratio)**()|
Compute the top1 occurring value's ratio. | -| **[top_n_key_avg_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_count_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_max_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_min_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_ratio_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | -| **[top_n_key_sum_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_avg_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_count_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_max_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_min_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_ratio_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | -| **[top_n_value_sum_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[topn_frequency](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-topn-frequency)**()|
Return the topN keys sorted by their frequency. | -| **[truncate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-truncate)**()|
Return the nearest integer that is not greater in magnitude than the expr. | -| **[ucase](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ucase)**()|
Convert all the characters to uppercase. Note that characters values > 127 are simply returned. | -| **[unhex](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-unhex)**()|
Convert hexadecimal to binary string. | -| **[unix_timestamp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-unix-timestamp)**()|
Cast date or string expression to unix_timestamp. If empty string or NULL is provided, return current timestamp. | -| **[upper](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-upper)**()| | -| **[var_pop](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-var-pop)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / n`| -| **[var_samp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-var-samp)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / (n-1)`| -| **[variance](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-variance)**()| | -| **[week](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-week)**()| | -| **[weekofyear](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-weekofyear)**()|
Return the week of year for a timestamp or date. | -| **[window_split](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-window-split)**()|
For each string value from specified column of window, split by delimeter and add segment to output list. Null values are skipped. | -| **[window_split_by_key](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-window-split-by-key)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each key to output list. Null and illegal segments are skipped. | -| **[window_split_by_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-window-split-by-value)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each value to output list. Null and illegal segments are skipped. | -| **[year](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-year)**()|
Return the year part of a timestamp or date. | +| **[abs](/openmldb_sql/Files/udfs_8h.md#function-abs)**()|
Return the absolute value of expr. | +| **[acos](/openmldb_sql/Files/udfs_8h.md#function-acos)**()|
Return the arc cosine of expr. | +| **[add](/openmldb_sql/Files/udfs_8h.md#function-add)**()|
Compute sum of two arguments. | +| **[add_months](/openmldb_sql/Files/udfs_8h.md#function-add-months)**()|
adds an integer months to a given date, returning the resulting date. | +| **[array_contains](/openmldb_sql/Files/udfs_8h.md#function-array-contains)**()|
array_contains(array, value) - Returns true if the array contains the value. | +| **[asin](/openmldb_sql/Files/udfs_8h.md#function-asin)**()|
Return the arc sine of expr. | +| **[at](/openmldb_sql/Files/udfs_8h.md#function-at)**()| | +| **[atan](/openmldb_sql/Files/udfs_8h.md#function-atan)**()|
Return the arc tangent of expr If called with one parameter, this function returns the arc tangent of expr. If called with two parameters X and Y, this function returns the arc tangent of Y / X. | +| **[atan2](/openmldb_sql/Files/udfs_8h.md#function-atan2)**()|
Return the arc tangent of Y / X.. | +| **[avg](/openmldb_sql/Files/udfs_8h.md#function-avg)**()|
Compute average of values. | +| **[avg_cate](/openmldb_sql/Files/udfs_8h.md#function-avg-cate)**()|
Compute average of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[avg_cate_where](/openmldb_sql/Files/udfs_8h.md#function-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V', separated by comma, and sorted by key in ascend order. | +| **[avg_where](/openmldb_sql/Files/udfs_8h.md#function-avg-where)**()|
Compute average of values match specified condition. | +| **[bigint](/openmldb_sql/Files/udfs_8h.md#function-bigint)**()| | +| **[bool](/openmldb_sql/Files/udfs_8h.md#function-bool)**()|
Cast string expression to bool. | +| **[ceil](/openmldb_sql/Files/udfs_8h.md#function-ceil)**()|
Return the smallest integer value not less than the expr. | +| **[ceiling](/openmldb_sql/Files/udfs_8h.md#function-ceiling)**()| | +| **[char](/openmldb_sql/Files/udfs_8h.md#function-char)**()|
Returns the ASCII character having the binary equivalent to expr. If n >= 256 the result is equivalent to char(n % 256). | +| **[char_length](/openmldb_sql/Files/udfs_8h.md#function-char-length)**()|
Returns the length of the string. It is measured in characters and multibyte character string is not supported. | +| **[character_length](/openmldb_sql/Files/udfs_8h.md#function-character-length)**()| | +| **[concat](/openmldb_sql/Files/udfs_8h.md#function-concat)**()|
This function returns a string resulting from the joining of two or more string values in an end-to-end manner. (To add a separating value during joining, see concat_ws.) | +| **[concat_ws](/openmldb_sql/Files/udfs_8h.md#function-concat-ws)**()|
Returns a string resulting from the joining of two or more string value in an end-to-end manner. It separates those concatenated string values with the delimiter specified in the first function argument. | +| **[cos](/openmldb_sql/Files/udfs_8h.md#function-cos)**()|
Return the cosine of expr. | +| **[cot](/openmldb_sql/Files/udfs_8h.md#function-cot)**()|
Return the cotangent of expr. | +| **[count](/openmldb_sql/Files/udfs_8h.md#function-count)**()|
Compute number of values. | +| **[count_cate](/openmldb_sql/Files/udfs_8h.md#function-count-cate)**()|
Compute count of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[count_cate_where](/openmldb_sql/Files/udfs_8h.md#function-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[count_where](/openmldb_sql/Files/udfs_8h.md#function-count-where)**()|
Compute number of values match specified condition. | +| **[date](/openmldb_sql/Files/udfs_8h.md#function-date)**()|
Cast timestamp or string expression to date (date >= 1900-01-01) | +| **[date_format](/openmldb_sql/Files/udfs_8h.md#function-date-format)**()|
Formats the date value according to the format string. | +| **[datediff](/openmldb_sql/Files/udfs_8h.md#function-datediff)**()|
days difference from date1 to date2 | +| **[day](/openmldb_sql/Files/udfs_8h.md#function-day)**()| | +| **[dayofmonth](/openmldb_sql/Files/udfs_8h.md#function-dayofmonth)**()|
Return the day of the month for a timestamp or date. | +| **[dayofweek](/openmldb_sql/Files/udfs_8h.md#function-dayofweek)**()|
Return the day of week for a timestamp or date. | +| **[dayofyear](/openmldb_sql/Files/udfs_8h.md#function-dayofyear)**()|
Return the day of year for a timestamp or date. Returns 0 given an invalid date. | +| **[degrees](/openmldb_sql/Files/udfs_8h.md#function-degrees)**()|
Convert radians to degrees. | +| **[distinct_count](/openmldb_sql/Files/udfs_8h.md#function-distinct-count)**()|
Compute number of distinct values. | +| **[double](/openmldb_sql/Files/udfs_8h.md#function-double)**()|
Cast string expression to double. | +| **[drawdown](/openmldb_sql/Files/udfs_8h.md#function-drawdown)**()|
Compute drawdown of values. | +| **[earth_distance](/openmldb_sql/Files/udfs_8h.md#function-earth-distance)**()|
Returns the great circle distance between two points on the surface of the Earth. Km as return unit. add a minus (-) sign if heading west (W) or south (S). | +| **[entropy](/openmldb_sql/Files/udfs_8h.md#function-entropy)**()|
Calculate Shannon entropy of a column of values. Null values are skipped. | +| **[ew_avg](/openmldb_sql/Files/udfs_8h.md#function-ew-avg)**()|
Compute exponentially-weighted average of values. It's equivalent to pandas ewm(alpha={alpha}, adjust=True, ignore_na=True, com=None, span=None, halflife=None, min_periods=0) | +| **[exp](/openmldb_sql/Files/udfs_8h.md#function-exp)**()|
Return the value of e (the base of natural logarithms) raised to the power of expr. | +| **[farm_fingerprint](/openmldb_sql/Files/udfs_8h.md#function-farm-fingerprint)**()| | +| **[first_value](/openmldb_sql/Files/udfs_8h.md#function-first-value)**()|
Returns the value of expr from the latest row (last row) of the window frame. | +| **[float](/openmldb_sql/Files/udfs_8h.md#function-float)**()|
Cast string expression to float. | +| **[floor](/openmldb_sql/Files/udfs_8h.md#function-floor)**()|
Return the largest integer value not less than the expr. | +| **[get_json_object](/openmldb_sql/Files/udfs_8h.md#function-get-json-object)**()|
Extracts a JSON object from [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901)| +| **[hash64](/openmldb_sql/Files/udfs_8h.md#function-hash64)**()|
Returns a hash value of the arguments. It is not a cryptographic hash function and should not be used as such. | +| **[hex](/openmldb_sql/Files/udfs_8h.md#function-hex)**()|
Convert integer to hexadecimal. | +| **[hour](/openmldb_sql/Files/udfs_8h.md#function-hour)**()|
Return the hour for a timestamp. | +| **[identity](/openmldb_sql/Files/udfs_8h.md#function-identity)**()|
Return value. | +| **[if_null](/openmldb_sql/Files/udfs_8h.md#function-if-null)**()|
If input is not null, return input value; else return default value. | +| **[ifnull](/openmldb_sql/Files/udfs_8h.md#function-ifnull)**()| | +| **[ilike_match](/openmldb_sql/Files/udfs_8h.md#function-ilike-match)**()|
pattern match same as ILIKE predicate | +| **[inc](/openmldb_sql/Files/udfs_8h.md#function-inc)**()|
Return expression + 1. | +| **[int](/openmldb_sql/Files/udfs_8h.md#function-int)**()| | +| **[int16](/openmldb_sql/Files/udfs_8h.md#function-int16)**()|
Cast string expression to int16. | +| **[int32](/openmldb_sql/Files/udfs_8h.md#function-int32)**()|
Cast string expression to int32. | +| **[int64](/openmldb_sql/Files/udfs_8h.md#function-int64)**()|
Cast string expression to int64. | +| **[is_null](/openmldb_sql/Files/udfs_8h.md#function-is-null)**()|
Check if input value is null, return bool. | +| **[isnull](/openmldb_sql/Files/udfs_8h.md#function-isnull)**()| | +| **[join](/openmldb_sql/Files/udfs_8h.md#function-join)**()|
For each string value from specified column of window, join by delimeter. Null values are skipped. | +| **[json_array_length](/openmldb_sql/Files/udfs_8h.md#function-json-array-length)**()|
Returns the number of elements in the outermost JSON array. | +| **[lag](/openmldb_sql/Files/udfs_8h.md#function-lag)**()|
Returns value evaluated at the row that is offset rows before the current row within the partition. Offset is evaluated with respect to the current row. | +| **[last_day](/openmldb_sql/Files/udfs_8h.md#function-last-day)**()|
Return the last day of the month to which the date belongs to. | +| **[lcase](/openmldb_sql/Files/udfs_8h.md#function-lcase)**()|
Convert all the characters to lowercase. Note that characters with values > 127 are simply returned. | +| **[like_match](/openmldb_sql/Files/udfs_8h.md#function-like-match)**()|
pattern match same as LIKE predicate | +| **[list_except_by_key](/openmldb_sql/Files/udfs_8h.md#function-list-except-by-key)**()|
Return list of elements in list1 but keys not in except_str. | +| **[list_except_by_value](/openmldb_sql/Files/udfs_8h.md#function-list-except-by-value)**()|
Return list of elements in list1 but values not in except_str. | +| **[ln](/openmldb_sql/Files/udfs_8h.md#function-ln)**()|
Return the natural logarithm of expr. | +| **[log](/openmldb_sql/Files/udfs_8h.md#function-log)**()|
log(base, expr) If called with one parameter, this function returns the natural logarithm of expr. If called with two parameters, this function returns the logarithm of expr to the base. | +| **[log10](/openmldb_sql/Files/udfs_8h.md#function-log10)**()|
Return the base-10 logarithm of expr. | +| **[log2](/openmldb_sql/Files/udfs_8h.md#function-log2)**()|
Return the base-2 logarithm of expr. | +| **[lower](/openmldb_sql/Files/udfs_8h.md#function-lower)**()| | +| **[make_tuple](/openmldb_sql/Files/udfs_8h.md#function-make-tuple)**()| | +| **[max](/openmldb_sql/Files/udfs_8h.md#function-max)**()|
Compute maximum of values. | +| **[max_cate](/openmldb_sql/Files/udfs_8h.md#function-max-cate)**()|
Compute maximum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[max_cate_where](/openmldb_sql/Files/udfs_8h.md#function-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[max_where](/openmldb_sql/Files/udfs_8h.md#function-max-where)**()|
Compute maximum of values match specified condition. | +| **[maximum](/openmldb_sql/Files/udfs_8h.md#function-maximum)**()|
Compute maximum of two arguments. | +| **[median](/openmldb_sql/Files/udfs_8h.md#function-median)**()|
Compute the median of values. | +| **[min](/openmldb_sql/Files/udfs_8h.md#function-min)**()|
Compute minimum of values. | +| **[min_cate](/openmldb_sql/Files/udfs_8h.md#function-min-cate)**()|
Compute minimum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[min_cate_where](/openmldb_sql/Files/udfs_8h.md#function-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[min_where](/openmldb_sql/Files/udfs_8h.md#function-min-where)**()|
Compute minimum of values match specified condition. | +| **[minimum](/openmldb_sql/Files/udfs_8h.md#function-minimum)**()|
Compute minimum of two arguments. | +| **[minute](/openmldb_sql/Files/udfs_8h.md#function-minute)**()|
Return the minute for a timestamp. | +| **[month](/openmldb_sql/Files/udfs_8h.md#function-month)**()|
Return the month part of a timestamp or date. | +| **[nth_value_where](/openmldb_sql/Files/udfs_8h.md#function-nth-value-where)**()|
Returns the value of expr from the idx th row matches the condition. | +| **[nvl](/openmldb_sql/Files/udfs_8h.md#function-nvl)**()| | +| **[nvl2](/openmldb_sql/Files/udfs_8h.md#function-nvl2)**()|
nvl2(expr1, expr2, expr3) - Returns expr2 if expr1 is not null, or expr3 otherwise. | +| **[pmod](/openmldb_sql/Files/udfs_8h.md#function-pmod)**()|
Compute pmod of two arguments. If any param is NULL, output NULL. If divisor is 0, output NULL. | +| **[pow](/openmldb_sql/Files/udfs_8h.md#function-pow)**()|
Return the value of expr1 to the power of expr2. | +| **[power](/openmldb_sql/Files/udfs_8h.md#function-power)**()| | +| **[radians](/openmldb_sql/Files/udfs_8h.md#function-radians)**()|
Returns the argument X, converted from degrees to radians. (Note that π radians equals 180 degrees.) | +| **[regexp_like](/openmldb_sql/Files/udfs_8h.md#function-regexp-like)**()|
pattern match same as RLIKE predicate (based on RE2) | +| **[replace](/openmldb_sql/Files/udfs_8h.md#function-replace)**()|
replace(str, search[, replace]) - Replaces all occurrences of `search` with `replace`| +| **[reverse](/openmldb_sql/Files/udfs_8h.md#function-reverse)**()|
Returns the reversed given string. | +| **[round](/openmldb_sql/Files/udfs_8h.md#function-round)**()|
Returns expr rounded to d decimal places using HALF_UP rounding mode. | +| **[second](/openmldb_sql/Files/udfs_8h.md#function-second)**()|
Return the second for a timestamp. | +| **[sin](/openmldb_sql/Files/udfs_8h.md#function-sin)**()|
Return the sine of expr. | +| **[size](/openmldb_sql/Files/udfs_8h.md#function-size)**()|
Get the size of a List (e.g., result of split) | +| **[smallint](/openmldb_sql/Files/udfs_8h.md#function-smallint)**()| | +| **[split](/openmldb_sql/Files/udfs_8h.md#function-split)**()|
Split string to list by delimeter. Null values are skipped. | +| **[split_array](/openmldb_sql/Files/udfs_8h.md#function-split-array)**()|
Split string to array of string by delimeter. | +| **[split_by_key](/openmldb_sql/Files/udfs_8h.md#function-split-by-key)**()|
Split string by delimeter and split each segment as kv pair, then add each key to output list. Null or illegal segments are skipped. | +| **[split_by_value](/openmldb_sql/Files/udfs_8h.md#function-split-by-value)**()|
Split string by delimeter and split each segment as kv pair, then add each value to output list. Null or illegal segments are skipped. | +| **[sqrt](/openmldb_sql/Files/udfs_8h.md#function-sqrt)**()|
Return square root of expr. | +| **[std](/openmldb_sql/Files/udfs_8h.md#function-std)**()| | +| **[stddev](/openmldb_sql/Files/udfs_8h.md#function-stddev)**()|
Compute sample standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / (n-1) )`| +| **[stddev_pop](/openmldb_sql/Files/udfs_8h.md#function-stddev-pop)**()|
Compute population standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / n )`| +| **[stddev_samp](/openmldb_sql/Files/udfs_8h.md#function-stddev-samp)**()| | +| **[strcmp](/openmldb_sql/Files/udfs_8h.md#function-strcmp)**()|
Returns 0 if the strings are the same, -1 if the first argument is smaller than the second according to the current sort order, and 1 otherwise. | +| **[string](/openmldb_sql/Files/udfs_8h.md#function-string)**()|
Return string converted from timestamp expression. | +| **[substr](/openmldb_sql/Files/udfs_8h.md#function-substr)**()| | +| **[substring](/openmldb_sql/Files/udfs_8h.md#function-substring)**()|
Return a substring `len` characters long from string str, starting at position `pos`. Alias function: `substr`| +| **[sum](/openmldb_sql/Files/udfs_8h.md#function-sum)**()|
Compute sum of values. | +| **[sum_cate](/openmldb_sql/Files/udfs_8h.md#function-sum-cate)**()|
Compute sum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[sum_cate_where](/openmldb_sql/Files/udfs_8h.md#function-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[sum_where](/openmldb_sql/Files/udfs_8h.md#function-sum-where)**()|
Compute sum of values match specified condition. | +| **[tan](/openmldb_sql/Files/udfs_8h.md#function-tan)**()|
Return the tangent of expr. | +| **[timestamp](/openmldb_sql/Files/udfs_8h.md#function-timestamp)**()|
Cast int64, date or string expression to timestamp. | +| **[top](/openmldb_sql/Files/udfs_8h.md#function-top)**()|
Compute top k of values and output string separated by comma. The outputs are sorted in desc order. | +| **[top1_ratio](/openmldb_sql/Files/udfs_8h.md#function-top1-ratio)**()|
Compute the top1 occurring value's ratio. | +| **[top_n_key_avg_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_count_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_max_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_min_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_ratio_cate](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | +| **[top_n_key_sum_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_avg_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_count_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_max_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_min_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_ratio_cate](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | +| **[top_n_value_sum_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[topn_frequency](/openmldb_sql/Files/udfs_8h.md#function-topn-frequency)**()|
Return the topN keys sorted by their frequency. | +| **[truncate](/openmldb_sql/Files/udfs_8h.md#function-truncate)**()|
Return the nearest integer that is not greater in magnitude than the expr. | +| **[ucase](/openmldb_sql/Files/udfs_8h.md#function-ucase)**()|
Convert all the characters to uppercase. Note that characters values > 127 are simply returned. | +| **[unhex](/openmldb_sql/Files/udfs_8h.md#function-unhex)**()|
Convert hexadecimal to binary string. | +| **[unix_timestamp](/openmldb_sql/Files/udfs_8h.md#function-unix-timestamp)**()|
Cast date or string expression to unix_timestamp. If empty string or NULL is provided, return current timestamp. | +| **[upper](/openmldb_sql/Files/udfs_8h.md#function-upper)**()| | +| **[var_pop](/openmldb_sql/Files/udfs_8h.md#function-var-pop)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / n`| +| **[var_samp](/openmldb_sql/Files/udfs_8h.md#function-var-samp)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / (n-1)`| +| **[variance](/openmldb_sql/Files/udfs_8h.md#function-variance)**()| | +| **[week](/openmldb_sql/Files/udfs_8h.md#function-week)**()| | +| **[weekofyear](/openmldb_sql/Files/udfs_8h.md#function-weekofyear)**()|
Return the week of year for a timestamp or date. | +| **[window_split](/openmldb_sql/Files/udfs_8h.md#function-window-split)**()|
For each string value from specified column of window, split by delimeter and add segment to output list. Null values are skipped. | +| **[window_split_by_key](/openmldb_sql/Files/udfs_8h.md#function-window-split-by-key)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each key to output list. Null and illegal segments are skipped. | +| **[window_split_by_value](/openmldb_sql/Files/udfs_8h.md#function-window-split-by-value)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each value to output list. Null and illegal segments are skipped. | +| **[year](/openmldb_sql/Files/udfs_8h.md#function-year)**()|
Return the year part of a timestamp or date. | ## Functions Documentation @@ -501,13 +501,13 @@ Compute average of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -541,13 +541,13 @@ Compute average of values grouped by category key and output string. Each group Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -586,13 +586,13 @@ Compute average of values matching specified condition grouped by category key a Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -634,13 +634,13 @@ Compute average of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -884,7 +884,7 @@ SELECT COS(0); -* The value returned by [cos()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-cos) is always in the range: -1 to 1. +* The value returned by [cos()](/openmldb_sql/Files/udfs_8h.md#function-cos) is always in the range: -1 to 1. **Supported Types**: @@ -946,13 +946,13 @@ Compute number of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -987,13 +987,13 @@ Compute count of values grouped by category key and output string. Each group is Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -1032,13 +1032,13 @@ Compute count of values matching specified condition grouped by category key and Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -1080,13 +1080,13 @@ Compute number of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -1178,7 +1178,12 @@ Supported date string style: * yyyy-mm-dd * yyyymmdd -* yyyy-mm-dd hh:mm:ss +* yyyy-mm-dd HH:MM:SS +* yyyy-mm-ddTHH:MM:SS.fff+HH:MM (RFC3399 format) + +Dates from string are transformed into the same time zone (which is currently always UTC+8) before differentiation, dates from date type by default is at UTC+8, you may see a +1/-1 difference if the two date string have different time zones. + +Hint: since openmldb date type limits range from year 1900, to datadiff from/to a date before 1900, pass it as string. Example: @@ -1225,7 +1230,7 @@ Return the day of the month for a timestamp or date. 0.1.0 -Note: This function equals the `[day()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-day)` function. +Note: This function equals the `[day()](/openmldb_sql/Files/udfs_8h.md#function-day)` function. Example: @@ -1259,7 +1264,7 @@ Return the day of week for a timestamp or date. 0.4.0 -Note: This function equals the `[week()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-week)` function. +Note: This function equals the `[week()](/openmldb_sql/Files/udfs_8h.md#function-week)` function. Example: @@ -1369,13 +1374,13 @@ Compute number of distinct values. Example: -| value | +| value | | -------- | -| 0 | -| 0 | -| 2 | -| 2 | -| 4 | +| 0 | +| 0 | +| 2 | +| 2 | +| 4 | ```sql @@ -1445,14 +1450,14 @@ It requires that all values are non-negative. Negative values will be ignored. Example: -| value | +| value | | -------- | -| 1 | -| 8 | -| 5 | -| 2 | -| 10 | -| 4 | +| 1 | +| 8 | +| 5 | +| 2 | +| 10 | +| 4 | ```sql @@ -1563,13 +1568,13 @@ It requires that values are ordered so that it can only be used with WINDOW (PAR Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -1647,11 +1652,11 @@ window w as (partition by gp order by ts rows between 3 preceding and current ro ``` -| id | gp | ts | agg | +| id | gp | ts | agg | | -------- | -------- | -------- | -------- | -| 1 | 100 | 98 | 98 | -| 2 | 100 | 99 | 99 | -| 3 | 100 | 100 | 100 | +| 1 | 100 | 98 | 98 | +| 2 | 100 | 99 | 99 | +| 3 | 100 | 100 | 100 | @@ -2246,21 +2251,21 @@ Returns value evaluated at the row that is offset rows before the current row wi * **offset** The number of rows forwarded from the current row, must not negative -Note: This function equals the `[at()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-at)` function. +Note: This function equals the `[at()](/openmldb_sql/Files/udfs_8h.md#function-at)` function. -The offset in window is `nth_value()`, not `[lag()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lag)/at()`. The old `[at()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-at)`(version < 0.5.0) is start from the last row of window(may not be the current row), it's more like `nth_value()` +The offset in window is `nth_value()`, not `[lag()](/openmldb_sql/Files/udfs_8h.md#function-lag)/at()`. The old `[at()](/openmldb_sql/Files/udfs_8h.md#function-at)`(version < 0.5.0) is start from the last row of window(may not be the current row), it's more like `nth_value()` Example: -| c1 | c2 | +| c1 | c2 | | -------- | -------- | -| 0 | 1 | -| 1 | 1 | -| 2 | 2 | -| 3 | 2 | -| 4 | 2 | +| 0 | 1 | +| 1 | 1 | +| 2 | 2 | +| 3 | 2 | +| 4 | 2 | ```sql @@ -2648,13 +2653,13 @@ Compute maximum of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2691,13 +2696,13 @@ Compute maximum of values grouped by category key and output string. Each group Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -2736,13 +2741,13 @@ Compute maximum of values matching specified condition grouped by category key a Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -2784,13 +2789,13 @@ Compute maximum of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2856,12 +2861,12 @@ Compute the median of values. Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2898,13 +2903,13 @@ Compute minimum of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2941,13 +2946,13 @@ Compute minimum of values grouped by category key and output string. Each group Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -2986,14 +2991,14 @@ Compute minimum of values matching specified condition grouped by category key a Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 1 | true | y | -| 4 | true | x | -| 3 | true | y | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 1 | true | y | +| 4 | true | x | +| 3 | true | y | ```sql @@ -3035,13 +3040,13 @@ Compute minimum of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -3171,12 +3176,12 @@ select col1, cond, gp, nth_value_where(col1, 2, cond) over (partition by gp orde ``` -| col1 | cond | gp | agg | +| col1 | cond | gp | agg | | -------- | -------- | -------- | -------- | -| 1 | true | 100 | NULL | -| 2 | false | 100 | NULL | -| 3 | NULL | 100 | NULL | -| 4 | true | 100 | 4 | +| 1 | true | 100 | NULL | +| 2 | false | 100 | NULL | +| 3 | NULL | 100 | NULL | +| 4 | true | 100 | 4 | @@ -3563,7 +3568,7 @@ SELECT SIN(0); -* The value returned by [sin()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sin) is always in the range: -1 to 1. +* The value returned by [sin()](/openmldb_sql/Files/udfs_8h.md#function-sin) is always in the range: -1 to 1. **Supported Types**: @@ -3805,12 +3810,12 @@ Alias function: `std`, `stddev_samp` Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -3847,12 +3852,12 @@ Compute population standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -4008,13 +4013,13 @@ Compute sum of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -4048,13 +4053,13 @@ Compute sum of values grouped by category key and output string. Each group is r Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -4093,13 +4098,13 @@ Compute sum of values matching specified condition grouped by category key and o Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -4141,13 +4146,13 @@ Compute sum of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -4257,13 +4262,13 @@ Compute top k of values and output string separated by comma. The outputs are so Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | +| 4 | ```sql @@ -4314,11 +4319,11 @@ SELECT key, top1_ratio(key) over () as ratio FROM t1; ``` -| key | ratio | +| key | ratio | | -------- | -------- | -| 1 | 1.0 | -| 2 | 0.5 | -| NULL | 0.5 | +| 1 | 1.0 | +| 2 | 0.5 | +| NULL | 0.5 | @@ -4355,15 +4360,15 @@ Compute average of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4415,15 +4420,15 @@ Compute count of values matching specified condition grouped by category key. Ou Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4475,15 +4480,15 @@ Compute maximum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4535,15 +4540,15 @@ Compute minimum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4597,15 +4602,15 @@ For each group, ratio value is `value` expr count matches condtion divide total Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 2 | true | x | -| 4 | true | x | -| 1 | true | y | -| 3 | false | y | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 2 | true | x | +| 4 | true | x | +| 1 | true | y | +| 3 | false | y | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4656,15 +4661,15 @@ Compute sum of values matching specified condition grouped by category key. Outp Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4716,15 +4721,15 @@ Compute average of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | false | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | false | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4776,15 +4781,15 @@ Compute count of values matching specified condition grouped by category key. Ou Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | true | x | -| 3 | false | y | -| 4 | true | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | true | x | +| 3 | false | y | +| 4 | true | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4836,15 +4841,15 @@ Compute maximum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4896,15 +4901,15 @@ Compute minimum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | true | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | true | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4958,15 +4963,15 @@ For each group, ratio value is `value` expr count matches condtion divide total Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 2 | true | x | -| 4 | true | x | -| 1 | true | y | -| 3 | false | y | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 2 | true | x | +| 4 | true | x | +| 1 | true | y | +| 3 | false | y | +| 5 | true | z | +| 6 | true | z | ```sql @@ -5017,15 +5022,15 @@ Compute sum of values matching specified condition grouped by category key. Outp Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | false | y | -| 4 | true | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | false | y | +| 4 | true | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -5240,11 +5245,11 @@ Compute population variance of values, i.e., `sum((x_i - avg)^2) / n` Example: -| value | +| value | | -------- | -| 0 | -| 3 | -| 6 | +| 0 | +| 3 | +| 6 | ```sql @@ -5281,11 +5286,11 @@ Compute population variance of values, i.e., `sum((x_i - avg)^2) / (n-1)` Example: -| value | +| value | | -------- | -| 0 | -| 3 | -| 6 | +| 0 | +| 3 | +| 6 | ```sql diff --git a/docs/en/use_case/JD_recommendation_en.md b/docs/en/use_case/JD_recommendation_en.md index 3a3a7df6f0a..089bb7e810b 100644 --- a/docs/en/use_case/JD_recommendation_en.md +++ b/docs/en/use_case/JD_recommendation_en.md @@ -52,7 +52,7 @@ Oneflow-serving:https://github.com/Oneflow-Inc/serving/tree/ce5d667468b6b3ba66 Pull the OpenMLDB docker image and run. ```bash -docker run -dit --name=openmldb --network=host -v $demodir:/work/oneflow_demo 4pdosc/openmldb:0.8.3 bash +docker run -dit --name=openmldb --network=host -v $demodir:/work/oneflow_demo 4pdosc/openmldb:0.8.4 bash docker exec -it openmldb bash ``` diff --git a/docs/en/use_case/airflow_provider_demo.md b/docs/en/use_case/airflow_provider_demo.md index bf430b7cce2..9019ba2c5a6 100644 --- a/docs/en/use_case/airflow_provider_demo.md +++ b/docs/en/use_case/airflow_provider_demo.md @@ -34,7 +34,7 @@ For the newest version, please visit [GitHub example_dags](https://github.com/4p - Please project the previously downloaded files to the path `/work/airflow/dags`, where Airflow will access for the DAG. ``` -docker run -p 8080:8080 -v `pwd`/airflow_demo_files:/work/airflow/dags -it 4pdosc/openmldb:0.8.3 bash +docker run -p 8080:8080 -v `pwd`/airflow_demo_files:/work/airflow/dags -it 4pdosc/openmldb:0.8.4 bash ``` #### 0.3 Download and Install the Airflow and the Airflow OpenMLDB Provider diff --git a/docs/en/use_case/dolphinscheduler_task_demo.md b/docs/en/use_case/dolphinscheduler_task_demo.md index 8f3d9b51e97..5a4a8e6bfb8 100644 --- a/docs/en/use_case/dolphinscheduler_task_demo.md +++ b/docs/en/use_case/dolphinscheduler_task_demo.md @@ -33,7 +33,7 @@ In addition to the feature engineering done by OpenMLDB, the prediction also req The demo can run on MacOS or Linux, the OpenMLDB docker image is recommended. We'll start OpenMLDB and DolphinScheduler in the same container, expose the DolphinScheduler web port: ``` -docker run -it -p 12345:12345 4pdosc/openmldb:0.8.3 bash +docker run -it -p 12345:12345 4pdosc/openmldb:0.8.4 bash ``` ```{attention} diff --git a/docs/en/use_case/kafka_connector_demo.md b/docs/en/use_case/kafka_connector_demo.md index be6c17e9fae..70288b0001d 100644 --- a/docs/en/use_case/kafka_connector_demo.md +++ b/docs/en/use_case/kafka_connector_demo.md @@ -22,7 +22,7 @@ For OpenMLDB Kafka Connector implementation, please refer to [extensions/kafka-c This article will start the OpenMLDB in docker container, so there is no need to download the OpenMLDB separately. Moreover, Kafka and connector can be started in the same container. We recommend that you save the three downloaded packages to the same directory. Let's assume that the packages are in the `/work/kafka` directory. ``` -docker run -it -v `pwd`:/work/kafka --name openmldb 4pdosc/openmldb:0.8.3 bash +docker run -it -v `pwd`:/work/kafka --name openmldb 4pdosc/openmldb:0.8.4 bash ``` ### Steps diff --git a/docs/en/use_case/lightgbm_demo.md b/docs/en/use_case/lightgbm_demo.md index f4e602373a6..c1310fdea66 100644 --- a/docs/en/use_case/lightgbm_demo.md +++ b/docs/en/use_case/lightgbm_demo.md @@ -13,7 +13,7 @@ Note that: (1) this case is based on the OpenMLDB cluster version for tutorial d - Pull the OpenMLDB docker image and run the corresponding container: ```bash -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` The image is preinstalled with OpenMLDB and preset with all scripts, third-party libraries, open-source tools and training data required for this case. @@ -152,7 +152,7 @@ Assuming that the model produced by the features designed in Section 2.3 in the ```sql > USE demo_db; > SET @@execute_mode='online'; -> DEPLOY demo SELECT trip_duration, passenger_count, +> DEPLOY demo OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') SELECT trip_duration, passenger_count, sum(pickup_latitude) OVER w AS vendor_sum_pl, max(pickup_latitude) OVER w AS vendor_max_pl, min(pickup_latitude) OVER w AS vendor_min_pl, diff --git a/docs/en/use_case/pulsar_connector_demo.md b/docs/en/use_case/pulsar_connector_demo.md index 194195da3fd..dd3733d291b 100644 --- a/docs/en/use_case/pulsar_connector_demo.md +++ b/docs/en/use_case/pulsar_connector_demo.md @@ -29,7 +29,7 @@ Only OpenMLDB cluster mode can be the sink dist, and only write to online storag We recommend that you use ‘host network’ to run docker. And bind volume ‘files’ too. The sql scripts are in it. ``` -docker run -dit --network host -v `pwd`/files:/work/pulsar_files --name openmldb 4pdosc/openmldb:0.8.3 bash +docker run -dit --network host -v `pwd`/files:/work/pulsar_files --name openmldb 4pdosc/openmldb:0.8.4 bash docker exec -it openmldb bash ``` ```{note} diff --git a/docs/en/use_case/talkingdata_demo.md b/docs/en/use_case/talkingdata_demo.md index 4c0370d375f..a61fbaa95ce 100644 --- a/docs/en/use_case/talkingdata_demo.md +++ b/docs/en/use_case/talkingdata_demo.md @@ -13,7 +13,7 @@ It is recommended to run this demo in Docker. Please make sure that OpenMLDB and **Start the OpenMLDB Docker Image** ``` -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` #### 1.1.2 Run Locally diff --git a/docs/poetry.lock b/docs/poetry.lock index 01f5d11fa68..724b4f19340 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -45,13 +45,13 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -670,17 +670,17 @@ test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "urllib3" -version = "1.26.12" +version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] diff --git a/docs/zh/deploy/compile.md b/docs/zh/deploy/compile.md index aec38f6a5a3..6f08780e3e9 100644 --- a/docs/zh/deploy/compile.md +++ b/docs/zh/deploy/compile.md @@ -4,7 +4,7 @@ 此节介绍在官方编译镜像 [hybridsql](https://hub.docker.com/r/4pdosc/hybridsql) 中编译 OpenMLDB,主要可以用于在容器内试用和开发目的。镜像内置了编译所需要的工具和依赖,因此不需要额外的步骤单独配置它们。关于基于非 docker 的编译使用方式,请参照下面的 [从源码全量编译](#从源码全量编译) 章节。 -对于编译镜像的版本,需要注意拉取的镜像版本和 [OpenMLDB 发布版本](https://github.com/4paradigm/OpenMLDB/releases)保持一致。以下例子演示了在 `hybridsql:0.8.3` 镜像版本上编译 [OpenMLDB v0.8.3](https://github.com/4paradigm/OpenMLDB/releases/tag/v0.8.3) 的代码,如果要编译最新 `main` 分支的代码,则需要拉取 `hybridsql:latest` 版本镜像。 +对于编译镜像的版本,需要注意拉取的镜像版本和 [OpenMLDB 发布版本](https://github.com/4paradigm/OpenMLDB/releases)保持一致。以下例子演示了在 `hybridsql:0.8.4` 镜像版本上编译 [OpenMLDB v0.8.4](https://github.com/4paradigm/OpenMLDB/releases/tag/v0.8.4) 的代码,如果要编译最新 `main` 分支的代码,则需要拉取 `hybridsql:latest` 版本镜像。 1. 下载 docker 镜像 ```bash @@ -16,10 +16,10 @@ docker run -it 4pdosc/hybridsql:0.8 bash ``` -3. 在 docker 容器内, 克隆 OpenMLDB, 并切换分支到 v0.8.3 +3. 在 docker 容器内, 克隆 OpenMLDB, 并切换分支到 v0.8.4 ```bash cd ~ - git clone -b v0.8.3 https://github.com/4paradigm/OpenMLDB.git + git clone -b v0.8.4 https://github.com/4paradigm/OpenMLDB.git ``` 4. 在 docker 容器内编译 OpenMLDB @@ -110,7 +110,7 @@ make CMAKE_BUILD_TYPE=Debug - CMAKE_EXTRA_FLAGS: 传递给 cmake 的额外参数 - 默认: ‘’ + 默认: '' - BUILD_BUNDLED: 从源码编译 thirdparty 依赖,而不是下载预编译包 @@ -124,6 +124,9 @@ make CMAKE_BUILD_TYPE=Debug 默认: all +- THIRD_PARTY_CMAKE_FLAGS: 编译thirdparty时可以配置额外参数。例如,配置每个thirdparty项目并发编译,`THIRD_PARTY_CMAKE_FLAGS=-DMAKEOPTS=-j8`。thirdparty不受NPROC影响,thirdparty的多项目将会串行执行。 + 默认:'' + ### 并发编译Java SDK ``` @@ -141,7 +144,7 @@ make SQL_JAVASDK_ENABLE=ON NPROC=4 1. 下载预编译的OpenMLDB Spark发行版。 ```bash -wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.3/spark-3.2.1-bin-openmldbspark.tgz +wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.4/spark-3.2.1-bin-openmldbspark.tgz ``` 或者下载源代码并从头开始编译。 @@ -185,14 +188,25 @@ docker run -it -v`pwd`:/root/OpenMLDB ghcr.io/4paradigm/centos6_gcc7_hybridsql b ```bash cd OpenMLDB bash steps/centos6_build.sh +# THIRD_PARTY_CMAKE_FLAGS=-DMAKEOPTS=-j8 bash steps/centos6_build.sh # run fast when build single project # OPENMLDB_SOURCE=true bash steps/centos6_build.sh -# SQL_JAVASDK_ENABLE=ON SQL_PYSDK_ENABLE=ON NRPOC=8 bash steps/centos6_build.sh +# SQL_JAVASDK_ENABLE=ON SQL_PYSDK_ENABLE=ON NPROC=8 bash steps/centos6_build.sh # NPROC will build openmldb in parallel, thirdparty should use THIRD_PARTY_CMAKE_FLAGS ``` +本地2.20GHz CPU,SSD硬盘,32线程编译三方库与OpenMLDB主体,耗时参考: +`THIRD_PARTY_CMAKE_FLAGS=-DMAKEOPTS=-j32 SQL_JAVASDK_ENABLE=ON SQL_PYSDK_ENABLE=ON NPROC=32 bash steps/centos6_build.sh` +- thirdparty(不包括下载src时间)~40m:zetasql打patch 13m,所有thirdparty编译30m +- OpenMLDB 本体,包括python和java native,~12min + #### 云编译 -Fork OpenMLDB仓库后,可以使用在`Actions`中触发workflow `Other OS Build`,编译产出在`Actions`的`Artifacts`中。workflow 配置 `os name`为`centos6`, -如果不需要Java或Python SDK,可配置`java sdk enable`或`python sdk enable`为`OFF`,节约编译时间。 +Fork OpenMLDB仓库后,可以使用在`Actions`中触发workflow `Other OS Build`,编译产出在`Actions`的`Artifacts`中。workflow 配置方式: +- 不要更换`Use workflow from`为某个tag,可以是其他分支。 +- 选择`os name`为`centos6`。 +- 如果不是编译main分支,在`The branch, tag or SHA to checkout, otherwise use the branch`中填写想要的分支名、Tag(e.g. v0.8.4)或SHA。 +- 编译产出在触发后的runs界面中,参考[成功产出的runs链接](https://github.com/4paradigm/OpenMLDB/actions/runs/6044951902)。 + - 一定会产出openmldb binary文件。 + - 如果不需要Java或Python SDK,可配置`java sdk enable`或`python sdk enable`为`OFF`,节约编译时间。 此编译流程需要从源码编译thirdparty,且资源较少,无法开启较高的并发编译。因此编译时间较长,大约需要3h5m(2h thirdparty+1h OpenMLDB)。workflow会缓存thirdparty的编译产出,因此第二次编译会快很多(1h15m OpenMLDB)。 diff --git a/docs/zh/deploy/conf.md b/docs/zh/deploy/conf.md index ef05f0c8dc9..de538720e5d 100644 --- a/docs/zh/deploy/conf.md +++ b/docs/zh/deploy/conf.md @@ -9,6 +9,8 @@ # 如果是部署单机版不需要配置zk_cluster和zk_root_path,把这俩配置注释即可. 部署集群版需要配置这两项,一个集群中所有节点的这两个配置必须保持一致 #--zk_cluster=127.0.0.1:7181 #--zk_root_path=/openmldb_cluster +# 配置zk认证的用户名和密码, 用冒号分割 +#--zk_cert=user:passwd # 单机版需要指定tablet的地址, 集群版此配置可忽略 --tablet=127.0.0.1:9921 # 配置log目录 @@ -76,6 +78,8 @@ # 如果启动集群版需要指定zk的地址和集群在zk的节点路径 #--zk_cluster=127.0.0.1:7181 #--zk_root_path=/openmldb_cluster +# 配置zk认证的用户名和密码, 用冒号分割 +#--zk_cert=user:passwd # 配置线程池大小,建议和cpu核数一致 --thread_pool_size=24 @@ -222,6 +226,8 @@ # 如果部署的openmldb是集群版,需要指定zk地址和集群zk节点目录 #--zk_cluster=127.0.0.1:7181 #--zk_root_path=/openmldb_cluster +# 配置zk认证的用户名和密码, 用冒号分割 +#--zk_cert=user:passwd # 配置日志路径 --openmldb_log_dir=./logs @@ -254,6 +260,7 @@ zookeeper.connection_timeout=5000 zookeeper.max_retries=10 zookeeper.base_sleep_time=1000 zookeeper.max_connect_waitTime=30000 +#zookeeper.cert=user:passwd # Spark Config spark.home= diff --git a/docs/zh/deploy/index.rst b/docs/zh/deploy/index.rst index 29007be2d86..91a3116489e 100644 --- a/docs/zh/deploy/index.rst +++ b/docs/zh/deploy/index.rst @@ -8,6 +8,5 @@ install_deploy conf compile - integrate_hadoop offline_integrate_kubernetes [Alpha]在线引擎基于 Kubernetes 部署 diff --git a/docs/zh/deploy/install_deploy.md b/docs/zh/deploy/install_deploy.md index d060cce3b01..84f3e05ff98 100644 --- a/docs/zh/deploy/install_deploy.md +++ b/docs/zh/deploy/install_deploy.md @@ -47,17 +47,17 @@ strings /lib64/libc.so.6 | grep ^GLIBC_ ### Linux 平台预测试 -由于 Linux 平台的多样性,发布包可能在你的机器上不兼容,请先通过简单的运行测试。比如,下载预编译包 `openmldb-0.8.3-linux.tar.gz` 以后,运行: +由于 Linux 平台的多样性,发布包可能在你的机器上不兼容,请先通过简单的运行测试。比如,下载预编译包 `openmldb-0.8.4-linux.tar.gz` 以后,运行: ``` -tar -zxvf openmldb-0.8.3-linux.tar.gz -./openmldb-0.8.3-linux/bin/openmldb --version +tar -zxvf openmldb-0.8.4-linux.tar.gz +./openmldb-0.8.4-linux/bin/openmldb --version ``` 结果应显示该程序的版本号,类似 ``` -openmldb version 0.8.3-xxxx +openmldb version 0.8.4-xxxx Debug build (NDEBUG not #defined) ``` @@ -171,9 +171,9 @@ DataCollector和SyncTool暂不支持一键部署。请参考手动部署方式 ### 下载OpenMLDB发行版 ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -cd openmldb-0.8.3-linux +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +cd openmldb-0.8.4-linux ``` ### 环境配置 @@ -181,7 +181,7 @@ cd openmldb-0.8.3-linux | 环境变量 | 默认值 | 定义 | |-----------------------------------|------------------------------------|-------------------------------------------------------------------------| -| OPENMLDB_VERSION | 0.8.3 | OpenMLDB版本 | +| OPENMLDB_VERSION | 0.8.4 | OpenMLDB版本 | | OPENMLDB_MODE | standalone | standalone或者cluster | | OPENMLDB_HOME | 当前发行版的根目录 | openmldb发行版根目录 | | SPARK_HOME | $OPENMLDB_HOME/spark | openmldb spark发行版根目录,如果该目录不存在,自动从网上下载 | @@ -348,10 +348,10 @@ bash bin/zkCli.sh -server 172.27.128.33:7181 **1. 下载OpenMLDB部署包** ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-tablet-0.8.3 -cd openmldb-tablet-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-tablet-0.8.4 +cd openmldb-tablet-0.8.4 ``` **2. 修改配置文件`conf/tablet.flags`** ```bash @@ -402,12 +402,12 @@ Start tablet success 在另一台机器启动下一个TabletServer只需在该机器上重复以上步骤。如果是在同一个机器上启动下一个TabletServer,请保证是在另一个目录中,不要重复使用已经启动过TabletServer的目录。 -比如,可以再次解压压缩包(不要cp已经启动过TabletServer的目录,启动后的生成文件会造成影响),并命名目录为`openmldb-tablet-0.8.3-2`。 +比如,可以再次解压压缩包(不要cp已经启动过TabletServer的目录,启动后的生成文件会造成影响),并命名目录为`openmldb-tablet-0.8.4-2`。 ``` -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-tablet-0.8.3-2 -cd openmldb-tablet-0.8.3-2 +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-tablet-0.8.4-2 +cd openmldb-tablet-0.8.4-2 ``` 再修改配置并启动。注意,TabletServer如果都在同一台机器上,请使用不同端口号,否则日志(logs/tablet.WARNING)中将会有"Fail to listen"信息。 @@ -421,10 +421,10 @@ cd openmldb-tablet-0.8.3-2 ``` **1. 下载OpenMLDB部署包** ```` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-ns-0.8.3 -cd openmldb-ns-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-ns-0.8.4 +cd openmldb-ns-0.8.4 ```` **2. 修改配置文件conf/nameserver.flags** ```bash @@ -462,12 +462,12 @@ NameServer 可以只存在一台,如果你需要高可用性,可以部署多 在另一台机器启动下一个 NameServer 只需在该机器上重复以上步骤。如果是在同一个机器上启动下一个 NameServer,请保证是在另一个目录中,不要重复使用已经启动过 namserver 的目录。 -比如,可以再次解压压缩包(不要cp已经启动过 namserver 的目录,启动后的生成文件会造成影响),并命名目录为`openmldb-ns-0.8.3-2`。 +比如,可以再次解压压缩包(不要cp已经启动过 namserver 的目录,启动后的生成文件会造成影响),并命名目录为`openmldb-ns-0.8.4-2`。 ``` -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-ns-0.8.3-2 -cd openmldb-ns-0.8.3-2 +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-ns-0.8.4-2 +cd openmldb-ns-0.8.4-2 ``` 然后再修改配置并启动。 @@ -505,10 +505,10 @@ APIServer负责接收http请求,转发给OpenMLDB集群并返回结果。它 **1. 下载OpenMLDB部署包** ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-apiserver-0.8.3 -cd openmldb-apiserver-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-apiserver-0.8.4 +cd openmldb-apiserver-0.8.4 ``` **2. 修改配置文件conf/apiserver.flags** @@ -563,18 +563,18 @@ TaskManager 可以只存在一台,如果你需要高可用性,可以部署 Spark发行版: ```shell -wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.3/spark-3.2.1-bin-openmldbspark.tgz -# 中国镜像地址:http://43.138.115.238/download/v0.8.3/spark-3.2.1-bin-openmldbspark.tgz +wget https://github.com/4paradigm/spark/releases/download/v3.2.1-openmldb0.8.4/spark-3.2.1-bin-openmldbspark.tgz +# 中国镜像地址:http://43.138.115.238/download/v0.8.4/spark-3.2.1-bin-openmldbspark.tgz tar -zxvf spark-3.2.1-bin-openmldbspark.tgz export SPARK_HOME=`pwd`/spark-3.2.1-bin-openmldbspark/ ``` OpenMLDB部署包: ``` -wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.3/openmldb-0.8.3-linux.tar.gz -tar -zxvf openmldb-0.8.3-linux.tar.gz -mv openmldb-0.8.3-linux openmldb-taskmanager-0.8.3 -cd openmldb-taskmanager-0.8.3 +wget https://github.com/4paradigm/OpenMLDB/releases/download/v0.8.4/openmldb-0.8.4-linux.tar.gz +tar -zxvf openmldb-0.8.4-linux.tar.gz +mv openmldb-0.8.4-linux openmldb-taskmanager-0.8.4 +cd openmldb-taskmanager-0.8.4 ``` **2. 修改配置文件conf/taskmanager.properties** diff --git a/docs/zh/developer/built_in_function_develop_guide.md b/docs/zh/developer/built_in_function_develop_guide.md index 12231384078..cbc186005cf 100644 --- a/docs/zh/developer/built_in_function_develop_guide.md +++ b/docs/zh/developer/built_in_function_develop_guide.md @@ -1034,10 +1034,9 @@ RegisterUdafTemplate("distinct_count") ## 6. 文档管理 -内置函数文档可在 [Built-in Functions](https://openmldb.ai/docs/zh/main/openmldb_sql/functions_and_operators/Files/udfs_8h.html) 查看,它是一个代码生成的 markdown 文件,注意请不要进行直接编辑。 +内置函数文档可在 [Built-in Functions](../openmldb_sql/udfs_8h.md) 查看,它是一个代码生成的 markdown 文件,注意请不要进行直接编辑。 -- 如果需要对新增加的函数添加文档,请参照 2.2.4 配置函数文档 章节,说明了内置函数的文档是在 CPP 源代码中管理的。后续会通过一系列步骤生成如上网页中更加可读的文档, 即`docs/*/openmldb_sql/functions_and_operators/`目录下的内容。 +- 如果需要对新增加的函数添加文档,请参照 2.2.4 配置函数文档 章节,说明了内置函数的文档是在 CPP 源代码中管理的。后续会通过一系列步骤生成如上网页中更加可读的文档, 即`docs/*/openmldb_sql/`目录下的内容。 - 如果需要修改一个已存在函数的文档,可以在文件 `hybridse/src/udf/default_udf_library.cc` 或者 `hybridse/src/udf/default_defs/*_def.cc` 下查找到对应函数的文档说明,进行修改。 OpenMLDB 项目中创建了一个定期天级别的 GitHub Workflow 任务来定期更新这里的相关文档。因此内置函数文档相关的改动只需按照上面的步骤修改对应源代码位置的内容即可,`docs` 目录和网站的内容会随之定期更新。具体的文档生成流程可以查看源代码路径下的 [udf_doxygen](https://github.com/4paradigm/OpenMLDB/tree/main/hybridse/tools/documentation/udf_doxygen)。 - diff --git a/docs/zh/faq/client_faq.md b/docs/zh/faq/client_faq.md new file mode 100644 index 00000000000..894cca02e57 --- /dev/null +++ b/docs/zh/faq/client_faq.md @@ -0,0 +1,88 @@ +# Client FAQ + +## fail to get tablet ... 的错误日志 + +优先检查集群中tablet server是否意外下线,或者在线表是否不可读写。推荐通过[openmldb_tool](../maintain/diagnose.md)诊断,使用`status`(status --diff)和`inspect online`两个检查命令。 +TODO diag tool 测到offline或online表不正常,会输出警告和下一步应该怎么操作? +如果只能手动检查,需要两步: +- `show components`,检查server是否存在在列表中(TaskManager如果下线,将不在表中。Tablet如果下线,将在表中,但状态为offline),以及在列表中的server的状态是否为online。如果存在offline的server,**先将server重启加入集群**。 +- `show table status like '%'`(低版本如果不支持like,需要分别查询系统db和用户db),检查每个表的"Warnings"是否报错。 + +一般会得到`real replica number X does not match the configured replicanum X`等错误,具体错误信息请参考[SHOW TABLE STATUS](../openmldb_sql/ddl/SHOW_TABLE_STATUS.md)。这些错误都说明表目前是有问题的,无法提供正常读写功能,通常是由于Tablet + +## 为什么收到 Reached timeout 的警告日志? +``` +rpc_client.h:xxx] request error. [E1008] Reached timeout=xxxms +``` +这是由于client端本身发送的rpc request的timeout设置小了,client端自己主动断开,注意这是rpc的超时。需要更改通用的`request_timeout`配置。 +1. CLI: 启动时配置`--request_timeout_ms` +2. JAVA/Python SDK: Option或url中调整`SdkOption.requestTimeout` +```{note} +同步的离线命令通常不会出现这个错误,因为同步离线命令的timeout设置为了TaskManager可接受的最长时间。 +``` + +## 为什么收到 Got EOF of Socket 的警告日志? +``` +rpc_client.h:xxx] request error. [E1014]Got EOF of Socket{id=x fd=x addr=xxx} (xx) +``` +这是因为`addr`端主动断开了连接,`addr`的地址大概率是TaskManager。这不代表TaskManager不正常,而是TaskManager端认为这个连接没有活动,超过keepAliveTime了,而主动断开通信channel。 +在0.5.0及以后的版本中,可以调大TaskManager的`server.channel_keep_alive_time`来提高对不活跃channel的容忍度。默认值为1800s(0.5h),特别是使用同步的离线命令时,这个值可能需要适当调大。 +在0.5.0以前的版本中,无法更改此配置,请升级TaskManager版本。 + +## 离线查询结果显示中文为什么乱码? + +在使用离线查询时,可能出现包含中文的查询结果乱码,主要和系统默认编码格式与Spark任务编码格式参数有关。 + +如果出现乱码情况,可以通过添加Spark高级参数`spark.driver.extraJavaOptions=-Dfile.encoding=utf-8`和`spark.executor.extraJavaOptions=-Dfile.encoding=utf-8`来解决。 + +客户端配置方法可参考[客户端Spark配置文件](../reference/client_config/client_spark_config.md),也可以在TaskManager配置文件中添加此项配置。 + +``` +spark.default.conf=spark.driver.extraJavaOptions=-Dfile.encoding=utf-8;spark.executor.extraJavaOptions=-Dfile.encoding=utf-8 +``` + +## 如何配置TaskManager来访问开启Kerberos的Yarn集群? + +如果Yarn集群开启Kerberos认证,TaskManager可以通过添加以下配置来访问开启Kerberos认证的Yarn集群。注意请根据实际配置修改keytab路径以及principal账号。 + +``` +spark.default.conf=spark.yarn.keytab=/tmp/test.keytab;spark.yarn.principal=test@EXAMPLE.COM +``` + +## 如何配置客户端的core日志? + +客户端core日志主要有两种,zk日志和sdk日志(glog日志),两者是独立的。 + +zk日志: +1. CLI:启动时配置`--zk_log_level`调整level,`--zk_log_file`配置日志保存文件。 +2. JAVA/Python SDK:Option或url中使用`zkLogLevel`调整level,`zkLogFile`配置日志保存文件。 + +- `zk_log_level`(int, 默认=0, 即DISABLE_LOGGING): +打印这个等级及**以下**等级的日志。0-禁止所有zk log, 1-error, 2-warn, 3-info, 4-debug。 + +sdk日志(glog日志): +1. CLI:启动时配置`--glog_level`调整level,`--glog_dir`配置日志保存文件。 +2. JAVA/Python SDK:Option或url中使用`glogLevel`调整level,`glogDir`配置日志保存文件。 + +- `glog_level`(int, 默认=1, 即WARNING): +打印这个等级及**以上**等级的日志。 INFO, WARNING, ERROR, and FATAL日志分别对应 0, 1, 2, and 3。 + + +## 插入错误,日志显示`please use getInsertRow with ... first` + +在JAVA client使用InsertPreparedStatement进行插入,或在Python中使用sql和parameter进行插入时,client底层实际有cache影响,第一步`getInsertRow`生成sql cache并返回sql还需要补充的parameter信息,第二步才会真正执行insert,而执行insert需要使用第一步缓存的sql cache。所以,当多线程使用同一个client时,可能因为插入和查询频繁更新cache表,将你想要执行的insert sql cache淘汰掉了,所以会出现好像第一步`getInsertRow`并未执行的样子。 + +目前可以通过调大`maxSqlCacheSize`这一配置项来避免错误。仅JAVA/Python SDK支持配置。 + +## 离线命令Spark报错 + +`java.lang.OutOfMemoryError: Java heap space` + +离线命令的Spark配置默认为`local[*]`,并发较高可能出现OutOfMemoryError错误,请调整`spark.driver.memory`和`spark.executor.memory`两个spark配置项。可以写在TaskManager运行目录的`conf/taskmanager.properties`的`spark.default.conf`并重启TaskManager,或者使用CLI客户端进行配置,参考[客户端Spark配置文件](../reference/client_config/client_spark_config.md)。 +``` +spark.default.conf=spark.driver.memory=16g;spark.executor.memory=16g +``` + +Container killed by YARN for exceeding memory limits. 5 GB of 5 GB physical memory used. Consider boosting spark.yarn.executor.memoryOverhead. + +local时drivermemory diff --git a/docs/zh/faq/index.rst b/docs/zh/faq/index.rst new file mode 100644 index 00000000000..a5d1e94a540 --- /dev/null +++ b/docs/zh/faq/index.rst @@ -0,0 +1,10 @@ +============================= +FAQ +============================= + + +.. toctree:: + :maxdepth: 1 + + client_faq + server_faq diff --git a/docs/zh/faq/server_faq.md b/docs/zh/faq/server_faq.md new file mode 100644 index 00000000000..1b89fd383d6 --- /dev/null +++ b/docs/zh/faq/server_faq.md @@ -0,0 +1,61 @@ +# Server FAQ + +Server中有任何上下线变化或问题,都先openmldb_tool status + inspect online检查下集群是否正常。 + +## 部署和启动 FAQ + +### 1. 如何确认集群已经正常运行? +虽然有一键启动脚本,但由于配置繁多,可能出现“端口已被占用”,“目录无读写权限”等问题。这些问题都是server进程运行之后才能发现,退出后没有及时反馈。(如果配置了监控,可以通过监控直接检查。) +所以,请先确认集群的所有server进程都正常运行。 + +可以通过`ps axu | grep openmldb`或sql命令`show components;`来查询。(注意,如果你使用了守护进程,openmldb server进程可能是在启动停止的循环中,并不代表持续运行,可以通过日志或`show components;`连接时间来确认。) + +如果进程都活着,集群还是表现不正常,需要查询一下server日志。可以优先看WARN和ERROR级日志,很大概率上,它们就是根本原因。 + +### 2. 如果数据没有自动恢复成功怎么办? + +通常情况,当我们重启服务,表中数据会自动进行恢复,但有些情况可能会造成恢复失败,通常失败的情况包括: + +- tablet异常退出 +- 多副本表多个副本所在的tablets同时重启或者重启太快,造成某些`auto_failover`操作还没完成tablet就重启 +- auto_failover设成`false` + +当服务启动成功后,可以通过`gettablestatus`获得所有表的状态: +``` +python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=172.24.4.40:30481 --zk_root_path=/openmldb --cmd=gettablestatus +``` + +如果表中有`Warnings`,可以通过`recoverdata`来自动恢复数据: +``` +python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=172.24.4.40:30481 --zk_root_path=/openmldb --cmd=recoverdata +``` + +## Server FAQ + +### 1. 为什么日志中有 Fail to write into Socket 的警告日志? +``` +http_rpc_protocol.cpp:911] Fail to write into Socket{id=xx fd=xx addr=xxx} (0x7a7ca00): Unknown error 1014 [1014] +``` +这是server端会打印的日志。一般是client端使用了连接池或短连接模式,在RPC超时后会关闭连接,server写回response时发现连接已经关了就报这个错。Got EOF就是指之前已经收到了EOF(对端正常关闭了连接)。client端使用单连接模式server端一般不会报这个。 + +### 2. 表数据的ttl初始设置不合适,如何调整? +这需要使用nsclient来修改,普通client无法做到。nsclient启动方式与命令,见[ns client](../maintain/cli.md#ns-client)。 + +在nsclient中使用命令`setttl`可以更改一个表的ttl,类似 +``` +setttl table_name ttl_type ttl [ttl] [index_name] +``` +可以看到,如果在命令末尾配置index的名字,可以做到只修改单个index的ttl。 +```{caution} +`setttl`的改变不会及时生效,会受到tablet server的配置`gc_interval`的影响。(每台tablet server的配置是独立的,互不影响。) + +举例说明,有一个tablet server的`gc_interval`是1h,那么ttl的配置重载,会在下一次gc的最后时刻进行(最坏情况下,会在1h后重载)。重载ttl的这一次gc就不会按最新ttl来淘汰数据。再下一次gc时才会使用最新ttl进行数据淘汰。 + +所以,**ttl更改后,需要等待两次gc interval的时间才会生效**。请耐心等待。 + +当然,你可以调整tablet server的`gc_interval`,但这个配置无法动态更改,只能重启生效。所以,如果内存压力较大,可以尝试扩容,迁移数据分片,来减少内存压力。不推荐轻易调整`gc_interval`。 +``` + +### 3. 出现警告日志:Last Join right table is empty,这是什么意思? +通常来讲,这是一个正常现象,不代表集群异常。只是runner中join右表为空,是可能的现象,大概率是数据问题。 + diff --git a/docs/zh/index.rst b/docs/zh/index.rst index 1a3fd0deb56..f3b3f63106b 100644 --- a/docs/zh/index.rst +++ b/docs/zh/index.rst @@ -16,3 +16,4 @@ OpenMLDB 文档 (|version|) maintain/index reference/index developer/index + faq/index diff --git a/docs/zh/integration/deploy_integration/OpenMLDB_Byzer_taxi.md b/docs/zh/integration/deploy_integration/OpenMLDB_Byzer_taxi.md index 926c079469d..f3c570fe75b 100644 --- a/docs/zh/integration/deploy_integration/OpenMLDB_Byzer_taxi.md +++ b/docs/zh/integration/deploy_integration/OpenMLDB_Byzer_taxi.md @@ -13,7 +13,7 @@ 执行命令如下: ``` -docker run --network host -dit --name openmldb -v /mlsql/admin/:/byzermnt 4pdosc/openmldb:0.8.3 bash +docker run --network host -dit --name openmldb -v /mlsql/admin/:/byzermnt 4pdosc/openmldb:0.8.4 bash docker exec -it openmldb bash /work/init.sh echo "create database db1;" | /work/openmldb/bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --role=sql_client @@ -232,7 +232,7 @@ and `sql-0`=''' SET @@execute_mode='online'; ''' and `sql-1`=''' -DEPLOY d1 SELECT trip_duration, passenger_count, +DEPLOY d1 OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') SELECT trip_duration, passenger_count, sum(pickup_latitude) OVER w AS vendor_sum_pl, max(pickup_latitude) OVER w AS vendor_max_pl, min(pickup_latitude) OVER w AS vendor_min_pl, diff --git a/docs/zh/integration/deploy_integration/airflow_provider_demo.md b/docs/zh/integration/deploy_integration/airflow_provider_demo.md index 5e8a77df979..a6cc0ee0dc3 100644 --- a/docs/zh/integration/deploy_integration/airflow_provider_demo.md +++ b/docs/zh/integration/deploy_integration/airflow_provider_demo.md @@ -17,9 +17,9 @@ DAG流程如上图所示,首先建表,然后进行离线数据导入与特 我们导入上述的DAG完成TalkingData Demo中的特征计算与上线,并使用TalkingData Demo的predict server来进行上线后的实时推理测试。 -### 0 准备 +### 准备工作 -#### 0.1 下载DAG +#### 下载DAG 除了DAG文件,还需要训练的脚本,所以我们提供了[下载包](https://openmldb.ai/download/airflow_demo/airflow_demo_files.tar.gz),可以直接下载。如果想要使用最新版本,请在[github example_dags](https://github.com/4paradigm/OpenMLDB/tree/main/extensions/airflow-provider-openmldb/openmldb_provider/example_dags)中获取。 @@ -28,24 +28,24 @@ wget https://openmldb.ai/download/airflow_demo/airflow_demo_files.tar.gz tar zxf airflow_demo_files.tar.gz ls airflow_demo_files ``` -#### 0.2 启动镜像 +#### 启动镜像 我们推荐使用docker镜像直接启动OpenMLDB,并在docker内部安装启动Airflow。 登录Airflow Web需要对外端口,所以此处暴露容器的端口。并且直接将上一步下载的文件映射到`/work/airflow/dags`,接下来Airflow将加载此文件夹的DAG。 ``` -docker run -p 8080:8080 -v `pwd`/airflow_demo_files:/work/airflow_demo_files -it 4pdosc/openmldb:0.8.3 bash +docker run -p 8080:8080 -v `pwd`/airflow_demo_files:/work/airflow_demo_files -it 4pdosc/openmldb:0.8.4 bash ``` -#### 0.3 下载安装Airflow与Airflow OpenMLDB Provider +#### 下载安装Airflow与Airflow OpenMLDB Provider 在docker容器中,执行: ``` pip3 install airflow-provider-openmldb ``` 由于airflow-provider-openmldb依赖airflow,所以会一起下载。 -#### 0.4 源数据与DAG准备 +#### 源数据与DAG准备 由于在DAG中导入数据用的文件为`/tmp/train_sample.csv`,所以我们需要将sample数据文件拷贝到tmp目录。Airflow 的DAG文件和DAG中使用的训练脚本也需要拷贝到airflow目录中。 ``` cp /work/airflow_demo_files/train_sample.csv /tmp/ @@ -53,7 +53,7 @@ mkdir -p /work/airflow/dags cp /work/airflow_demo_files/example_openmldb_complex.py /work/airflow_demo_files/xgboost_train_sample.py /work/airflow/dags ``` -### 1 启动OpenMLDB与Airflow +### 步骤1:启动OpenMLDB与Airflow 以下命令将启动OpenMLDB cluster,支持上线并测试的predict server,与Airflow standalone。 ``` /work/init.sh @@ -73,7 +73,7 @@ Airflow standalone运行输出将提示登录用户名和密码,如下图所 `airflow standalone`为前台程序,退出即airflow退出。你可以在dag运行完成后再退出airflow进行[第三步————测试](#3-测试),或者将airflow进程放入后台。 ``` -### 2 运行DAG +### 步骤2:运行DAG 在Airflow Web中点击DAG example_openmldb_complex,可以点击`Code`查看DAG的详情,见下图。 ![dag home](images/dag_home.png) @@ -82,7 +82,7 @@ Airflow standalone运行输出将提示登录用户名和密码,如下图所 ![dag code](images/dag_code.png) -#### 2.1 创建connection +#### 创建connection 在管理界面中点击connection。 ![connection](images/connection.png) @@ -96,15 +96,15 @@ Airflow OpenMLDB Provider是连接OpenMLDB Api Server的,所以此处配置中 创建完成后的connection如下图所示。 ![display](images/connection_display.png) -#### 2.2 运行DAG +#### 运行DAG 运行dag,即完成一次训练模型、sql部署与模型部署。成功运行的结果,类似下图。 ![dag run](images/dag_run.png) -### 3 测试 +### 步骤3:测试 Airflow如果在容器中是前台运行的,现在可以退出,以下测试将不依赖airflow。 -#### 3.1 在线导入 +#### 在线导入 Airflow DAG中完成了SQL和模型的上线。但在线存储中还没有数据,所以我们需要做一次在线数据导入。 ``` curl -X POST http://127.0.0.1:9080/dbs/example_db -d'{"mode":"online", "sql":"load data infile \"file:///tmp/train_sample.csv\" into table example_table options(mode=\"append\");"}' @@ -115,7 +115,7 @@ curl -X POST http://127.0.0.1:9080/dbs/example_db -d'{"mode":"online", "sql":"lo curl -X POST http://127.0.0.1:9080/dbs/example_db -d'{"mode":"online", "sql":"show jobs"}' ``` -#### 3.2 预测 +#### 预测 执行预测脚本,进行一次预测,预测将使用新部署好的sql与模型。 ``` python3 /work/airflow_demo_files/predict.py diff --git a/docs/zh/integration/deploy_integration/dolphinscheduler_task_demo.md b/docs/zh/integration/deploy_integration/dolphinscheduler_task_demo.md index da484e5dad7..f24e668ed17 100644 --- a/docs/zh/integration/deploy_integration/dolphinscheduler_task_demo.md +++ b/docs/zh/integration/deploy_integration/dolphinscheduler_task_demo.md @@ -31,7 +31,7 @@ OpenMLDB 希望能达成开发即上线的目标,让开发回归本质,而 测试可以在macOS或Linux上运行,推荐在我们提供的 OpenMLDB 镜像内进行演示测试。我们将在这个容器中启动OpenMLDB和DolphinScheduler,暴露DolphinScheduler的web端口: ``` -docker run -it -p 12345:12345 4pdosc/openmldb:0.8.3 bash +docker run -it -p 12345:12345 4pdosc/openmldb:0.8.4 bash ``` ```{attention} DolphinScheduler 需要配置租户,是操作系统的用户,并且该用户需要有 sudo 权限。所以推荐在 OpenMLDB 容器内下载并启动 DolphinScheduler。否则,请准备有sudo权限的操作系统用户。 @@ -108,7 +108,7 @@ DolphinScheduler 的 worker server 需要 OpenMLDB Python SDK, DolphinScheduler ### Demo 演示 -#### 1. 初始配置 +#### 步骤1:初始配置 在 DolphinScheduler Web中创建租户,进入租户管理界面,填写**有 sudo 权限的操作系统用户**,queue 可以使用 default。docker容器内可直接使用root用户。 @@ -121,7 +121,7 @@ DolphinScheduler 的 worker server 需要 OpenMLDB Python SDK, DolphinScheduler 绑定后,用户状态类似下图。 ![bind status](images/ds_bind_status.png) -#### 2. 创建工作流 +#### 步骤2:创建工作流 DolphinScheduler 中,需要先创建项目,再在项目中创建工作流。 所以,首先创建一个test项目,如下图所示,点击创建项目并进入项目。 @@ -155,7 +155,7 @@ DolphinScheduler 中,需要先创建项目,再在项目中创建工作流。 ![set tenant](images/ds_set_tenant.png) -#### 3. 上线运行工作流 +#### 步骤3:上线运行工作流 工作流保存后,需要先上线再运行。上线后,运行按钮才会点亮。如下图所示。 @@ -175,7 +175,7 @@ DolphinScheduler 中,需要先创建项目,再在项目中创建工作流。 `echo "show jobs;" | /work/openmldb/bin/openmldb --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb --role=sql_client`。如果某个任务状态是`FAILED`,请查询该任务的日志,方法见[任务日志](../../quickstart/beginner_must_read.md#离线)。 ``` -#### 4. 在线预测测试 +#### 步骤4:在线预测测试 predict server同时提供了在线预测服务,通过`curl /predict`请求。我们简单地构造一个实时请求,发送至predict server。 ``` curl -X POST 127.0.0.1:8881/predict -d '{"ip": 114904, diff --git a/docs/zh/integration/online_datasources/kafka_connector_demo.md b/docs/zh/integration/online_datasources/kafka_connector_demo.md index 7dffd7be109..fce0437623f 100644 --- a/docs/zh/integration/online_datasources/kafka_connector_demo.md +++ b/docs/zh/integration/online_datasources/kafka_connector_demo.md @@ -21,7 +21,7 @@ OpenMLDB Kafka Connector实现见[extensions/kafka-connect-jdbc](https://github. 我们推荐你将下载的三个文件包都绑定到文件目录`kafka`。当然,也可以在启动容器后,再进行文件包的下载。我们假设文件包都在`/work/kafka`目录中。 ``` -docker run -it -v `pwd`:/work/kafka 4pdosc/openmldb:0.8.3 bash +docker run -it -v `pwd`:/work/kafka 4pdosc/openmldb:0.8.4 bash ``` ### 注意事项 diff --git a/docs/zh/integration/online_datasources/pulsar_connector_demo.md b/docs/zh/integration/online_datasources/pulsar_connector_demo.md index 7277f039ee9..93dd5f8eee0 100644 --- a/docs/zh/integration/online_datasources/pulsar_connector_demo.md +++ b/docs/zh/integration/online_datasources/pulsar_connector_demo.md @@ -35,7 +35,7 @@ Apache Pulsar是一个云原生的,分布式消息流平台。它可以作为O ``` 我们更推荐你使用‘host network’模式运行docker,以及绑定文件目录‘files’,sql脚本在该目录中。 ``` -docker run -dit --network host -v `pwd`/files:/work/pulsar_files --name openmldb 4pdosc/openmldb:0.8.3 bash +docker run -dit --network host -v `pwd`/files:/work/pulsar_files --name openmldb 4pdosc/openmldb:0.8.4 bash docker exec -it openmldb bash ``` diff --git a/docs/zh/maintain/diagnose.md b/docs/zh/maintain/diagnose.md index eef7db5b5a1..cb5d7a30f74 100644 --- a/docs/zh/maintain/diagnose.md +++ b/docs/zh/maintain/diagnose.md @@ -8,14 +8,76 @@ 安装方式与使用: ```bash -pip install openmldb-tool # openmldb-tool[rpc] +pip install openmldb-tool # openmldb-tool[pb] openmldb_tool # 注意下划线 ``` 有以下几个子命令可选择执行: ```bash -usage: openmldb_tool [-h] [--helpfull] {status,inspect,test,static-check} ... +usage: openmldb_tool [-h] [--helpfull] {status,inspect,rpc,test,static-check} ... ``` -只有`static-check`静态检查命令需要指定`--dist_conf`参数,该参数指定OpenMLDB节点分布的配置文件。其他命令只需要`--cluster`参数,格式为`/`,默认为镜像中的OpenMLDB集群地址`127.0.0.1:2181/openmldb`。如果是自行设置的OpenMLDB集群,请配置此参数。 + +注意`-c/--cluster`参数,格式为`/`,默认将访问`127.0.0.1:2181/openmldb`。如果是自行设置的OpenMLDB集群,请配置此参数。其他参数根据子命令不同而不同,可以使用`-h`查看,或查看各个子命令的详细文档。 + +### 一键inspect + +`openmldb_tool inspect [--cluster=0.0.0.0:2181/openmldb]`可以一键查询,得到完整的集群状态报告。如果需要局部视角或额外的诊断功能,才需要其他子命令。 + +报告分为几个板块,其中如果所有表都是健康的,不会展示Ops和Partitions板块。用户首先看报告末尾的总结 summary & hint,如果存在server offline(红色),需先重启server,保证server尤其是TabletServer都在线。server重启后,集群可能会尝试自动修复,自动修复也可能会失败,所以,用户有必要等待一定时间后再次inspect。此时如果仍然有不健康的表,可以检查它们的状态,Fatal表需要尽快修复,它们可能会读写失败,Warn表,用户可以考虑推迟修复。修复方式见报告末尾提供的文档。 + +`inspect`可配置参数除了`--cluster/-c`,还可配置不显示彩色`--nocolor/-noc`方便复制,以及`--table_width/-tw n`配置表格宽度,`--offset_diff_thresh/-od n`配置offset diff的报警阈值。 + +``` +diagnosing cluster xxx + + +Server Detail +{server map} +{server online/offline report} + + +Table Partitions Detail +tablet server order: {tablet ip -> idx} +{partition tables of unhealthy tables} +Example: +{a detailed description of partition table} + + +Ops Detail +> failed ops do not mean cluster is unhealthy, just for reference +last one op(check time): {} +last 10 ops != finished: +{op list} + + + +================== +Summary & Hint +================== +Server: + +{online | offline servers ['[tablet]xxx'], restart them first} + +Table: +{all healthy | unhealthy tables desc} +[]Fatal/Warn table, {read/write may fail or still work}, {repair immediatly or not} +{partition detail: if leader healthy, if has unhealthy replicas, if offset too large, related ops} + + Make sure all servers online, and no ops for the table is running. + Repair table manually, run recoverdata, check https://openmldb.ai/docs/zh/main/maintain/openmldb_ops.html. + Check 'Table Partitions Detail' above for detail. +``` + +### 其他常用命令 + +除了一键inspect,在这样几个场景中,我们推荐使用诊断工具的子命令来帮助用户判断集群状态、简化运维。 + +- 部署好集群后,可以使用`test`测试集群是否能正常工作,不需要用户手动测试。如果发现问题,再使用`inspect`诊断。 +- 组件都在线,但出现超时或错误提示某组件无法连接时,可以使用`status --conn`检查与各组件的连接,会打印出简单访问的耗时。也可以用它来测试客户端主机与集群的连接情况,及时发现网络隔离。 +- 离线job如果出现问题,`SHOW JOBLOG id`可以查看日志,但经验较少的用户可能会被日志中的无关信息干扰,可以使用`inspect job`来提取job日志中的关键信息。 +- 离线job太多时,CLI中的展示会不容易读,可以使用`inspect offline`筛选所有failed的job,或者`inspect job --state `来筛选出特定状态的job。 +- 在一些棘手的问题中,可能需要用户通过RPC来获得一些信息,帮助定位问题。`openmldb_tool rpc`可以帮助用户简单快速地调用RPC,降低运维门槛。 +- 没有Prometheus监控时,可以通过`inspect online --dist`获得数据分布信息。 +- 如果你的操作节点到各个组件的机器是ssh免密的,那么,可以使用`static-check`检查配置文件是否正确,版本是否统一,避免部署失败。还可以一键收集整个集群的日志,方便打包并提供给开发人员分析。 ## 子命令详情 @@ -29,7 +91,8 @@ usage: openmldb_tool status [-h] [--helpfull] [--diff] optional arguments: -h, --help show this help message and exit --helpfull show full help message and exit - --diff check if all endpoints in conf are in cluster. If set, need to set `--conf_file` + --diff check if all endpoints in conf are in cluster. If set, need to set `-f,--conf_file` + --conn check network connection of all servers ``` - 简单查询集群状态: @@ -48,6 +111,11 @@ optional arguments: +-----------------+-------------+---------------+--------+---------+ ``` +- 检查并测试集群链接与版本: + ``` + openmldb_tool status --conn + ``` + #### 检查配置文件与集群状态是否一致 如果指定`--diff`参数,会检查配置文件中的所有节点是否都在已经启动的集群中,如果有节点不在集群中,会输出异常信息。如果集群中有节点不在配置文件中,不会输出异常信息。需要配置`-f,--conf_file`,例如,你可以在镜像里这样检查: @@ -57,7 +125,8 @@ openmldb_tool status --diff -f=/work/openmldb/conf/hosts ### inspect 检查 -`inspect`用于检查集群的在线和离线两个部分是否正常工作,可以选择单独检查`online`或`offline`,不指定则都检查。可以定期执行检查,以便及时发现异常。 +如果是为了检查集群状态,更推荐一键`inspect`获取集群完整检查报告,`inspect`子命令是更具有针对性的检查。 + ``` openmldb_tool inspect -h usage: openmldb_tool inspect [-h] [--helpfull] {online,offline,job} ... @@ -68,19 +137,26 @@ positional arguments: offline only inspect offline jobs. job show jobs by state, show joblog or parse joblog by id. ``` -在线检查会检查集群中的表状态(包括系统表),并输出有异常的表,包括表的状态,分区信息,副本信息等,等价于`SHOW TABLE STATUS`并筛选出有异常的表。如果发现集群表现不正常,请先检查下是否有异常表。例如,`SHOW JOBS`无法正常输出历史任务时,可以`inspect online`检查一下是否是job系统表出现问题。 + +#### online在线检查 + +`inspect online`检查在线表的健康状态,并输出有异常的表,包括表的状态,分区信息,副本信息等,等价于`SHOW TABLE STATUS`并筛选出有异常的表。 ##### 检查在线数据分布 -在线检查中,可以使用`inspect online --dist`检查在线数据分布,默认检查所有数据库,可以使用`--db`指定要检查的数据库。若要查询多个数据库,请使用 ',' 分隔数据库名称。会输出数据库在各个节点上的数据分布情况。 +可以使用`inspect online --dist`检查在线数据分布,默认检查所有数据库,可以使用`--db`指定要检查的数据库。若要查询多个数据库,请使用 ',' 分隔数据库名称。会输出数据库在各个节点上的数据分布情况。 -#### 离线检查 +#### offline离线检查 -离线检查会输出最终状态为失败的任务(不检查“运行中”的任务),等价于`SHOW JOBS`并筛选出失败任务。 +`inspect offline`离线检查会输出最终状态为失败的任务(不检查“运行中”的任务),等价于`SHOW JOBS`并筛选出失败任务。更多功能待补充。 #### JOB 检查 -JOB 检查会检查集群中的离线任务,可以使用`inspect job`或`inspect job --state all`查询所有任务,等价于`SHOW JOBS`并按job_id排序。使用`inspect job --state `可以筛选出特定状态的日志,可以使用 ',' 分隔,同时查询不同状态的日志。例如:`inspect offline` 相当于`inspect job --state failed,killed,lost`即筛选出所有失败的任务。 +JOB 检查是更灵活的离线任务检查命令,可以按条件筛选job,或针对单个job日志进行分析。 + +##### 按state筛选 + +可以使用`inspect job`或`inspect job --state all`查询所有任务,等价于`SHOW JOBS`并按job_id排序。使用`inspect job --state `可以筛选出特定状态的日志,可以使用 ',' 分隔,同时查询不同状态的日志。例如:`inspect offline` 相当于`inspect job --state failed,killed,lost`即筛选出所有失败的任务。 以下是一些常见的state: @@ -93,8 +169,13 @@ JOB 检查会检查集群中的离线任务,可以使用`inspect job`或`inspe 更多state信息详见[Spark State]( https://spark.apache.org/docs/3.2.1/api/java/org/apache/spark/launcher/SparkAppHandle.State.html),[Yarn State](https://hadoop.apache.org/docs/current/api/org/apache/hadoop/yarn/api/records/YarnApplicationState.html) +##### 解析单个JOB日志 -使用`inspect job --id `查询指定任务的log日志,其结果会使用配置文件筛选出主要错误信息。如需更新配置文件,可以添加`--conf-update`,并且可以使用`--conf-url`配置镜像源,例如使用`--conf-url https://openmldb.ai/download/diag/common_err.yml`配置国内镜像。如果需要完整的日志信息,可以添加`--detail`获取详细信息。 +使用`inspect job --id `查询指定任务的log日志,其结果会使用配置文件筛选出主要错误信息。 + +解析依靠配置文件,默认情况会自动下载。如需更新配置文件,可以`--conf-update`,它将会在解析前强制下载一次配置文件。如果默认下载源不合适,可以同时配置`--conf-url`配置镜像源,例如使用`--conf-url https://openmldb.ai/download/diag/common_err.yml`配置国内镜像。 + +如果只需要完整的日志信息而不是解析日志的结果,可以使用`--detail`获取详细信息,不会打印解析结果。 ### test 测试 @@ -185,7 +266,6 @@ nameserver: 如果检查配置文件或日志,将会把收集到的文件保存在`--collect_dir`中,默认为`/tmp/diag_collect`。你也也可以访问此目录查看收集到的配置或日志,进行更多的分析。 - #### 检查示例 在镜像容器中可以这样静态检查: @@ -193,14 +273,15 @@ nameserver: openmldb_tool static-check --conf_file=/work/openmldb/conf/hosts -VCL --local ``` -### rpc +### RPC 接口 + +`openmldb_tool`还提供了一个RPC接口,它可以让我们发送RPC更容易,不需要定位Server的IP,拼接RPC方法URL路径,也可以提示所有RPC方法和RPC方法的输入结构。使用方式是`openmldb_tool rpc`,例如,`openmldb_tool rpc ns ShowTable --field '{"show_all":true}'`可以调用`nameserver`的`ShowTable`接口,获取表的状态信息。 -`openmldb_tool`还提供了一个RPC接口,但它是一个额外组件,需要通过`pip install openmldb-tool[rpc]`安装。使用方式是`openmldb_tool rpc`,例如,`openmldb_tool rpc ns ShowTable --field '{"show_all":true}'`可以调用`nameserver`的`ShowTable`接口,获取表的状态信息。 +其中组件不使用ip,可以直接使用角色名。NameServer与TaskManager只有一个活跃,所以我们用ns和tm来代表这两个组件。而TabletServer有多个,我们用`tablet1`,`tablet2`等来指定某个TabletServer,从1开始,顺序可通过`openmldb_tool rpc`或`openmldb_tool status`来查看。 -NameServer与TaskManager只有一个活跃,所以我们用ns和tm来代表这两个组件。 -而TabletServer有多个,我们用`tablet1`,`tablet2`等来指定某个TabletServer,顺序可通过`openmldb_tool rpc`或`openmldb_tool status`来查看。 +如果对RPC服务的方法或者输入参数不熟悉,可以通过`openmldb_tool rpc [method] --hint`查看帮助信息。但它是一个额外组件,需要通过`pip install openmldb-tool[pb]`安装。hint还需要额外的pb文件,帮助解析输入参数,默认是从`/tmp/diag_cache`中读取,如果不存在则自动下载。如果你已有相应的文件,或者已经手动下载,可以通过`--pbdir`指定该目录。自行编译pb文件,见[openmldb tool开发文档](https://github.com/4paradigm/OpenMLDB/blob/main/python/openmldb_tool/README.md#rpc)。 -如果对RPC服务的方法或者输入参数不熟悉,可以通过`openmldb_tool rpc [method] --hint`查看帮助信息。例如: +例如: ```bash $ openmldb_tool rpc ns ShowTable --hint ... @@ -212,9 +293,7 @@ You should input json like this, ignore round brackets in the key and double quo "(optional)show_all": "bool" }' ``` -hint还需要额外的pb文件,帮助解析输入参数,默认是从`/tmp/diag_cache`中读取,如果不存在则自动下载。如果你已有相应的文件,或者已经手动下载,可以通过`--pbdir`指定该目录。 ## 附加 可使用`openmldb_tool --helpfull`查看所有配置项。例如,`--sdk_log`可以打印sdk的日志(zk,glog),可用于调试。 - \ No newline at end of file diff --git a/docs/zh/maintain/faq.md b/docs/zh/maintain/faq.md deleted file mode 100644 index 454bfb500ad..00000000000 --- a/docs/zh/maintain/faq.md +++ /dev/null @@ -1,130 +0,0 @@ -# 运维 FAQ - -## 部署和启动 FAQ - -### 1. 如何确认集群已经正常运行? -虽然有一键启动脚本,但由于配置繁多,可能出现“端口已被占用”,“目录无读写权限”等问题。这些问题都是server进程运行之后才能发现,退出后没有及时反馈。(如果配置了监控,可以通过监控直接检查。) -所以,请先确认集群的所有server进程都正常运行。 - -可以通过`ps axu | grep openmldb`或sql命令`show components;`来查询。(注意,如果你使用了守护进程,openmldb server进程可能是在启动停止的循环中,并不代表持续运行,可以通过日志或`show components;`连接时间来确认。) - -如果进程都活着,集群还是表现不正常,需要查询一下server日志。可以优先看WARN和ERROR级日志,很大概率上,它们就是根本原因。 - -### 2. 如果数据没有自动恢复成功怎么办? - -通常情况,当我们重启服务,表中数据会自动进行恢复,但有些情况可能会造成恢复失败,通常失败的情况包括: - -- tablet异常退出 -- 多副本表多个副本所在的tablets同时重启或者重启太快,造成某些`auto_failover`操作还没完成tablet就重启 -- auto_failover设成`false` - -当服务启动成功后,可以通过`gettablestatus`获得所有表的状态: -``` -python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=172.24.4.40:30481 --zk_root_path=/openmldb --cmd=gettablestatus -``` - -如果表中有`Warnings`,可以通过`recoverdata`来自动恢复数据: -``` -python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=172.24.4.40:30481 --zk_root_path=/openmldb --cmd=recoverdata -``` - -## Server FAQ - -### 1. 为什么日志中有 Fail to write into Socket 的警告日志? -``` -http_rpc_protocol.cpp:911] Fail to write into Socket{id=xx fd=xx addr=xxx} (0x7a7ca00): Unknown error 1014 [1014] -``` -这是server端会打印的日志。一般是client端使用了连接池或短连接模式,在RPC超时后会关闭连接,server写回response时发现连接已经关了就报这个错。Got EOF就是指之前已经收到了EOF(对端正常关闭了连接)。client端使用单连接模式server端一般不会报这个。 - -### 2. 表数据的ttl初始设置不合适,如何调整? -这需要使用nsclient来修改,普通client无法做到。nsclient启动方式与命令,见[ns client](../maintain/cli.md#ns-client)。 - -在nsclient中使用命令`setttl`可以更改一个表的ttl,类似 -``` -setttl table_name ttl_type ttl [ttl] [index_name] -``` -可以看到,如果在命令末尾配置index的名字,可以做到只修改单个index的ttl。 -```{caution} -`setttl`的改变不会及时生效,会受到tablet server的配置`gc_interval`的影响。(每台tablet server的配置是独立的,互不影响。) - -举例说明,有一个tablet server的`gc_interval`是1h,那么ttl的配置重载,会在下一次gc的最后时刻进行(最坏情况下,会在1h后重载)。重载ttl的这一次gc就不会按最新ttl来淘汰数据。再下一次gc时才会使用最新ttl进行数据淘汰。 - -所以,**ttl更改后,需要等待两次gc interval的时间才会生效**。请耐心等待。 - -当然,你可以调整tablet server的`gc_interval`,但这个配置无法动态更改,只能重启生效。所以,如果内存压力较大,可以尝试扩容,迁移数据分片,来减少内存压力。不推荐轻易调整`gc_interval`。 -``` - -### 3. 出现警告日志:Last Join right table is empty,这是什么意思? -通常来讲,这是一个正常现象,不代表集群异常。只是runner中join右表为空,是可能的现象,大概率是数据问题。 - -## Client FAQ - -### 1. 为什么收到 Reached timeout 的警告日志? -``` -rpc_client.h:xxx] request error. [E1008] Reached timeout=xxxms -``` -这是由于client端本身发送的rpc request的timeout设置小了,client端自己主动断开,注意这是rpc的超时。需要更改通用的`request_timeout`配置。 -1. CLI: 启动时配置`--request_timeout_ms` -2. JAVA/Python SDK: Option或url中调整`SdkOption.requestTimeout` -```{note} -同步的离线命令通常不会出现这个错误,因为同步离线命令的timeout设置为了TaskManager可接受的最长时间。 -``` -### 2. 为什么收到 Got EOF of Socket 的警告日志? -``` -rpc_client.h:xxx] request error. [E1014]Got EOF of Socket{id=x fd=x addr=xxx} (xx) -``` -这是因为`addr`端主动断开了连接,`addr`的地址大概率是TaskManager。这不代表TaskManager不正常,而是TaskManager端认为这个连接没有活动,超过keepAliveTime了,而主动断开通信channel。 -在0.5.0及以后的版本中,可以调大TaskManager的`server.channel_keep_alive_time`来提高对不活跃channel的容忍度。默认值为1800s(0.5h),特别是使用同步的离线命令时,这个值可能需要适当调大。 -在0.5.0以前的版本中,无法更改此配置,请升级TaskManager版本。 - -### 3. 离线查询结果显示中文为什么乱码? - -在使用离线查询时,可能出现包含中文的查询结果乱码,主要和系统默认编码格式与Spark任务编码格式参数有关。 - -如果出现乱码情况,可以通过添加Spark高级参数`spark.driver.extraJavaOptions=-Dfile.encoding=utf-8`和`spark.executor.extraJavaOptions=-Dfile.encoding=utf-8`来解决。 - -客户端配置方法可参考[客户端Spark配置文件](../reference/client_config/client_spark_config.md),也可以在TaskManager配置文件中添加此项配置。 - -``` -spark.default.conf=spark.driver.extraJavaOptions=-Dfile.encoding=utf-8;spark.executor.extraJavaOptions=-Dfile.encoding=utf-8 -``` - -### 4. 如何配置TaskManager来访问开启Kerberos的Yarn集群? - -如果Yarn集群开启Kerberos认证,TaskManager可以通过添加以下配置来访问开启Kerberos认证的Yarn集群。注意请根据实际配置修改keytab路径以及principal账号。 - -``` -spark.default.conf=spark.yarn.keytab=/tmp/test.keytab;spark.yarn.principal=test@EXAMPLE.COM -``` - -### 5. 如何配置客户端的core日志? - -客户端core日志主要有两种,zk日志和sdk日志(glog日志),两者是独立的。 - -zk日志: -1. CLI:启动时配置`--zk_log_level`调整level,`--zk_log_file`配置日志保存文件。 -2. JAVA/Python SDK:Option或url中使用`zkLogLevel`调整level,`zkLogFile`配置日志保存文件。 - -- `zk_log_level`(int, 默认=0, 即DISABLE_LOGGING): -打印这个等级及**以下**等级的日志。0-禁止所有zk log, 1-error, 2-warn, 3-info, 4-debug。 - -sdk日志(glog日志): -1. CLI:启动时配置`--glog_level`调整level,`--glog_dir`配置日志保存文件。 -2. JAVA/Python SDK:Option或url中使用`glogLevel`调整level,`glogDir`配置日志保存文件。 - -- `glog_level`(int, 默认=1, 即WARNING): -打印这个等级及**以上**等级的日志。 INFO, WARNING, ERROR, and FATAL日志分别对应 0, 1, 2, and 3。 - - -### 6. 插入错误,日志显示`please use getInsertRow with ... first` - -在JAVA client使用InsertPreparedStatement进行插入,或在Python中使用sql和parameter进行插入时,client底层实际有cache影响,第一步`getInsertRow`生成sql cache并返回sql还需要补充的parameter信息,第二步才会真正执行insert,而执行insert需要使用第一步缓存的sql cache。所以,当多线程使用同一个client时,可能因为插入和查询频繁更新cache表,将你想要执行的insert sql cache淘汰掉了,所以会出现好像第一步`getInsertRow`并未执行的样子。 - -目前可以通过调大`maxSqlCacheSize`这一配置项来避免错误。仅JAVA/Python SDK支持配置。 - -### 7. 离线命令错误`java.lang.OutOfMemoryError: Java heap space` - -离线命令的Spark配置默认为`local[*]`,并发较高可能出现OutOfMemoryError错误,请调整`spark.driver.memory`和`spark.executor.memory`两个spark配置项。可以写在TaskManager运行目录的`conf/taskmanager.properties`的`spark.default.conf`并重启TaskManager,或者使用CLI客户端进行配置,参考[客户端Spark配置文件](../reference/client_config/client_spark_config.md)。 -``` -spark.default.conf=spark.driver.memory=16g;spark.executor.memory=16g -``` diff --git a/docs/zh/maintain/index.rst b/docs/zh/maintain/index.rst index a114cccef15..bdb0b551e87 100644 --- a/docs/zh/maintain/index.rst +++ b/docs/zh/maintain/index.rst @@ -16,4 +16,3 @@ multi_cluster diagnose openmldb_ops - faq diff --git a/docs/zh/maintain/monitoring.md b/docs/zh/maintain/monitoring.md index 905644c74df..e51f0a3b8bc 100644 --- a/docs/zh/maintain/monitoring.md +++ b/docs/zh/maintain/monitoring.md @@ -31,10 +31,8 @@ OpenMLDB exporter 是以 Python 实现的 Prometheus exporter,核心是通过 2. 启动 OpenMLDB - 参见 [install_deploy](../deploy/install_deploy.md) 如何搭建 OpenMLDB。组件启动时需要保证有 flag `--enable_status_service=true`, 或者确认启动 flag 文件 (`conf/(tablet|nameserver).flags`) 中有 `--enable_status_service=true`。 + 参见 [install_deploy](../deploy/install_deploy.md) 如何搭建 OpenMLDB。组件启动时需要保证有 flag `--enable_status_service=true`, OpenMLDB启动脚本(无论是sbin或bin)都已配置为true,如果你使用个人方式启动,需要保证启动 flag 文件 (`conf/(tablet|nameserver).flags`) 中有 `--enable_status_service=true`。 - 默认启动脚本 `bin/start.sh` 开启了 server status, 不需要额外配置。 - 3. 注意:合理选择 OpenMLDB 各组件和 OpenMLDB exporter, 以及 Prometheus, Grafana 的绑定 IP 地址,确保 Grafana 可以访问到 Prometheus, 并且 Prometheus,OpenMLDB exporter 和 OpenMLDB 各个组件之间可以相互访问。 ### 部署 OpenMLDB exporter @@ -168,13 +166,6 @@ OpenMLDB 提供了 Prometheus 和 Grafana 配置文件以作参考,详见 [Ope - component status: 集群组件状态 - table status: 数据库表相关信息,如 `rows_count`, `memory_bytes` - - deploy query response time: deployment query 在 tablet 内部的运行时间 - - **除了 deploy query response time 指标外, 成功配置监控之后都可以直接查询到指标. Deploy query response time 需要全局变量 `deploy_stats` 开启后才会有数据, 在 OpenMLDB CLI 中输入 SQL:** - - ```sql - SET GLOBAL deploy_stats = 'on'; - ``` 你可以通过 @@ -184,9 +175,27 @@ OpenMLDB 提供了 Prometheus 和 Grafana 配置文件以作参考,详见 [Ope 查看完整 DB-Level 指标和帮助信息。 +通过Component-Level 指标通过Grafana聚合的DB-Level 指标(未单独声明时,time单位为us): + +- deploy query response time: deployment query 在OpenMLDB内部的运行时间,按DB.DEPLOYMENT汇总 + **需要全局变量 `deploy_stats` 开启后才会开始统计, 在 OpenMLDB CLI 中输入 SQL:** + + ```sql + SET GLOBAL deploy_stats = 'on'; + ``` + 然后,还需要执行deplpoyment,才会出现相应的指标。 + 如果SET变量为off,会清空server中的所有deployment指标并停止统计(已被Prometheus抓取的数据不影响)。 + - count:count类统计值从deploy_stats on时开始统计,不区分请求的成功和失败。 + - latency, qps:这类指标只统计`[current_time - interval, current_time]`时间窗口内的数据,interval由Tablet Server配置项`bvar_dump_interval`配置,默认为75秒。 + +- api server http time: 各API接口的处理耗时(不包含route),只监测接口耗时,不做细粒度区分,目前也不通过Grafana展示,可以通过Prometheus手动查询。目前监测`deployment`、`sp`和`query`三种方法。 + - api server route time: APIServer进行http route的耗时,通常为us级别,一般忽略不计 + +以上聚合指标的获取方式见下文。在组件指标中,deploy query response time关键字为`deployment`,api server http time关键字为`http_method`。如果指标展示不正常,可以查询组件指标定位问题。 + ### 2. Component-Level 指标 -OpenMLDB 的相关组件(即 nameserver, tablet, etc), 本身作为 BRPC server,暴露了 [Prometheus 相关指标](https://github.com/apache/incubator-brpc/blob/master/docs/en/bvar.md#export-to-prometheus), 只需要配置 Prometheus server 从对应地址拉取指标即可。对应 `prometheus_example.yml`中 `job_name=openmldb_components` 项: +OpenMLDB 的相关组件(即 nameserver, tablet, etc), 本身作为 BRPC server,暴露了 [Prometheus 相关指标](https://github.com/apache/brpc/blob/master/docs/en/bvar.md#export-to-prometheus), 只需要配置 Prometheus server 从对应地址拉取指标即可。对应 `prometheus_example.yml`中 `job_name=openmldb_components` 项: ```yaml - job_name: openmldb_components @@ -203,6 +212,7 @@ OpenMLDB 的相关组件(即 nameserver, tablet, etc), 本身作为 BRPC serve - BRPC server 进程相关信息 - 对应 BRPC server 定义的 RPC method 相关指标,例如该 RPC 的请求 `count`, `error_count`, `qps` 和 `response_time` + - Deployment 相关指标,分deployment统计,但只统计该tablet上的deployment请求。它们将通过Grafana聚合,形成最终的的集群级别Deployment指标。 通过 diff --git a/docs/zh/maintain/openmldb_ops.md b/docs/zh/maintain/openmldb_ops.md index 10b53437b52..d96b23131b3 100644 --- a/docs/zh/maintain/openmldb_ops.md +++ b/docs/zh/maintain/openmldb_ops.md @@ -31,9 +31,13 @@ **使用示例** ``` -python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=172.24.4.40:30481 --zk_root_path=/openmldb --cmd=scaleout +python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=0.0.0.0:2181 --zk_root_path=/openmldb --cmd=scaleout +python tools/openmldb_ops.py --openmldb_bin_path=./bin/openmldb --zk_cluster=0.0.0.0:2181 --zk_root_path=/openmldb --cmd=recoverdata ``` +运行结果可以只关注是否存在ERROR级日志,如果存在,请保留完整的日志记录,便于技术人员查找问题。 + ### 系统要求 - 要求python2.7及以上版本 +- 理论上openmldb_ops不要求与OpenMLDB集群的版本匹配,高版本openmldb_ops可以操作低版本的OpenMLDB集群。 - `showopstatus`和`showtablestatus`需要`prettytable`依赖 diff --git a/docs/zh/openmldb_sql/ddl/CREATE_TABLE_STATEMENT.md b/docs/zh/openmldb_sql/ddl/CREATE_TABLE_STATEMENT.md index 1dffc9d4cae..a44f699eed3 100644 --- a/docs/zh/openmldb_sql/ddl/CREATE_TABLE_STATEMENT.md +++ b/docs/zh/openmldb_sql/ddl/CREATE_TABLE_STATEMENT.md @@ -450,6 +450,11 @@ StorageMode ::= 'Memory' | 'HDD' | 'SSD' +CompressTypeOption + ::= 'COMPRESS_TYPE' '=' CompressType +CompressType + ::= 'NoCompress' + | 'Snappy' ``` @@ -460,6 +465,7 @@ StorageMode | `REPLICANUM` | 配置表的副本数。请注意,副本数只有在集群版中才可以配置。 | `OPTIONS (REPLICANUM=3)` | | `DISTRIBUTION` | 配置分布式的节点endpoint。一般包含一个Leader节点和若干Follower节点。`(leader, [follower1, follower2, ..])`。不显式配置时,OpenMLDB会自动根据环境和节点来配置`DISTRIBUTION`。 | `DISTRIBUTION = [ ('127.0.0.1:6527', [ '127.0.0.1:6528','127.0.0.1:6529' ])]` | | `STORAGE_MODE` | 表的存储模式,支持的模式有`Memory`、`HDD`或`SSD`。不显式配置时,默认为`Memory`。
如果需要支持非`Memory`模式的存储模式,`tablet`需要额外的配置选项,具体可参考[tablet配置文件 conf/tablet.flags](../../../deploy/conf.md)。 | `OPTIONS (STORAGE_MODE='HDD')` | +| `COMPRESS_TYPE` | 指定表的压缩类型。目前只支持Snappy压缩, 。默认为 `NoCompress` 即不压缩。 | `OPTIONS (COMPRESS_TYPE='Snappy')` #### 磁盘表与内存表区别 - 磁盘表对应`STORAGE_MODE`的取值为`HDD`或`SSD`。内存表对应的`STORAGE_MODE`取值为`Memory`。 @@ -488,11 +494,11 @@ DESC t1; --- -------------------- ------ ---------- ------ --------------- 1 INDEX_0_1651143735 col1 std_time 0min kAbsoluteTime --- -------------------- ------ ---------- ------ --------------- - -------------- - storage_mode - -------------- - HDD - -------------- + --------------- -------------- + compress_type storage_mode + --------------- -------------- + NoCompress HDD + --------------- -------------- ``` 创建一张表,指定分片的分布状态 ```sql diff --git a/docs/zh/openmldb_sql/ddl/DESC_STATEMENT.md b/docs/zh/openmldb_sql/ddl/DESC_STATEMENT.md index 1088411dc03..ca0d0de87bf 100644 --- a/docs/zh/openmldb_sql/ddl/DESC_STATEMENT.md +++ b/docs/zh/openmldb_sql/ddl/DESC_STATEMENT.md @@ -56,11 +56,11 @@ desc t1; --- -------------------- ------ ---------- ---------- --------------- 1 INDEX_0_1658136511 col1 std_time 43200min kAbsoluteTime --- -------------------- ------ ---------- ---------- --------------- - -------------- - storage_mode - -------------- - Memory - -------------- + --------------- -------------- + compress_type storage_mode + --------------- -------------- + NoCompress Memory + --------------- -------------- ``` diff --git a/docs/zh/openmldb_sql/ddl/SHOW_CREATE_TABLE_STATEMENT.md b/docs/zh/openmldb_sql/ddl/SHOW_CREATE_TABLE_STATEMENT.md new file mode 100644 index 00000000000..22c08fb754e --- /dev/null +++ b/docs/zh/openmldb_sql/ddl/SHOW_CREATE_TABLE_STATEMENT.md @@ -0,0 +1,28 @@ +# SHOW CREATE TABLE + +`SHOW CREATE TABLE` 用来显示指定表的建表语句 + +**Syntax** + +```sql +SHOW CREATE TABLE table_name; +``` + +**Example** + +```sql +show create table t1; + ------- --------------------------------------------------------------- + Table Create Table + ------- --------------------------------------------------------------- + t1 CREATE TABLE `t1` ( + `c1` varchar, + `c2` int, + `c3` bigInt, + `c4` timestamp, + INDEX (KEY=`c1`, TS=`c4`, TTL_TYPE=ABSOLUTE, TTL=0m) + ) OPTIONS (PARTITIONNUM=8, REPLICANUM=2, STORAGE_MODE='HDD', COMPRESS_TYPE='NoCompress'); + ------- --------------------------------------------------------------- + +1 rows in set +``` \ No newline at end of file diff --git a/docs/zh/openmldb_sql/ddl/TRUNCATE_TABLE_STATEMENT.md b/docs/zh/openmldb_sql/ddl/TRUNCATE_TABLE_STATEMENT.md new file mode 100644 index 00000000000..8ffb623f26f --- /dev/null +++ b/docs/zh/openmldb_sql/ddl/TRUNCATE_TABLE_STATEMENT.md @@ -0,0 +1,16 @@ +# TRUNCATE TABLE + +``` +TRUNCATE TABLE table_name +``` + +`TRUNCATE TABLE`语句用清空指定的表。 + +## Example: 清空t1表 + +```sql +TRUNCATE TABLE t1; +-- Truncate table t1? yes/no +-- yes +-- SUCCEED +``` \ No newline at end of file diff --git a/docs/zh/openmldb_sql/ddl/index.rst b/docs/zh/openmldb_sql/ddl/index.rst index 116b9ce29c3..9e420def154 100644 --- a/docs/zh/openmldb_sql/ddl/index.rst +++ b/docs/zh/openmldb_sql/ddl/index.rst @@ -23,3 +23,5 @@ CREATE_FUNCTION SHOW_FUNCTIONS DROP_FUNCTION + SHOW_CREATE_TABLE_STATEMENT + TRUNCATE_TABLE_STATEMENT \ No newline at end of file diff --git a/docs/zh/openmldb_sql/deployment_manage/DEPLOY_STATEMENT.md b/docs/zh/openmldb_sql/deployment_manage/DEPLOY_STATEMENT.md index 4f94e228357..41b3e1141e8 100644 --- a/docs/zh/openmldb_sql/deployment_manage/DEPLOY_STATEMENT.md +++ b/docs/zh/openmldb_sql/deployment_manage/DEPLOY_STATEMENT.md @@ -175,9 +175,9 @@ deploy demo options(SYNC="false") SELECT t1.col1, t2.col2, sum(col4) OVER w1 as WINDOW w1 AS (PARTITION BY t1.col2 ORDER BY t1.col3 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW); ``` -#### 设置偏移 +#### 设置偏移BIAS -如果你并不希望数据根据deploy的索引淘汰,或者希望晚一点淘汰,可以在deploy时设置偏移,常用于数据时间戳并不实时的情况、测试等情况。如果deploy后的索引ttl为abs 3h,但是数据的时间戳是3h前的(以系统时间为基准),那么这条数据就会被淘汰,无法参与计算。设置一定时间或永久的偏移,则可以让数据更久的停留在在线表中。 +如果你并不希望数据根据deploy的索引淘汰,或者希望晚一点淘汰,可以在deploy时设置偏移BIAS,常用于数据时间戳并不实时的情况、测试等情况。如果deploy后的索引ttl为abs 3h,但是数据的时间戳是3h前的(以系统时间为基准),那么这条数据就会被淘汰,无法参与计算。设置一定时间或永久的偏移,则可以让数据更久的停留在在线表中。 时间偏移,单位可以是`s`、`m`、`h`、`d`,也可以是整数,单位为`ms`,也可以是`inf`,表示永不淘汰;如果是行数偏移,可以是整数,单位是`row`,也可以是`inf`,表示永不淘汰。两种偏移中,0均表示不偏移。 @@ -185,6 +185,12 @@ deploy demo options(SYNC="false") SELECT t1.col1, t2.col2, sum(col4) OVER w1 as 而时间偏移的单位是`min`,我们会在内部将其转换为`min`,并且取上界。比如,新索引ttl是abs 2min,加上偏移20s,结果是`2min + ub(20s) = 3min`,然后和旧索引1min取上界,最终索引ttl是`max(1min, 3min) = 3min`。 +**Example** +```sql +DEPLOY demo OPTIONS(RANGE_BIAS="inf", ROWS_BIAS="inf") SELECT t1.col1, t2.col2, sum(col4) OVER w1 as w1_col4_sum FROM t1 LAST JOIN t2 ORDER BY t2.col3 ON t1.col2 = t2.col2 + WINDOW w1 AS (PARTITION BY t1.col2 ORDER BY t1.col3 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW); +``` + ## 相关SQL [USE DATABASE](../ddl/USE_DATABASE_STATEMENT.md) diff --git a/docs/zh/openmldb_sql/deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md b/docs/zh/openmldb_sql/deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md index 43b4c9e4941..7a4a8501490 100644 --- a/docs/zh/openmldb_sql/deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md +++ b/docs/zh/openmldb_sql/deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md @@ -12,10 +12,10 @@ OpenMLDB仅支持上线[SELECT查询语句](../dql/SELECT_STATEMENT.md)。 下表列出了在线请求模式支持的 `SELECT` 子句。 -| SELECT 子句 | 说明 | -|:-------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------| -| 单张表的简单表达式计算 | 简单的单表查询是对一张表进行列运算、使用运算表达式或单行处理函数(Scalar Function)以及它们的组合表达式作计算。需要遵循[在线请求模式下单表查询的使用规范](#在线请求模式下单表查询的使用规范) | -| [`JOIN` 子句](../dql/JOIN_CLAUSE.md) | OpenMLDB目前仅支持**LAST JOIN**。需要遵循[在线请求模式下LAST JOIN的使用规范](#在线请求模式下-last-join-的使用规范) | +| SELECT 子句 | 说明 | +| :--------------------------------------- | :----------------------------------------------------------- | +| 单张表的简单表达式计算 | 简单的单表查询是对一张表进行列运算、使用运算表达式或单行处理函数(Scalar Function)以及它们的组合表达式作计算。需要遵循[在线请求模式下单表查询的使用规范](#在线请求模式下单表查询的使用规范) | +| [`JOIN` 子句](../dql/JOIN_CLAUSE.md) | OpenMLDB目前仅支持**LAST JOIN**。需要遵循[在线请求模式下LAST JOIN的使用规范](#在线请求模式下-last-join-的使用规范) | | [`WINDOW` 子句](../dql/WINDOW_CLAUSE.md) | 窗口子句用于定义一个或者若干个窗口。窗口可以是有名或者匿名的。用户可以在窗口上调用聚合函数进行分析计算。需要遵循[在线请求模式下Window的使用规范](#在线请求模式下window的使用规范) | ## 在线请求模式下 `SELECT` 子句的使用规范 @@ -57,15 +57,19 @@ SELECT substr(COL7, 3, 6) FROM t1; ### 在线请求模式下 `LAST JOIN` 的使用规范 -- 仅支持`LAST JOIN`类型。 -- 至少有一个JOIN条件是形如`left_source.column=right_source.column`的EQUAL条件,**并且`right_source.column`列需要命中右表的索引(key 列)**。 -- 带排序LAST JOIN的情况下,`ORDER BY`只支持单列的列引用表达式,列类型为 int16, int32, int64 or timestamp, **并且列需要命中右表索引的时间列**。 -- 右表 TableRef +1. 仅支持`LAST JOIN`类型。 +2. 至少有一个JOIN条件是形如`left_source.column=right_source.column`的EQUAL条件,**并且`right_source.column`列需要命中右表的索引(key 列)**。 +3. 带排序LAST JOIN的情况下,`ORDER BY`只支持单列的列引用表达式,列类型为 int64 或 timestamp, **并且列需要命中右表索引的时间列**。满足条件 2 和 3 的情况我们简单称做表能被 LAST JOIN 的 JOIN 条件优化 +4. 右表 TableRef - 可以指一张物理表, 或者子查询语句 - - 子查询情况, 只支持 + - 子查询情况, 目前支持 - 简单列筛选 (`select * from tb` or `select id, val from tb`) - - 窗口聚合子查询, 例如 `select id, count(val) over w as cnt from t1 window w as (...)`. 这种情况下, 子查询和 last join 的左表必须有相同的主表, 主表指计划树下最左边的物理表节点. - - **Since OpenMLDB 0.8.0** 带 WHERE 条件过滤的简单列筛选 ( 例如 `select * from tb where id > 10`) + - 窗口聚合子查询, 例如 `select id, count(val) over w as cnt from t1 window w as (...)`. + - OpenMLDB 0.8.4 之前, 如果 LAST JOIN 的右表是窗口聚合子查询, 需要和 LAST JOIN 的左表输入有相同的主表 + - [ALPHA] OpenMLDB >= 0.8.4, 允许 LAST JOIN 下的窗口聚合子查询不带主表. 详细见下面的例子 + - **OpenMLDB >= 0.8.0** 带 WHERE 条件过滤的简单列筛选 ( 例如 `select * from tb where id > 10`) + - **[ALPHA] OpenMLDB >= 0.8.4** 右表是带 LAST JOIN 的子查询 `subquery`, 要求 `subquery` 最左的表能被 JOIN 条件优化, `subquery`剩余表能被自身 LAST JOIN 的 JOIN 条件优化 + - **[ALPHA] OpenMLDB >= 0.8.4** LEFT JOIN. 要求 LEFT JOIN 的右表能被 LEFT JOIN 条件优化, LEFT JOIN 的左表能被上层的 LAST JOIN 条件优化 **Example: 支持上线的 `LAST JOIN` 语句范例** 创建两张表以供后续`LAST JOIN`。 @@ -115,15 +119,82 @@ desc t1; t1.col0 as t1_col0, t1.col1 + t2.col1 + 1 as test_col1, FROM t1 - LAST JOIN t2 ORDER BY t2.std_time ON t1.col1=t2.col1; + LAST JOIN t2 ORDER BY t2.std_time ON t1.col1=t2.col1; ``` +右表是带 LAST JOIN 或者 WHERE 条件过滤的情况 + +```sql +CREATE TABLE t3 (col0 STRING, col1 int, std_time TIMESTAMP, INDEX(KEY=col1, TS=std_time, TTL_TYPE=absolute, TTL=30d)); +-- SUCCEED + +SELECT + t1.col1 as t1_col1, + t2.col1 as t2_col1, + t2.col0 as t2_col0 +FROM t1 LAST JOIN ( + SELECT * FROM t2 WHERE strlen(col0) > 0 +) t2 +ON t1.col1 = t2.col1 + +-- t2 被 JOIN 条件 't1.col1 = tx.t2_co1l' 优化, t3 被 JOIN 条件 't2.col1 = t3.col1' +SELECT + t1.col1 as t1_col1, + tx.t2_col1, + tx.t3_col1 +FROM t1 LAST JOIN ( + SELECT t2.col1 as t2_col1, t3.col1 as t3_col1 + FROM t2 LAST JOIN t3 + ON t2.col1 = t3.col1 +) tx +ON t1.col1 = tx.t2_col1 + +-- 右表是 LEFT JOIN +SELECT + t1.col1 as t1_col1, + tx.t2_col1, + tx.t3_col1 +FROM t1 LAST JOIN ( + SELECT t2.col1 as t2_col1, t3.col1 as t3_col1 + FROM t2 LEFT JOIN t3 + ON t2.col1 = t3.col1 +) tx +ON t1.col1 = tx.t2_col1 + +-- OpenMLDB 0.8.4 之前, LAST JOIN 窗口子查询需要窗口的子查询主表和当前主表一致 +-- 这里都是 t1 +SELECT + t1.col1, + tx.agg +FROM t1 LAST JOIN ( + SELECT col1, count(col2) over w as agg + FROM t1 WINDOW w AS ( + UNION t2 + PARTITION BY col2 order by std_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW + INSTANCE_NOT_IN_WINDOW EXCLUDE CURRENT_ROW + ) +) + +-- 右表是窗口聚合计算 +-- OpenMLDB >= 0.8.4, 允许 t1 LAST JOIN WINDOW (t2). t1 是主表, t2 是一张副表 +-- 此 SQL 和上一个例子语义一致 +SELECT + t1.col1, + tx.agg +FROM t1 LAST JOIN ( + SELECT col1, count(col2) over w as agg + FROM t2 WINDOW w AS (PARTITION BY col2 order by std_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) +) +``` + + + ### 在线请求模式下Window的使用规范 - 窗口边界仅支持`PRECEDING`和`CURRENT ROW` - 窗口类型仅支持`ROWS`和`ROWS_RANGE`。 - 窗口`PARTITION BY`只支持列表达式,可以是多列,并且所有列需要命中索引,主表和 union source 的表都需要符合要求 -- 窗口`ORDER BY`只支持列表达式,只能是单列,并且列需要命中索引的时间列,主表和 union source 的表都需要符合要求 +- 窗口`ORDER BY`只支持列表达式,只能是单列,并且列需要命中索引的时间列,主表和 union source 的表都需要符合要求. 从 OpenMLDB 0.8.4 开始, ORDER BY 可以不写, 但需要满足额外的要求, 详见 [WINDOW CLAUSE](../dql/WINDOW_CLAUSE.md) - 可支持使用 `EXCLUDE CURRENT_ROW`,`EXCLUDE CURRENT_TIME`,`MAXSIZE`,`INSTANCE_NOT_IN_WINDOW`对窗口进行其他特殊限制,详见[OpenMLDB特有的 WindowSpec 元素](#openmldb特有的-windowspec-元素)。 - `WINDOW UNION` source 要求,支持如下格式的子查询: - 表引用或者简单列筛选,例如 `t1` 或者 `select id, val from t1`。union source 和 主表的 schema 必须完全一致,并且 union source 对应的 `PARTITION BY`, `ORDER BY` 也需要命中索引 diff --git a/docs/zh/openmldb_sql/dql/JOIN_CLAUSE.md b/docs/zh/openmldb_sql/dql/JOIN_CLAUSE.md index 0ed4b357619..6e74adc7928 100644 --- a/docs/zh/openmldb_sql/dql/JOIN_CLAUSE.md +++ b/docs/zh/openmldb_sql/dql/JOIN_CLAUSE.md @@ -1,23 +1,31 @@ # JOIN Clause -OpenMLDB目前仅支持`LAST JOIN`一种**JoinType**。 +OpenMLDB目前支持 -LAST JOIN可以看作一种特殊的LEFT JOIN。在满足JOIN条件的前提下,左表的每一行拼接符合条件的最后一行。LAST JOIN分为无排序拼接,和排序拼接。 +- LAST JOIN +- LEFT JOIN (**OPENMLDB >= 0.8.4**) + +LEFT OUTER JOIN (或者简称 LEFT JOIN) 会将两个 from_item 进行联接, 同时保留左侧from_item中的所有记录, 即使右侧from_item满足联接条件的记录数为零。对于右侧表中没有找到匹配的记录,则右侧的列会以 NULL 值填充。 + +LAST JOIN 是 OpenMLDB SQL 拓展的 JOIN类型. 它的语法和 LEFT JOIN 基本一致, 但在右侧 from_item 后面允许带可选的 ORDER BY 子句, 表示筛选右侧 from_iem 的顺序. 根据是否带有这个 ORDER BY 子句, LAST JOIN分为无排序拼接,和排序拼接。 - 无排序拼接是指:未对右表作排序,直接拼接。 - 排序拼接是指:先对右表排序,然后再拼接。 -与LEFT JOIN相同,LAST JOIN也会返回左表中所有行,即使右表中没有匹配的行。 +与LEFT JOIN相同,LAST JOIN也会返回左表中所有行,即使右表中没有匹配的行。不同的是, LAST JOIN 是一对一, LEFT JOIN 是一对多. ## Syntax ``` -JoinClause - ::= TableRef JoinType 'JOIN' TableRef [OrderByClause] 'ON' Expression +join: + TableRef "LAST" "JOIN" TableRef [OrderByClause] "ON" Expression + | TableRef join_type "JOIN" TableRef "ON" Expression -JoinType ::= 'LAST' +join_type: + 'LEFT' [OUTER] -OrderByClause := 'ORDER' 'BY' +order_by_clause: + 'ORDER' 'BY' ``` ### 使用限制说明 @@ -30,14 +38,17 @@ OrderByClause := 'ORDER' 'BY' ## SQL语句模版 ```sql -SELECT ... FROM table_ref LAST JOIN table_ref ON expression; +SELECT ... FROM t1 LAST JOIN t2 ON expression; + +SELECT ... FROM t1 LEFT JOIN t2 ON expression; ``` ## 边界说明 | SELECT语句元素 | 离线模式 | 在线预览模式 | 在线请求模式 | 说明 | | :--------------------------------------------- | --------- | ------------ | ------------ |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| JOIN Clause| **``✓``** | **``x``** | **``✓``** | 表示数据来源多个表JOIN。OpenMLDB目前仅支持LAST JOIN。在线请求模式下,需要遵循[在线请求模式下LAST JOIN的使用规范](../deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md#在线请求模式下-last-join-的使用规范) | +| LAST JOIN | **``✓``** | **``x``** | **``✓``** | 表示数据来源多个表JOIN。在线请求模式下,需要遵循[在线请求模式下LAST JOIN的使用规范](../deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md#在线请求模式下-last-join-的使用规范) | +| LEFT JOIN | **``x``** | **``x``** | **``✓``** | 由于 LEFT JOIN 是一对多 JOIN, 本身不能直接用于在线请求模式. 但是可以作为其他类型查询内部的子查询, 例如作为 LAST JOIN 的右表. 具体参考[在线请求模式下LAST JOIN的使用规范](../deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md#在线请求模式下-last-join-的使用规范) | ### 未排序的LAST JOIN diff --git a/docs/zh/openmldb_sql/dql/WINDOW_CLAUSE.md b/docs/zh/openmldb_sql/dql/WINDOW_CLAUSE.md index 18f49149429..a206c92fb8c 100644 --- a/docs/zh/openmldb_sql/dql/WINDOW_CLAUSE.md +++ b/docs/zh/openmldb_sql/dql/WINDOW_CLAUSE.md @@ -86,27 +86,43 @@ SELECT select_expr [,select_expr...], window_function_name(expr) OVER window_nam 再看窗口想要什么大小,这里要分窗口类型说明: 1. 时间窗口:时间窗口通常使用s, m, h, d等时间单位,如果没有单位,默认为ms。比如: - [3小时前,当前行] - 3h preceding and current row - [3小时前,30分钟前] - 3h preceding and 30m preceding + - [3小时前,当前行] - 3h preceding and current row + - [3小时前,30分钟前] - 3h preceding and 30m preceding 1. 条数窗口:条数不需要单位。比如: - [10条,当前行] - 10 preceding and current row - [10条,3条] - 10 preceding and 3 preceding + - [10条,当前行] - 10 preceding and current row + - [10条,3条] - 10 preceding and 3 preceding ### 如何推断窗口是什么样的? 首先,先明确是什么执行模式: -离线模式,即批模式,它是对from表的每一行都做一次窗口划分与计算。因此,每一行对应产生一行SQL结果。 -请求模式,会带一条请求行,它会将请求行当做from表的数据,只对该行做窗口划分和计算,因此,只产生一行SQL结果。 +离线模式或在线预览模式,合称为批模式,它是对from表的每一行都做一次窗口划分与计算。因此,每一行对应产生一行SQL结果。 +请求模式,会带一条请求行,它会将请求行当做from表的数据,只对该行做窗口划分和计算,因此,只产生一行SQL结果。注意,不会将请求行插入到表中。 -再看,如何划分窗口: +我们将批模式看作多次请求模式来看待,所以请求模式查询如何划分窗口,我们分为三段来讲: -我们将批模式看作多次请求模式来看待。所以,对一次请求行来说,窗口只可能包含,它自己,与它的partition by列值相等的行(可能的全集)。 +- 对一次请求行来说,窗口**只可能**包含,它自己,与它的partition by列值相等的行 -partition key相等的所有行,还不是窗口,经由order by列排序后,还需要排除窗口范围以外的数据。比如,10 preceding and current row的条数窗口,就要抛弃10行以外的数据行(第10行包含在窗口内),又因为包括current row,于是窗口一共有11行数据。 +- partition key相等的所有行,它们不是乱序,而是按**order by列**排序 -* preceding为闭区间,包含该条,开区间使用open preceding +- 根据rows/rows_range排除窗口范围以外的数据 + - rows:例如,10 preceding and current row的条数窗口,就要抛弃10行以外的数据行(第10行包含在窗口内),又因为包括current row,于是窗口一共有11行数据。 + -rows_range:例如,10s preceding and current row的时间窗口,就要抛弃10s以外的数据行(第10s包含在窗口内),也包括current row,于是窗口只会出现order key值在`[current_row_order_key - 10s, current_row_order_key]`范围内的数据行。 + +```{note} +窗口划分范围,仅与order by列相关。如果认为窗口内行数或具体某数据不符合预期范围,一般是窗口写法的误解,极小概率是SQL引擎计算有误。请以某一个partition key为例,分步检查表的数据(以下操作都是在线模式): +- 提取与该key相等的所有数据。可以使用`select * from table where partition_key = xxx`来提取,或使用源数据文件,通过pandas/spark等工具提取。 +- 再按order by列排序,这类似于window设置窗口为unbounded preceding and current row。此处,可以将手动处理的数据和OpenMLDB的unbounded window计算结果进行对比。 + - 由于OpenMLDB只支持在窗口内聚合,很难看到窗口的数据全貌,而且窗口内数据较多时,查看全部也是很难的。通常是使用count/min/max/lag等聚合函数来衡量窗口内数据的数量和范围。 + - 如果仍需要通过窗口内具体数据来确认,可以使用top来展示前k大的值,但它会对列进行再排序,不能等同于窗口排序(order by列排序)。其他聚合函数,参考[udf函数](../udfs_8h.md)。 +- 最后,再检查窗口的rows/rows_range设置是否符合预期。 + - 通常情况,如果前两步没问题,条数划分一般不会有问题。 + - 时间划分,需要注意时间单位。OpenMLDB中order by列无论是timestamp还是bigint,都当作整数来计算的,timestamp是转换为ms为单位的整数。我们支持在窗口设置中使用时间单位,但不会对表中的order by列值做任何单位假设。例如,如果order by列 +并非timestamp,而是设置整数`20230905`,在时间窗口设置5ms时,窗口的范围是`[20230905 - 5, 20230905]`,而不是`[20230905 00:00:00 - 5ms, 20230905]`。**请谨慎对待order by列,最方便的做法是,任何时间格式都将其转换为timestamp或ms为单位的bigint**。 +``` + +* preceding为闭区间,包含该条,开区间需使用open preceding 窗口还可以exclude current time,current row等,详情见下文。 @@ -119,7 +135,7 @@ partition key相等的所有行,还不是窗口,经由order by列排序后 ## 基本的 WindowSpec 语法元素 -### Window Partition Clause 和 Window OrderBy Clause +### WINDOW PARTITION BY clause 和 WINDOW ORDER BY clause ```sql WindowPartitionClause @@ -129,9 +145,18 @@ WindowOrderByClause ::= ( 'ORDER' 'BY' ByList ) ``` -`PARTITION BY`选项将查询的行分为一组进入*partitions*, 这些行在窗口函数中单独处理。`PARTITION BY`和查询级别`GROUP BY` 子句做相似的工作,除了它的表达式只能作为表达式不能作为输出列的名字或数。OpenMLDB要求必须配置`PARTITION BY`。并且目前**仅支持按列分组**,无法支持按运算和函数表达式分组。 +`PARTITION BY`选项将查询的行分为一组进入*partitions*, 这些行在窗口函数中单独处理。`PARTITION BY`和查询级别`GROUP BY` 子句做相似的工作, 只是它只能作为表达式不能作为查询结果的输出列或输出列 ID。OpenMLDB要求必须配置`PARTITION BY`。PARTITION BY list 可以有多个, 但**仅支持按列分组**,无法支持按运算或函数表达式分组。 + +`ORDER BY` 选项决定分区中的行被窗口函数处理的顺序。它和查询级别`ORDER BY`子句做相似的工作, 同样不能作为查询结果的输出列或者输出列 ID。OpenMLDB 目前**仅支持按列排序**,ORDER BY list 有且只能有一个, 不支持按运算或函数表达式排序。**OpenMLDB 0.8.4** 以后, 在线模式下 ORDER BY 子句可以不写 (离线模式暂时不支持), 表示窗口内的列将以不确定的顺序处理, 不带 ORDER BY 子句的窗口需要额外满足如下条件: + +1. 不能有`EXCLUDE CURRENT_TIME` +2. 对于 ROWS 类型窗口没有更多限制, 对于 ROWS_RANGE 类型窗口: + 1. 窗口 FRAME 的边界不能是 `offset [OPEN] PRECEDING/FOLLOWING` 的格式, 目前情况只能为 `UNBOUNDED PRECEDING AND CURRENT ROW` + +```{note} +窗口不带 ORDER BY 的情况, 意味着对于在线预览模式, 计算结果是不确定的, 无法预测哪些行进去了窗口哪些行没有. 同时对于一些通用窗口函数, 例如 `lag, first_value`, 在所有模式下得到的计算结果都是不确定的,无法预测窗口内行的先后顺序. +``` -`ORDER BY` 选项决定分区中的行被窗口函数处理的顺序。它和查询级别`ORDER BY`子句做相似的工作, 但是同样的它不能作为输出列的名字或数。同样,OpenMLDB要求必须配置`ORDER BY`。并且目前**仅支持按列排序**,无法支持按运算和函数表达式排序。 ### Window Frame Clause @@ -332,5 +357,5 @@ WINDOW w1 AS (PARTITION BY col1 ORDER BY col5 ROWS_RANGE BETWEEN 10s PRECEDING A ``` ```{seealso} -窗口计算可使用的聚合函数,参考[Built-in Functions](../functions_and_operators/Files/udfs_8h.md) +窗口计算可使用的聚合函数,参考[Built-in Functions](../udfs_8h.md) ``` diff --git a/docs/zh/openmldb_sql/functions_and_operators/index.rst b/docs/zh/openmldb_sql/functions_and_operators/index.rst index 36329c03045..8dfb1e18cee 100644 --- a/docs/zh/openmldb_sql/functions_and_operators/index.rst +++ b/docs/zh/openmldb_sql/functions_and_operators/index.rst @@ -7,4 +7,3 @@ :maxdepth: 1 operators - Files/udfs_8h diff --git a/docs/zh/openmldb_sql/functions_and_operators/operators.md b/docs/zh/openmldb_sql/functions_and_operators/operators.md index e5a7ca86afe..31d79184bd8 100644 --- a/docs/zh/openmldb_sql/functions_and_operators/operators.md +++ b/docs/zh/openmldb_sql/functions_and_operators/operators.md @@ -1,4 +1,4 @@ -# 运算符 +# 表达式和运算符 ## 运算符优先级 @@ -19,9 +19,7 @@ %left UNARY_PRECEDENCE // For all unary operators, +, -, ~ ``` -## 各类运算 - -### 1. 比较运算 +## 比较运算 | 操作符名 | 功能描述 | | :-------------- | :--------------------- | @@ -37,7 +35,7 @@ | `ILIKE` | 模糊匹配, 大小写不敏感 | | `RLIKE` | 正则表达式匹配 | -### 2. 逻辑运算 +## 逻辑运算 | 操作符名 | 功能描述 | | :---------- | :------- | @@ -46,7 +44,7 @@ | `XOR` | 逻辑与或 | | `NOT`, `!` | 逻辑非, unary operator | -### 3. 算术运算 +## 算术运算 | 操作符名 | 功能描述 | | :--------- | :------------------------------------------------------- | @@ -59,7 +57,7 @@ | `+` | Unary plus | | `-` | Unary minus, 只支持数值型操作数-number | -### 4. 位运算 +## 位运算 | 操作符名 | Description | | :------- | :---------- | @@ -68,7 +66,7 @@ | `^` | Bitwise XOR | | `~` | Bitwise NOT, unary operator | -### 5. 类型运算和函数 +## 类型运算和函数 | 操作符名 | Description | | :------------- | :--------------------------------------------------------- | @@ -97,7 +95,7 @@ SELECT INT(1.2); X:表示从原类型转换为目标类型的转换是不支持的 -| src\|dist | bool | smallint | int | float | int64 | double | timestamp | date | string | +| src\|dst | bool | smallint | int | float | int64 | double | timestamp | date | string | | :------------ | :----- | :------- | :----- | :----- | :----- | :----- | :-------- | :----- | :----- | | **bool** | Safe | Safe | Safe | Safe | Safe | Safe | UnSafe | X | Safe | | **smallint** | UnSafe | Safe | Safe | Safe | Safe | Safe | UnSafe | X | Safe | @@ -114,3 +112,14 @@ X:表示从原类型转换为目标类型的转换是不支持的 | 操作符名 | 功能描述 | | :------- | :------------------------ | | `=` | 赋值 (可用于 SET 语句中 ) | + +## 条件表达式 + +### CASE 表达式 + ```sql + SELECT case 'bb' when 'aa' then 'apple' else 'nothing' end; -- SIMPLE CASE WHEN + SELECT case + when 'bb'='aa' then 'apple' + when 'bb'='bb' then 'banana' + else 'nothing' end; -- SEARCHED CASE WHEN + ``` diff --git a/docs/zh/openmldb_sql/index.rst b/docs/zh/openmldb_sql/index.rst index 7d00e9ed532..149147f1f55 100644 --- a/docs/zh/openmldb_sql/index.rst +++ b/docs/zh/openmldb_sql/index.rst @@ -10,6 +10,7 @@ OpenMLDB SQL language_structure/index data_types/index functions_and_operators/index + udfs_8h dql/index dml/index ddl/index diff --git a/docs/zh/openmldb_sql/sql_difference.md b/docs/zh/openmldb_sql/sql_difference.md index 3118f8f71bb..0b521dd2eca 100644 --- a/docs/zh/openmldb_sql/sql_difference.md +++ b/docs/zh/openmldb_sql/sql_difference.md @@ -14,7 +14,7 @@ | -------------- | ---------------------------- | -------------------------------- | -------------------------------- | ------------ | ------------------------------------------------------------ | | WHERE 子句 | ✓ | ✓ | ✕ | ✓ | 部分功能可以通过带有 `_where` 后缀的内置函数实现 | | HAVING 子句 | ✓ | ✓ | X | ✓ | | -| JOIN 子句 | ✓ | ✕ | ✓ | ✓ | OpenMLDB 仅支持特有的 **LAST JOIN** | +| JOIN 子句 | ✓ | ✕ | ✓ | ✓ | OpenMLDB 支持特有的 **LAST JOIN**, 和 **LEFT JOIN** | | GROUP BY 分组 | ✓ | ✕ | ✕ | ✓ | | | ORDER BY 关键字 | ✓ | ✓ | ✓ | ✓ | 仅支持在 `WINDOW` 和 `LAST JOIN` 子句内部使用,不支持倒排序 `DESC` | | LIMIT 限制行数 | ✓ | ✓ | ✕ | ✓ | | @@ -54,7 +54,7 @@ | LAST JOIN | ✓ | ✓ | ✕ | | 子查询 / WITH 子句 | ✓ | ✓ | ✕ | -虽然在线请求模式无法支持 `WHERE` 子句,但是部分功能可以通过带有 `_where` 后缀的计算函数实现,比如 `count_where`, `avg_where` 等,详情查看[内置计算函数文档](functions_and_operators/Files/udfs_8h.md)。 +虽然在线请求模式无法支持 `WHERE` 子句,但是部分功能可以通过带有 `_where` 后缀的计算函数实现,比如 `count_where`, `avg_where` 等,详情查看[内置计算函数文档](./udfs_8h.md)。 ### LIMIT 子句 @@ -81,7 +81,7 @@ WINDOW 子句和 GROUP BY & HAVING 子句不支持同时使用。上线时 WINDO 特殊限制: -- 在线请求模式下,WINDOW 的输入是 LAST JOIN 或者子查询内的 LAST JOIN, 注意窗口的定义里 `PARTITION BY` & `ORDER BY` 的列都必须来自 JOIN 最左边的表。 +- 在线请求模式下,WINDOW 的输入是 LAST JOIN 或者带子查询内的 LAST JOIN, 注意窗口的定义里 `PARTITION BY` & `ORDER BY` 的列都必须来自 JOIN 最左边的表。 ### GROUP BY & HAVING 子句 @@ -94,19 +94,23 @@ GROUP BY 语句,目前仍为实验性功能,仅支持输入表是一张物 | LAST JOIN | ✕ | ✕ | ✕ | | 子查询 | ✕ | ✕ | ✕ | -### JOIN 子句(LAST JOIN) +### JOIN 子句 -OpenMLDB 仅支持 LAST JOIN 一种 JOIN 语法,详细描述参考扩展语法的 LAST JOIN 部分。JOIN 有左右两个输入,在线请求模式下,支持两个输入为物理表,或者特定的子查询,详见表格,未列出情况不支持。 +OpenMLDB 支持 LAST JOIN 和 LEFT JOIN,详细描述参考扩展语法的 JOIN 部分。JOIN 有左右两个输入,在线请求模式下,支持两个输入为物理表,或者特定的子查询,LEFT JOIN 不能直接用于在线请求模式, 但可以作为 LAST JOIN 的右表输入. 详见表格,未列出情况不支持。 -| **应用于** | **离线模式** | **在线预览模式** | **在线请求模式** | -| ------------------------------------------------------------ | ------------ | ---------------- | ---------------- | -| 两个表引用 | ✓ | ✕ | ✓ | -| 子查询, 仅包括:
左右表均为简单列筛选
左右表为 WINDOW 或 LAST JOIN 操作 | ✓ | ✓ | ✓ | +| **应用于** | **离线模式** | **在线预览模式** | **在线请求模式** | +| ---------------------------------------------- | ------------ | ---------------- | ---------------- | +| LAST JOIN + 两个表引用 | ✓ | ✕ | ✓ | +| LAST JOIN + 左右表均为简单列筛选 | ✓ | ✕ | ✓l | +| LAST JOIN + 右表是带 WHERE 条件过滤的单表查询 | ✓ | ✕ | ✓ | +| LAST JOIN左表或右表为 WINDOW 或 LAST JOIN 操作 | ✓ | ✕ | ✓ | +| LAST JOIN + 右表是LEFT JOIN 的子查询 | ✕ | ✕ | ✓ | +| LEFT JOIN | ✕ | ✕ | ✕ | 特殊限制: - 关于特定子查询的 LAST JOIN 上线,还有额外要求,详见[上线要求](../openmldb_sql/deployment_manage/ONLINE_REQUEST_REQUIREMENTS.md#在线请求模式下-last-join-的使用规范) 。 -- 在线预览模式下暂不支持 LAST JOIN +- 在线预览模式下暂不支持 LAST JOIN 和 LEFT JOIN ### WITH 子句 @@ -118,7 +122,7 @@ OpenMLDB (>= v0.7.2) 支持非递归的 WITH 子句。WITH 子句等价于其它 ### ORDER BY 关键字 -排序关键字 `ORDER BY` 仅在窗口定义 `WINDOW` 和拼表操作 `LAST JOIN` 子句内部被支持,并且不支持倒排序关键字 `DESC`。参见 WINDOW 子句和 LAST JOIN 子句内的相关说明。 +排序关键字 `ORDER BY` 仅在窗口定义 `WINDOW` 和拼表操作 `LAST JOIN` 子句内部被支持,并且不支持倒排序关键字 `DESC`。 OpenMLDB 0.8.4 以后支持窗口定义不带 ORDER BY, 但需额外满足特定条件. 参见 WINDOW 子句和 LAST JOIN 子句内的相关说明。 ### 聚合函数 @@ -127,7 +131,7 @@ OpenMLDB (>= v0.7.2) 支持非递归的 WITH 子句。WITH 子句等价于其它 特殊限制: - OpenMLDB v0.6.0 开始支持在线预览模式的全表聚合,但注意所描述的[扫描限制配置](https://openmldb.feishu.cn/wiki/wikcnhBl4NsKcAX6BO9NDtKAxDf#doxcnLWICKzccMuPiWwdpVjSaIe)。 -- OpenMLDB 有较多的聚合函数扩展,请查看产品文档具体查询所支持的函数 [OpenMLDB 内置函数](../openmldb_sql/functions_and_operators/Files/udfs_8h.md)。 +- OpenMLDB 有较多的聚合函数扩展,请查看产品文档具体查询所支持的函数 [OpenMLDB 内置函数](../openmldb_sql/udfs_8h.md)。 ## 扩展语法 @@ -149,10 +153,10 @@ OpenMLDB 主要对 `WINDOW` 以及 `LAST JOIN` 语句进行了深度定制化开 | **语句元素** | **支持语法** | **说明** | **必需 ?** | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------- | | 数据定义 | PARTITION BY | 可支持多列
支持的列数据类型: bool, int16, int32, int64, string, date, timestamp | ✓ | -| 数据排序 | ORDER BY | 仅支持对单一列排序
可支持数据类型: int16, int32, int64, timestamp
不支持倒序 `DESC` | ✓ | +| 数据排序 | ORDER BY | 仅支持对单一列排序
可支持数据类型: int16, int32, int64, timestamp
不支持倒序 `DESC`
OpenMLDB 0.8.4 之前必填 | - | | 范围定义 |
基本上下界定义语法:ROWS/ROWS_RANGE BETWEEN ... AND ...
支持范围定义关键字 PRECEDING, OPEN PRECEDING, CURRENT ROW, UNBOUNDED | 必须给定上下边界
不支持边界关键字 FOLLOWING
在线请求模式中,CURRENT ROW 为当前的请求行。在表格视角下,当前行将会被虚拟的插入到表格根据 ORDER BY 排序的正确位置上。 | ✓ | -| 范围单位 | ROWS
ROWS_RANGE(扩展) | ROWS_RANGE 为扩展语法,其定义的窗口边界属性等价于标准 SQL 的 RANGE 类型窗口,支持用数值或者带时间单位的数值定义窗口边界,后者为拓展语法。
带时间单位定义的窗口范围,等价于时间转化成毫秒数值后的窗口定义。例如 `ROWS_RANGE 10s PRCEDING ...` 和 `ROWS_RANGE 10000 PRECEDNG ...` 是等价的。 | ✓ | -| 窗口属性(扩展) | MAXSIZE
EXCLUDE CURRENT_ROW
EXCLUDE CURRENT_TIME
INSTANCE_NOT_IN_WINDOW | MAXSIZE 只对 ROWS_RANGE 有效 | - | +| 范围单位 | ROWS
ROWS_RANGE(扩展) | ROWS_RANGE 为扩展语法,其定义的窗口边界属性等价于标准 SQL 的 RANGE 类型窗口,支持用数值或者带时间单位的数值定义窗口边界,后者为拓展语法。
带时间单位定义的窗口范围,等价于时间转化成毫秒数值后的窗口定义。例如 `ROWS_RANGE 10s PRCEDING ...` 和 `ROWS_RANGE 10000 PRECEDNG ...` 是等价的。 | ✓ | +| 窗口属性(扩展) | MAXSIZE
EXCLUDE CURRENT_ROW
EXCLUDE CURRENT_TIME
INSTANCE_NOT_IN_WINDOW | MAXSIZE 只对 ROWS_RANGE 有效
不带 ORDER BY 和 EXCLUDE CURRENT_TIME 不能同时使用 | - | | 多表定义(扩展) | 实际使用中语法形态较为复杂,参考:
[跨表特征开发教程](../tutorial/tutorial_sql_2.md)
[WINDOW UNION 语法文档](../openmldb_sql/dql/WINDOW_CLAUSE.md#1-window--union) | 允许合并多个表
允许联合简单子查询
实践中,一般和聚合函数搭配使用,实现跨表的聚合操作 | - | | 匿名窗口 | - | 必须包括 PARTITION BY、ORDER BY、以及窗口范围定义 | - | @@ -238,15 +242,15 @@ SELECT 在实际开发中,较多的应用的数据是存放在多个表格中,在这种情况下,一般会使用 WINDOW ... UNION 的语法进行跨表的聚合操作。请参考[跨表特征开发教程](../tutorial/tutorial_sql_2.md)关于“ 副表多行聚合特征”部分。 -### LAST JOIN 子句 +### JOIN 子句 -关于 LAST JOIN 详细语法规范,请参考 [LAST JOIN 文档](../openmldb_sql/dql/JOIN_CLAUSE.md#join-clause)。 +关于 JOIN 详细语法规范,请参考 [JOIN 文档](../openmldb_sql/dql/JOIN_CLAUSE.md#join-clause)。 | **语句元素** | **支持语法** | **说明** | **必需?** | | ------------ | ------------ | ------------------------------------------------------------ | ---------- | | ON | ✓ | 列类型支持:BOOL, INT16, INT32, INT64, STRING, DATE, TIMESTAMP | ✓ | | USING | 不支持 | - | - | -| ORDER BY | ✓ | 后面只能接单列列类型 : INT16, INT32, INT64, TIMESTAMP
不支持倒序关键字 DESC | - | +| ORDER BY | ✓ | LAST JOIN 的拓展语法, LEFT JON 不支持.
后面只能接单列列类型 : INT16, INT32, INT64, TIMESTAMP, 不支持倒序关键字 DESC | - | #### LAST JOIN 举例 @@ -256,4 +260,10 @@ SELECT FROM t1 LAST JOIN t2 ON t1.col1 = t2.col1; + +SELECT + * +FROM + t1 +LEFT JOIN t2 ON t1.col1 = t2.col1; ``` diff --git a/docs/zh/openmldb_sql/udf_develop_guide.md b/docs/zh/openmldb_sql/udf_develop_guide.md index 7fe4e81988d..761e66dea6f 100644 --- a/docs/zh/openmldb_sql/udf_develop_guide.md +++ b/docs/zh/openmldb_sql/udf_develop_guide.md @@ -11,7 +11,7 @@ #### 2.1.1 C++函数名规范 - C++内置函数名统一使用[snake_case](https://en.wikipedia.org/wiki/Snake_case)风格 - 要求函数名能清晰表达函数功能 -- 函数不能重名。函数名不能和内置函数及其他自定义函数重名。所有内置函数的列表参考[这里](../openmldb_sql/functions_and_operators/Files/udfs_8h.md) +- 函数不能重名。函数名不能和内置函数及其他自定义函数重名。所有内置函数的列表参考[这里](../openmldb_sql/udfs_8h.md) #### 2.1.2 C++类型与SQL类型对应关系 内置C++函数的参数类型限定为:BOOL类型,数值类型,时间戳日期类型和字符串类型。C++类型SQL类型对应关系如下: diff --git a/docs/en/reference/sql/functions_and_operators/Files/udfs_8h.md b/docs/zh/openmldb_sql/udfs_8h.md similarity index 68% rename from docs/en/reference/sql/functions_and_operators/Files/udfs_8h.md rename to docs/zh/openmldb_sql/udfs_8h.md index ac96c6bfc3f..9cfab05977f 100644 --- a/docs/en/reference/sql/functions_and_operators/Files/udfs_8h.md +++ b/docs/zh/openmldb_sql/udfs_8h.md @@ -10,158 +10,158 @@ title: udfs/udfs.h | Name | Description | | -------------- | -------------- | -| **[abs](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-abs)**()|
Return the absolute value of expr. | -| **[acos](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-acos)**()|
Return the arc cosine of expr. | -| **[add](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-add)**()|
Compute sum of two arguments. | -| **[add_months](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-add-months)**()|
adds an integer months to a given date, returning the resulting date. | -| **[array_contains](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-array-contains)**()|
array_contains(array, value) - Returns true if the array contains the value. | -| **[asin](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-asin)**()|
Return the arc sine of expr. | -| **[at](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-at)**()| | -| **[atan](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-atan)**()|
Return the arc tangent of expr If called with one parameter, this function returns the arc tangent of expr. If called with two parameters X and Y, this function returns the arc tangent of Y / X. | -| **[atan2](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-atan2)**()|
Return the arc tangent of Y / X.. | -| **[avg](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg)**()|
Compute average of values. | -| **[avg_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg-cate)**()|
Compute average of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[avg_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V', separated by comma, and sorted by key in ascend order. | -| **[avg_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-avg-where)**()|
Compute average of values match specified condition. | -| **[bigint](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-bigint)**()| | -| **[bool](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-bool)**()|
Cast string expression to bool. | -| **[ceil](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ceil)**()|
Return the smallest integer value not less than the expr. | -| **[ceiling](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ceiling)**()| | -| **[char](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-char)**()|
Returns the ASCII character having the binary equivalent to expr. If n >= 256 the result is equivalent to char(n % 256). | -| **[char_length](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-char-length)**()|
Returns the length of the string. It is measured in characters and multibyte character string is not supported. | -| **[character_length](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-character-length)**()| | -| **[concat](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-concat)**()|
This function returns a string resulting from the joining of two or more string values in an end-to-end manner. (To add a separating value during joining, see concat_ws.) | -| **[concat_ws](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-concat-ws)**()|
Returns a string resulting from the joining of two or more string value in an end-to-end manner. It separates those concatenated string values with the delimiter specified in the first function argument. | -| **[cos](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-cos)**()|
Return the cosine of expr. | -| **[cot](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-cot)**()|
Return the cotangent of expr. | -| **[count](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count)**()|
Compute number of values. | -| **[count_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count-cate)**()|
Compute count of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[count_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[count_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-count-where)**()|
Compute number of values match specified condition. | -| **[date](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-date)**()|
Cast timestamp or string expression to date (date >= 1900-01-01) | -| **[date_format](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-date-format)**()|
Formats the date value according to the format string. | -| **[datediff](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-datediff)**()|
days difference from date1 to date2 | -| **[day](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-day)**()| | -| **[dayofmonth](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-dayofmonth)**()|
Return the day of the month for a timestamp or date. | -| **[dayofweek](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-dayofweek)**()|
Return the day of week for a timestamp or date. | -| **[dayofyear](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-dayofyear)**()|
Return the day of year for a timestamp or date. Returns 0 given an invalid date. | -| **[degrees](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-degrees)**()|
Convert radians to degrees. | -| **[distinct_count](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-distinct-count)**()|
Compute number of distinct values. | -| **[double](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-double)**()|
Cast string expression to double. | -| **[drawdown](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-drawdown)**()|
Compute drawdown of values. | -| **[earth_distance](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-earth-distance)**()|
Returns the great circle distance between two points on the surface of the Earth. Km as return unit. add a minus (-) sign if heading west (W) or south (S). | -| **[entropy](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-entropy)**()|
Calculate Shannon entropy of a column of values. Null values are skipped. | -| **[ew_avg](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ew-avg)**()|
Compute exponentially-weighted average of values. It's equivalent to pandas ewm(alpha={alpha}, adjust=True, ignore_na=True, com=None, span=None, halflife=None, min_periods=0) | -| **[exp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-exp)**()|
Return the value of e (the base of natural logarithms) raised to the power of expr. | -| **[farm_fingerprint](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-farm-fingerprint)**()| | -| **[first_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-first-value)**()|
Returns the value of expr from the latest row (last row) of the window frame. | -| **[float](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-float)**()|
Cast string expression to float. | -| **[floor](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-floor)**()|
Return the largest integer value not less than the expr. | -| **[get_json_object](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-get-json-object)**()|
Extracts a JSON object from [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901)| -| **[hash64](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-hash64)**()|
Returns a hash value of the arguments. It is not a cryptographic hash function and should not be used as such. | -| **[hex](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-hex)**()|
Convert integer to hexadecimal. | -| **[hour](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-hour)**()|
Return the hour for a timestamp. | -| **[identity](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-identity)**()|
Return value. | -| **[if_null](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-if-null)**()|
If input is not null, return input value; else return default value. | -| **[ifnull](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ifnull)**()| | -| **[ilike_match](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ilike-match)**()|
pattern match same as ILIKE predicate | -| **[inc](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-inc)**()|
Return expression + 1. | -| **[int](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int)**()| | -| **[int16](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int16)**()|
Cast string expression to int16. | -| **[int32](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int32)**()|
Cast string expression to int32. | -| **[int64](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-int64)**()|
Cast string expression to int64. | -| **[is_null](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-is-null)**()|
Check if input value is null, return bool. | -| **[isnull](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-isnull)**()| | -| **[join](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-join)**()|
For each string value from specified column of window, join by delimeter. Null values are skipped. | -| **[json_array_length](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-json-array-length)**()|
Returns the number of elements in the outermost JSON array. | -| **[lag](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lag)**()|
Returns value evaluated at the row that is offset rows before the current row within the partition. Offset is evaluated with respect to the current row. | -| **[last_day](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-last-day)**()|
Return the last day of the month to which the date belongs to. | -| **[lcase](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lcase)**()|
Convert all the characters to lowercase. Note that characters with values > 127 are simply returned. | -| **[like_match](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-like-match)**()|
pattern match same as LIKE predicate | -| **[list_except_by_key](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-list-except-by-key)**()|
Return list of elements in list1 but keys not in except_str. | -| **[list_except_by_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-list-except-by-value)**()|
Return list of elements in list1 but values not in except_str. | -| **[ln](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ln)**()|
Return the natural logarithm of expr. | -| **[log](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-log)**()|
log(base, expr) If called with one parameter, this function returns the natural logarithm of expr. If called with two parameters, this function returns the logarithm of expr to the base. | -| **[log10](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-log10)**()|
Return the base-10 logarithm of expr. | -| **[log2](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-log2)**()|
Return the base-2 logarithm of expr. | -| **[lower](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lower)**()| | -| **[make_tuple](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-make-tuple)**()| | -| **[max](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max)**()|
Compute maximum of values. | -| **[max_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max-cate)**()|
Compute maximum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[max_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[max_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-max-where)**()|
Compute maximum of values match specified condition. | -| **[maximum](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-maximum)**()|
Compute maximum of two arguments. | -| **[median](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-median)**()|
Compute the median of values. | -| **[min](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min)**()|
Compute minimum of values. | -| **[min_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min-cate)**()|
Compute minimum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[min_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[min_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-min-where)**()|
Compute minimum of values match specified condition. | -| **[minimum](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-minimum)**()|
Compute minimum of two arguments. | -| **[minute](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-minute)**()|
Return the minute for a timestamp. | -| **[month](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-month)**()|
Return the month part of a timestamp or date. | -| **[nth_value_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-nth-value-where)**()|
Returns the value of expr from the idx th row matches the condition. | -| **[nvl](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-nvl)**()| | -| **[nvl2](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-nvl2)**()|
nvl2(expr1, expr2, expr3) - Returns expr2 if expr1 is not null, or expr3 otherwise. | -| **[pmod](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-pmod)**()|
Compute pmod of two arguments. If any param is NULL, output NULL. If divisor is 0, output NULL. | -| **[pow](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-pow)**()|
Return the value of expr1 to the power of expr2. | -| **[power](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-power)**()| | -| **[radians](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-radians)**()|
Returns the argument X, converted from degrees to radians. (Note that π radians equals 180 degrees.) | -| **[regexp_like](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-regexp-like)**()|
pattern match same as RLIKE predicate (based on RE2) | -| **[replace](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-replace)**()|
replace(str, search[, replace]) - Replaces all occurrences of `search` with `replace`| -| **[reverse](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-reverse)**()|
Returns the reversed given string. | -| **[round](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-round)**()|
Returns expr rounded to d decimal places using HALF_UP rounding mode. | -| **[second](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-second)**()|
Return the second for a timestamp. | -| **[sin](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sin)**()|
Return the sine of expr. | -| **[size](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-size)**()|
Get the size of a List (e.g., result of split) | -| **[smallint](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-smallint)**()| | -| **[split](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split)**()|
Split string to list by delimeter. Null values are skipped. | -| **[split_array](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split-array)**()|
Split string to array of string by delimeter. | -| **[split_by_key](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split-by-key)**()|
Split string by delimeter and split each segment as kv pair, then add each key to output list. Null or illegal segments are skipped. | -| **[split_by_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-split-by-value)**()|
Split string by delimeter and split each segment as kv pair, then add each value to output list. Null or illegal segments are skipped. | -| **[sqrt](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sqrt)**()|
Return square root of expr. | -| **[std](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-std)**()| | -| **[stddev](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-stddev)**()|
Compute sample standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / (n-1) )`| -| **[stddev_pop](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-stddev-pop)**()|
Compute population standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / n )`| -| **[stddev_samp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-stddev-samp)**()| | -| **[strcmp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-strcmp)**()|
Returns 0 if the strings are the same, -1 if the first argument is smaller than the second according to the current sort order, and 1 otherwise. | -| **[string](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-string)**()|
Return string converted from timestamp expression. | -| **[substr](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-substr)**()| | -| **[substring](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-substring)**()|
Return a substring `len` characters long from string str, starting at position `pos`. Alias function: `substr`| -| **[sum](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum)**()|
Compute sum of values. | -| **[sum_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum-cate)**()|
Compute sum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[sum_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | -| **[sum_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sum-where)**()|
Compute sum of values match specified condition. | -| **[tan](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-tan)**()|
Return the tangent of expr. | -| **[timestamp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-timestamp)**()|
Cast int64, date or string expression to timestamp. | -| **[top](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top)**()|
Compute top k of values and output string separated by comma. The outputs are sorted in desc order. | -| **[top1_ratio](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top1-ratio)**()|
Compute the top1 occurring value's ratio. | -| **[top_n_key_avg_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_count_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_max_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_min_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_key_ratio_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | -| **[top_n_key_sum_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-key-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_avg_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_count_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_max_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_min_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[top_n_value_ratio_cate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | -| **[top_n_value_sum_cate_where](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-top-n-value-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | -| **[topn_frequency](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-topn-frequency)**()|
Return the topN keys sorted by their frequency. | -| **[truncate](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-truncate)**()|
Return the nearest integer that is not greater in magnitude than the expr. | -| **[ucase](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-ucase)**()|
Convert all the characters to uppercase. Note that characters values > 127 are simply returned. | -| **[unhex](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-unhex)**()|
Convert hexadecimal to binary string. | -| **[unix_timestamp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-unix-timestamp)**()|
Cast date or string expression to unix_timestamp. If empty string or NULL is provided, return current timestamp. | -| **[upper](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-upper)**()| | -| **[var_pop](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-var-pop)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / n`| -| **[var_samp](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-var-samp)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / (n-1)`| -| **[variance](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-variance)**()| | -| **[week](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-week)**()| | -| **[weekofyear](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-weekofyear)**()|
Return the week of year for a timestamp or date. | -| **[window_split](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-window-split)**()|
For each string value from specified column of window, split by delimeter and add segment to output list. Null values are skipped. | -| **[window_split_by_key](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-window-split-by-key)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each key to output list. Null and illegal segments are skipped. | -| **[window_split_by_value](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-window-split-by-value)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each value to output list. Null and illegal segments are skipped. | -| **[year](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-year)**()|
Return the year part of a timestamp or date. | +| **[abs](/openmldb_sql/Files/udfs_8h.md#function-abs)**()|
Return the absolute value of expr. | +| **[acos](/openmldb_sql/Files/udfs_8h.md#function-acos)**()|
Return the arc cosine of expr. | +| **[add](/openmldb_sql/Files/udfs_8h.md#function-add)**()|
Compute sum of two arguments. | +| **[add_months](/openmldb_sql/Files/udfs_8h.md#function-add-months)**()|
adds an integer months to a given date, returning the resulting date. | +| **[array_contains](/openmldb_sql/Files/udfs_8h.md#function-array-contains)**()|
array_contains(array, value) - Returns true if the array contains the value. | +| **[asin](/openmldb_sql/Files/udfs_8h.md#function-asin)**()|
Return the arc sine of expr. | +| **[at](/openmldb_sql/Files/udfs_8h.md#function-at)**()| | +| **[atan](/openmldb_sql/Files/udfs_8h.md#function-atan)**()|
Return the arc tangent of expr If called with one parameter, this function returns the arc tangent of expr. If called with two parameters X and Y, this function returns the arc tangent of Y / X. | +| **[atan2](/openmldb_sql/Files/udfs_8h.md#function-atan2)**()|
Return the arc tangent of Y / X.. | +| **[avg](/openmldb_sql/Files/udfs_8h.md#function-avg)**()|
Compute average of values. | +| **[avg_cate](/openmldb_sql/Files/udfs_8h.md#function-avg-cate)**()|
Compute average of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[avg_cate_where](/openmldb_sql/Files/udfs_8h.md#function-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V', separated by comma, and sorted by key in ascend order. | +| **[avg_where](/openmldb_sql/Files/udfs_8h.md#function-avg-where)**()|
Compute average of values match specified condition. | +| **[bigint](/openmldb_sql/Files/udfs_8h.md#function-bigint)**()| | +| **[bool](/openmldb_sql/Files/udfs_8h.md#function-bool)**()|
Cast string expression to bool. | +| **[ceil](/openmldb_sql/Files/udfs_8h.md#function-ceil)**()|
Return the smallest integer value not less than the expr. | +| **[ceiling](/openmldb_sql/Files/udfs_8h.md#function-ceiling)**()| | +| **[char](/openmldb_sql/Files/udfs_8h.md#function-char)**()|
Returns the ASCII character having the binary equivalent to expr. If n >= 256 the result is equivalent to char(n % 256). | +| **[char_length](/openmldb_sql/Files/udfs_8h.md#function-char-length)**()|
Returns the length of the string. It is measured in characters and multibyte character string is not supported. | +| **[character_length](/openmldb_sql/Files/udfs_8h.md#function-character-length)**()| | +| **[concat](/openmldb_sql/Files/udfs_8h.md#function-concat)**()|
This function returns a string resulting from the joining of two or more string values in an end-to-end manner. (To add a separating value during joining, see concat_ws.) | +| **[concat_ws](/openmldb_sql/Files/udfs_8h.md#function-concat-ws)**()|
Returns a string resulting from the joining of two or more string value in an end-to-end manner. It separates those concatenated string values with the delimiter specified in the first function argument. | +| **[cos](/openmldb_sql/Files/udfs_8h.md#function-cos)**()|
Return the cosine of expr. | +| **[cot](/openmldb_sql/Files/udfs_8h.md#function-cot)**()|
Return the cotangent of expr. | +| **[count](/openmldb_sql/Files/udfs_8h.md#function-count)**()|
Compute number of values. | +| **[count_cate](/openmldb_sql/Files/udfs_8h.md#function-count-cate)**()|
Compute count of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[count_cate_where](/openmldb_sql/Files/udfs_8h.md#function-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[count_where](/openmldb_sql/Files/udfs_8h.md#function-count-where)**()|
Compute number of values match specified condition. | +| **[date](/openmldb_sql/Files/udfs_8h.md#function-date)**()|
Cast timestamp or string expression to date (date >= 1900-01-01) | +| **[date_format](/openmldb_sql/Files/udfs_8h.md#function-date-format)**()|
Formats the date value according to the format string. | +| **[datediff](/openmldb_sql/Files/udfs_8h.md#function-datediff)**()|
days difference from date1 to date2 | +| **[day](/openmldb_sql/Files/udfs_8h.md#function-day)**()| | +| **[dayofmonth](/openmldb_sql/Files/udfs_8h.md#function-dayofmonth)**()|
Return the day of the month for a timestamp or date. | +| **[dayofweek](/openmldb_sql/Files/udfs_8h.md#function-dayofweek)**()|
Return the day of week for a timestamp or date. | +| **[dayofyear](/openmldb_sql/Files/udfs_8h.md#function-dayofyear)**()|
Return the day of year for a timestamp or date. Returns 0 given an invalid date. | +| **[degrees](/openmldb_sql/Files/udfs_8h.md#function-degrees)**()|
Convert radians to degrees. | +| **[distinct_count](/openmldb_sql/Files/udfs_8h.md#function-distinct-count)**()|
Compute number of distinct values. | +| **[double](/openmldb_sql/Files/udfs_8h.md#function-double)**()|
Cast string expression to double. | +| **[drawdown](/openmldb_sql/Files/udfs_8h.md#function-drawdown)**()|
Compute drawdown of values. | +| **[earth_distance](/openmldb_sql/Files/udfs_8h.md#function-earth-distance)**()|
Returns the great circle distance between two points on the surface of the Earth. Km as return unit. add a minus (-) sign if heading west (W) or south (S). | +| **[entropy](/openmldb_sql/Files/udfs_8h.md#function-entropy)**()|
Calculate Shannon entropy of a column of values. Null values are skipped. | +| **[ew_avg](/openmldb_sql/Files/udfs_8h.md#function-ew-avg)**()|
Compute exponentially-weighted average of values. It's equivalent to pandas ewm(alpha={alpha}, adjust=True, ignore_na=True, com=None, span=None, halflife=None, min_periods=0) | +| **[exp](/openmldb_sql/Files/udfs_8h.md#function-exp)**()|
Return the value of e (the base of natural logarithms) raised to the power of expr. | +| **[farm_fingerprint](/openmldb_sql/Files/udfs_8h.md#function-farm-fingerprint)**()| | +| **[first_value](/openmldb_sql/Files/udfs_8h.md#function-first-value)**()|
Returns the value of expr from the latest row (last row) of the window frame. | +| **[float](/openmldb_sql/Files/udfs_8h.md#function-float)**()|
Cast string expression to float. | +| **[floor](/openmldb_sql/Files/udfs_8h.md#function-floor)**()|
Return the largest integer value not less than the expr. | +| **[get_json_object](/openmldb_sql/Files/udfs_8h.md#function-get-json-object)**()|
Extracts a JSON object from [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901)| +| **[hash64](/openmldb_sql/Files/udfs_8h.md#function-hash64)**()|
Returns a hash value of the arguments. It is not a cryptographic hash function and should not be used as such. | +| **[hex](/openmldb_sql/Files/udfs_8h.md#function-hex)**()|
Convert integer to hexadecimal. | +| **[hour](/openmldb_sql/Files/udfs_8h.md#function-hour)**()|
Return the hour for a timestamp. | +| **[identity](/openmldb_sql/Files/udfs_8h.md#function-identity)**()|
Return value. | +| **[if_null](/openmldb_sql/Files/udfs_8h.md#function-if-null)**()|
If input is not null, return input value; else return default value. | +| **[ifnull](/openmldb_sql/Files/udfs_8h.md#function-ifnull)**()| | +| **[ilike_match](/openmldb_sql/Files/udfs_8h.md#function-ilike-match)**()|
pattern match same as ILIKE predicate | +| **[inc](/openmldb_sql/Files/udfs_8h.md#function-inc)**()|
Return expression + 1. | +| **[int](/openmldb_sql/Files/udfs_8h.md#function-int)**()| | +| **[int16](/openmldb_sql/Files/udfs_8h.md#function-int16)**()|
Cast string expression to int16. | +| **[int32](/openmldb_sql/Files/udfs_8h.md#function-int32)**()|
Cast string expression to int32. | +| **[int64](/openmldb_sql/Files/udfs_8h.md#function-int64)**()|
Cast string expression to int64. | +| **[is_null](/openmldb_sql/Files/udfs_8h.md#function-is-null)**()|
Check if input value is null, return bool. | +| **[isnull](/openmldb_sql/Files/udfs_8h.md#function-isnull)**()| | +| **[join](/openmldb_sql/Files/udfs_8h.md#function-join)**()|
For each string value from specified column of window, join by delimeter. Null values are skipped. | +| **[json_array_length](/openmldb_sql/Files/udfs_8h.md#function-json-array-length)**()|
Returns the number of elements in the outermost JSON array. | +| **[lag](/openmldb_sql/Files/udfs_8h.md#function-lag)**()|
Returns value evaluated at the row that is offset rows before the current row within the partition. Offset is evaluated with respect to the current row. | +| **[last_day](/openmldb_sql/Files/udfs_8h.md#function-last-day)**()|
Return the last day of the month to which the date belongs to. | +| **[lcase](/openmldb_sql/Files/udfs_8h.md#function-lcase)**()|
Convert all the characters to lowercase. Note that characters with values > 127 are simply returned. | +| **[like_match](/openmldb_sql/Files/udfs_8h.md#function-like-match)**()|
pattern match same as LIKE predicate | +| **[list_except_by_key](/openmldb_sql/Files/udfs_8h.md#function-list-except-by-key)**()|
Return list of elements in list1 but keys not in except_str. | +| **[list_except_by_value](/openmldb_sql/Files/udfs_8h.md#function-list-except-by-value)**()|
Return list of elements in list1 but values not in except_str. | +| **[ln](/openmldb_sql/Files/udfs_8h.md#function-ln)**()|
Return the natural logarithm of expr. | +| **[log](/openmldb_sql/Files/udfs_8h.md#function-log)**()|
log(base, expr) If called with one parameter, this function returns the natural logarithm of expr. If called with two parameters, this function returns the logarithm of expr to the base. | +| **[log10](/openmldb_sql/Files/udfs_8h.md#function-log10)**()|
Return the base-10 logarithm of expr. | +| **[log2](/openmldb_sql/Files/udfs_8h.md#function-log2)**()|
Return the base-2 logarithm of expr. | +| **[lower](/openmldb_sql/Files/udfs_8h.md#function-lower)**()| | +| **[make_tuple](/openmldb_sql/Files/udfs_8h.md#function-make-tuple)**()| | +| **[max](/openmldb_sql/Files/udfs_8h.md#function-max)**()|
Compute maximum of values. | +| **[max_cate](/openmldb_sql/Files/udfs_8h.md#function-max-cate)**()|
Compute maximum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[max_cate_where](/openmldb_sql/Files/udfs_8h.md#function-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[max_where](/openmldb_sql/Files/udfs_8h.md#function-max-where)**()|
Compute maximum of values match specified condition. | +| **[maximum](/openmldb_sql/Files/udfs_8h.md#function-maximum)**()|
Compute maximum of two arguments. | +| **[median](/openmldb_sql/Files/udfs_8h.md#function-median)**()|
Compute the median of values. | +| **[min](/openmldb_sql/Files/udfs_8h.md#function-min)**()|
Compute minimum of values. | +| **[min_cate](/openmldb_sql/Files/udfs_8h.md#function-min-cate)**()|
Compute minimum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[min_cate_where](/openmldb_sql/Files/udfs_8h.md#function-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[min_where](/openmldb_sql/Files/udfs_8h.md#function-min-where)**()|
Compute minimum of values match specified condition. | +| **[minimum](/openmldb_sql/Files/udfs_8h.md#function-minimum)**()|
Compute minimum of two arguments. | +| **[minute](/openmldb_sql/Files/udfs_8h.md#function-minute)**()|
Return the minute for a timestamp. | +| **[month](/openmldb_sql/Files/udfs_8h.md#function-month)**()|
Return the month part of a timestamp or date. | +| **[nth_value_where](/openmldb_sql/Files/udfs_8h.md#function-nth-value-where)**()|
Returns the value of expr from the idx th row matches the condition. | +| **[nvl](/openmldb_sql/Files/udfs_8h.md#function-nvl)**()| | +| **[nvl2](/openmldb_sql/Files/udfs_8h.md#function-nvl2)**()|
nvl2(expr1, expr2, expr3) - Returns expr2 if expr1 is not null, or expr3 otherwise. | +| **[pmod](/openmldb_sql/Files/udfs_8h.md#function-pmod)**()|
Compute pmod of two arguments. If any param is NULL, output NULL. If divisor is 0, output NULL. | +| **[pow](/openmldb_sql/Files/udfs_8h.md#function-pow)**()|
Return the value of expr1 to the power of expr2. | +| **[power](/openmldb_sql/Files/udfs_8h.md#function-power)**()| | +| **[radians](/openmldb_sql/Files/udfs_8h.md#function-radians)**()|
Returns the argument X, converted from degrees to radians. (Note that π radians equals 180 degrees.) | +| **[regexp_like](/openmldb_sql/Files/udfs_8h.md#function-regexp-like)**()|
pattern match same as RLIKE predicate (based on RE2) | +| **[replace](/openmldb_sql/Files/udfs_8h.md#function-replace)**()|
replace(str, search[, replace]) - Replaces all occurrences of `search` with `replace`| +| **[reverse](/openmldb_sql/Files/udfs_8h.md#function-reverse)**()|
Returns the reversed given string. | +| **[round](/openmldb_sql/Files/udfs_8h.md#function-round)**()|
Returns expr rounded to d decimal places using HALF_UP rounding mode. | +| **[second](/openmldb_sql/Files/udfs_8h.md#function-second)**()|
Return the second for a timestamp. | +| **[sin](/openmldb_sql/Files/udfs_8h.md#function-sin)**()|
Return the sine of expr. | +| **[size](/openmldb_sql/Files/udfs_8h.md#function-size)**()|
Get the size of a List (e.g., result of split) | +| **[smallint](/openmldb_sql/Files/udfs_8h.md#function-smallint)**()| | +| **[split](/openmldb_sql/Files/udfs_8h.md#function-split)**()|
Split string to list by delimeter. Null values are skipped. | +| **[split_array](/openmldb_sql/Files/udfs_8h.md#function-split-array)**()|
Split string to array of string by delimeter. | +| **[split_by_key](/openmldb_sql/Files/udfs_8h.md#function-split-by-key)**()|
Split string by delimeter and split each segment as kv pair, then add each key to output list. Null or illegal segments are skipped. | +| **[split_by_value](/openmldb_sql/Files/udfs_8h.md#function-split-by-value)**()|
Split string by delimeter and split each segment as kv pair, then add each value to output list. Null or illegal segments are skipped. | +| **[sqrt](/openmldb_sql/Files/udfs_8h.md#function-sqrt)**()|
Return square root of expr. | +| **[std](/openmldb_sql/Files/udfs_8h.md#function-std)**()| | +| **[stddev](/openmldb_sql/Files/udfs_8h.md#function-stddev)**()|
Compute sample standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / (n-1) )`| +| **[stddev_pop](/openmldb_sql/Files/udfs_8h.md#function-stddev-pop)**()|
Compute population standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) / n )`| +| **[stddev_samp](/openmldb_sql/Files/udfs_8h.md#function-stddev-samp)**()| | +| **[strcmp](/openmldb_sql/Files/udfs_8h.md#function-strcmp)**()|
Returns 0 if the strings are the same, -1 if the first argument is smaller than the second according to the current sort order, and 1 otherwise. | +| **[string](/openmldb_sql/Files/udfs_8h.md#function-string)**()|
Return string converted from timestamp expression. | +| **[substr](/openmldb_sql/Files/udfs_8h.md#function-substr)**()| | +| **[substring](/openmldb_sql/Files/udfs_8h.md#function-substring)**()|
Return a substring `len` characters long from string str, starting at position `pos`. Alias function: `substr`| +| **[sum](/openmldb_sql/Files/udfs_8h.md#function-sum)**()|
Compute sum of values. | +| **[sum_cate](/openmldb_sql/Files/udfs_8h.md#function-sum-cate)**()|
Compute sum of values grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[sum_cate_where](/openmldb_sql/Files/udfs_8h.md#function-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key and output string. Each group is represented as 'K:V' and separated by comma in outputs and are sorted by key in ascend order. | +| **[sum_where](/openmldb_sql/Files/udfs_8h.md#function-sum-where)**()|
Compute sum of values match specified condition. | +| **[tan](/openmldb_sql/Files/udfs_8h.md#function-tan)**()|
Return the tangent of expr. | +| **[timestamp](/openmldb_sql/Files/udfs_8h.md#function-timestamp)**()|
Cast int64, date or string expression to timestamp. | +| **[top](/openmldb_sql/Files/udfs_8h.md#function-top)**()|
Compute top k of values and output string separated by comma. The outputs are sorted in desc order. | +| **[top1_ratio](/openmldb_sql/Files/udfs_8h.md#function-top1-ratio)**()|
Compute the top1 occurring value's ratio. | +| **[top_n_key_avg_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_count_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_max_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_min_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_key_ratio_cate](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | +| **[top_n_key_sum_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-key-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N category keys in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_avg_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-avg-cate-where)**()|
Compute average of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_count_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-count-cate-where)**()|
Compute count of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_max_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-max-cate-where)**()|
Compute maximum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_min_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-min-cate-where)**()|
Compute minimum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[top_n_value_ratio_cate](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-ratio-cate)**()|
Ratios (cond match cnt / total cnt) for groups. | +| **[top_n_value_sum_cate_where](/openmldb_sql/Files/udfs_8h.md#function-top-n-value-sum-cate-where)**()|
Compute sum of values matching specified condition grouped by category key. Output string for top N aggregate values in descend order. Each group is represented as 'K:V' and separated by comma(,). Empty string returned if no rows selected. | +| **[topn_frequency](/openmldb_sql/Files/udfs_8h.md#function-topn-frequency)**()|
Return the topN keys sorted by their frequency. | +| **[truncate](/openmldb_sql/Files/udfs_8h.md#function-truncate)**()|
Return the nearest integer that is not greater in magnitude than the expr. | +| **[ucase](/openmldb_sql/Files/udfs_8h.md#function-ucase)**()|
Convert all the characters to uppercase. Note that characters values > 127 are simply returned. | +| **[unhex](/openmldb_sql/Files/udfs_8h.md#function-unhex)**()|
Convert hexadecimal to binary string. | +| **[unix_timestamp](/openmldb_sql/Files/udfs_8h.md#function-unix-timestamp)**()|
Cast date or string expression to unix_timestamp. If empty string or NULL is provided, return current timestamp. | +| **[upper](/openmldb_sql/Files/udfs_8h.md#function-upper)**()| | +| **[var_pop](/openmldb_sql/Files/udfs_8h.md#function-var-pop)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / n`| +| **[var_samp](/openmldb_sql/Files/udfs_8h.md#function-var-samp)**()|
Compute population variance of values, i.e., `sum((x_i - avg)^2) / (n-1)`| +| **[variance](/openmldb_sql/Files/udfs_8h.md#function-variance)**()| | +| **[week](/openmldb_sql/Files/udfs_8h.md#function-week)**()| | +| **[weekofyear](/openmldb_sql/Files/udfs_8h.md#function-weekofyear)**()|
Return the week of year for a timestamp or date. | +| **[window_split](/openmldb_sql/Files/udfs_8h.md#function-window-split)**()|
For each string value from specified column of window, split by delimeter and add segment to output list. Null values are skipped. | +| **[window_split_by_key](/openmldb_sql/Files/udfs_8h.md#function-window-split-by-key)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each key to output list. Null and illegal segments are skipped. | +| **[window_split_by_value](/openmldb_sql/Files/udfs_8h.md#function-window-split-by-value)**()|
For each string value from specified column of window, split by delimeter and then split each segment as kv pair, then add each value to output list. Null and illegal segments are skipped. | +| **[year](/openmldb_sql/Files/udfs_8h.md#function-year)**()|
Return the year part of a timestamp or date. | ## Functions Documentation @@ -501,13 +501,13 @@ Compute average of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -541,13 +541,13 @@ Compute average of values grouped by category key and output string. Each group Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -586,13 +586,13 @@ Compute average of values matching specified condition grouped by category key a Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -634,13 +634,13 @@ Compute average of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -884,7 +884,7 @@ SELECT COS(0); -* The value returned by [cos()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-cos) is always in the range: -1 to 1. +* The value returned by [cos()](/openmldb_sql/Files/udfs_8h.md#function-cos) is always in the range: -1 to 1. **Supported Types**: @@ -946,13 +946,13 @@ Compute number of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -987,13 +987,13 @@ Compute count of values grouped by category key and output string. Each group is Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -1032,13 +1032,13 @@ Compute count of values matching specified condition grouped by category key and Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -1080,13 +1080,13 @@ Compute number of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -1178,7 +1178,12 @@ Supported date string style: * yyyy-mm-dd * yyyymmdd -* yyyy-mm-dd hh:mm:ss +* yyyy-mm-dd HH:MM:SS +* yyyy-mm-ddTHH:MM:SS.fff+HH:MM (RFC3399 format) + +Dates from string are transformed into the same time zone (which is currently always UTC+8) before differentiation, dates from date type by default is at UTC+8, you may see a +1/-1 difference if the two date string have different time zones. + +Hint: since openmldb date type limits range from year 1900, to datadiff from/to a date before 1900, pass it as string. Example: @@ -1225,7 +1230,7 @@ Return the day of the month for a timestamp or date. 0.1.0 -Note: This function equals the `[day()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-day)` function. +Note: This function equals the `[day()](/openmldb_sql/Files/udfs_8h.md#function-day)` function. Example: @@ -1259,7 +1264,7 @@ Return the day of week for a timestamp or date. 0.4.0 -Note: This function equals the `[week()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-week)` function. +Note: This function equals the `[week()](/openmldb_sql/Files/udfs_8h.md#function-week)` function. Example: @@ -1369,13 +1374,13 @@ Compute number of distinct values. Example: -| value | +| value | | -------- | -| 0 | -| 0 | -| 2 | -| 2 | -| 4 | +| 0 | +| 0 | +| 2 | +| 2 | +| 4 | ```sql @@ -1445,14 +1450,14 @@ It requires that all values are non-negative. Negative values will be ignored. Example: -| value | +| value | | -------- | -| 1 | -| 8 | -| 5 | -| 2 | -| 10 | -| 4 | +| 1 | +| 8 | +| 5 | +| 2 | +| 10 | +| 4 | ```sql @@ -1563,13 +1568,13 @@ It requires that values are ordered so that it can only be used with WINDOW (PAR Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -1647,11 +1652,11 @@ window w as (partition by gp order by ts rows between 3 preceding and current ro ``` -| id | gp | ts | agg | +| id | gp | ts | agg | | -------- | -------- | -------- | -------- | -| 1 | 100 | 98 | 98 | -| 2 | 100 | 99 | 99 | -| 3 | 100 | 100 | 100 | +| 1 | 100 | 98 | 98 | +| 2 | 100 | 99 | 99 | +| 3 | 100 | 100 | 100 | @@ -2246,21 +2251,21 @@ Returns value evaluated at the row that is offset rows before the current row wi * **offset** The number of rows forwarded from the current row, must not negative -Note: This function equals the `[at()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-at)` function. +Note: This function equals the `[at()](/openmldb_sql/Files/udfs_8h.md#function-at)` function. -The offset in window is `nth_value()`, not `[lag()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-lag)/at()`. The old `[at()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-at)`(version < 0.5.0) is start from the last row of window(may not be the current row), it's more like `nth_value()` +The offset in window is `nth_value()`, not `[lag()](/openmldb_sql/Files/udfs_8h.md#function-lag)/at()`. The old `[at()](/openmldb_sql/Files/udfs_8h.md#function-at)`(version < 0.5.0) is start from the last row of window(may not be the current row), it's more like `nth_value()` Example: -| c1 | c2 | +| c1 | c2 | | -------- | -------- | -| 0 | 1 | -| 1 | 1 | -| 2 | 2 | -| 3 | 2 | -| 4 | 2 | +| 0 | 1 | +| 1 | 1 | +| 2 | 2 | +| 3 | 2 | +| 4 | 2 | ```sql @@ -2648,13 +2653,13 @@ Compute maximum of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2691,13 +2696,13 @@ Compute maximum of values grouped by category key and output string. Each group Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -2736,13 +2741,13 @@ Compute maximum of values matching specified condition grouped by category key a Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -2784,13 +2789,13 @@ Compute maximum of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2856,12 +2861,12 @@ Compute the median of values. Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2898,13 +2903,13 @@ Compute minimum of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -2941,13 +2946,13 @@ Compute minimum of values grouped by category key and output string. Each group Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -2986,14 +2991,14 @@ Compute minimum of values matching specified condition grouped by category key a Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 1 | true | y | -| 4 | true | x | -| 3 | true | y | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 1 | true | y | +| 4 | true | x | +| 3 | true | y | ```sql @@ -3035,13 +3040,13 @@ Compute minimum of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -3171,12 +3176,12 @@ select col1, cond, gp, nth_value_where(col1, 2, cond) over (partition by gp orde ``` -| col1 | cond | gp | agg | +| col1 | cond | gp | agg | | -------- | -------- | -------- | -------- | -| 1 | true | 100 | NULL | -| 2 | false | 100 | NULL | -| 3 | NULL | 100 | NULL | -| 4 | true | 100 | 4 | +| 1 | true | 100 | NULL | +| 2 | false | 100 | NULL | +| 3 | NULL | 100 | NULL | +| 4 | true | 100 | 4 | @@ -3563,7 +3568,7 @@ SELECT SIN(0); -* The value returned by [sin()](/openmldb_sql/functions_and_operators/Files/udfs_8h.md#function-sin) is always in the range: -1 to 1. +* The value returned by [sin()](/openmldb_sql/Files/udfs_8h.md#function-sin) is always in the range: -1 to 1. **Supported Types**: @@ -3805,12 +3810,12 @@ Alias function: `std`, `stddev_samp` Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -3847,12 +3852,12 @@ Compute population standard deviation of values, i.e., `sqrt( sum((x_i - avg)^2) Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -4008,13 +4013,13 @@ Compute sum of values. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -4048,13 +4053,13 @@ Compute sum of values grouped by category key and output string. Each group is r Example: -| value | catagory | +| value | catagory | | -------- | -------- | -| 0 | x | -| 1 | y | -| 2 | x | -| 3 | y | -| 4 | x | +| 0 | x | +| 1 | y | +| 2 | x | +| 3 | y | +| 4 | x | ```sql @@ -4093,13 +4098,13 @@ Compute sum of values matching specified condition grouped by category key and o Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | ```sql @@ -4141,13 +4146,13 @@ Compute sum of values match specified condition. Example: -| value | +| value | | -------- | -| 0 | -| 1 | -| 2 | -| 3 | -| 4 | +| 0 | +| 1 | +| 2 | +| 3 | +| 4 | ```sql @@ -4257,13 +4262,13 @@ Compute top k of values and output string separated by comma. The outputs are so Example: -| value | +| value | | -------- | -| 1 | -| 2 | -| 3 | -| 4 | -| 4 | +| 1 | +| 2 | +| 3 | +| 4 | +| 4 | ```sql @@ -4314,11 +4319,11 @@ SELECT key, top1_ratio(key) over () as ratio FROM t1; ``` -| key | ratio | +| key | ratio | | -------- | -------- | -| 1 | 1.0 | -| 2 | 0.5 | -| NULL | 0.5 | +| 1 | 1.0 | +| 2 | 0.5 | +| NULL | 0.5 | @@ -4355,15 +4360,15 @@ Compute average of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4415,15 +4420,15 @@ Compute count of values matching specified condition grouped by category key. Ou Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4475,15 +4480,15 @@ Compute maximum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4535,15 +4540,15 @@ Compute minimum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4597,15 +4602,15 @@ For each group, ratio value is `value` expr count matches condtion divide total Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 2 | true | x | -| 4 | true | x | -| 1 | true | y | -| 3 | false | y | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 2 | true | x | +| 4 | true | x | +| 1 | true | y | +| 3 | false | y | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4656,15 +4661,15 @@ Compute sum of values matching specified condition grouped by category key. Outp Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4716,15 +4721,15 @@ Compute average of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | false | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | false | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4776,15 +4781,15 @@ Compute count of values matching specified condition grouped by category key. Ou Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | true | x | -| 3 | false | y | -| 4 | true | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | true | x | +| 3 | false | y | +| 4 | true | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4836,15 +4841,15 @@ Compute maximum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | false | y | -| 2 | false | x | -| 3 | true | y | -| 4 | true | x | -| 5 | true | z | -| 6 | false | z | +| 0 | true | x | +| 1 | false | y | +| 2 | false | x | +| 3 | true | y | +| 4 | true | x | +| 5 | true | z | +| 6 | false | z | ```sql @@ -4896,15 +4901,15 @@ Compute minimum of values matching specified condition grouped by category key. Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | true | x | -| 3 | true | y | -| 4 | false | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | true | x | +| 3 | true | y | +| 4 | false | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -4958,15 +4963,15 @@ For each group, ratio value is `value` expr count matches condtion divide total Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 2 | true | x | -| 4 | true | x | -| 1 | true | y | -| 3 | false | y | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 2 | true | x | +| 4 | true | x | +| 1 | true | y | +| 3 | false | y | +| 5 | true | z | +| 6 | true | z | ```sql @@ -5017,15 +5022,15 @@ Compute sum of values matching specified condition grouped by category key. Outp Example: -| value | condition | catagory | +| value | condition | catagory | | -------- | -------- | -------- | -| 0 | true | x | -| 1 | true | y | -| 2 | false | x | -| 3 | false | y | -| 4 | true | x | -| 5 | true | z | -| 6 | true | z | +| 0 | true | x | +| 1 | true | y | +| 2 | false | x | +| 3 | false | y | +| 4 | true | x | +| 5 | true | z | +| 6 | true | z | ```sql @@ -5240,11 +5245,11 @@ Compute population variance of values, i.e., `sum((x_i - avg)^2) / n` Example: -| value | +| value | | -------- | -| 0 | -| 3 | -| 6 | +| 0 | +| 3 | +| 6 | ```sql @@ -5281,11 +5286,11 @@ Compute population variance of values, i.e., `sum((x_i - avg)^2) / (n-1)` Example: -| value | +| value | | -------- | -| 0 | -| 3 | -| 6 | +| 0 | +| 3 | +| 6 | ```sql diff --git a/docs/zh/quickstart/beginner_must_read.md b/docs/zh/quickstart/beginner_must_read.md index 37f1b57ae8e..117ad6fedb7 100644 --- a/docs/zh/quickstart/beginner_must_read.md +++ b/docs/zh/quickstart/beginner_must_read.md @@ -1,6 +1,20 @@ # 上手必读 -由于OpenMLDB是分布式系统,多种模式,客户端丰富,初次使用可能会有很多疑问,或者遇到一些运行、使用问题,本文从新手使用的角度,讲解如何进行诊断调试,需求帮助时如何提供有效信息给技术人员等等。 +由于OpenMLDB是分布式系统,多种模式,客户端丰富,初次使用可能会有很多疑问,或者遇到一些运行、使用问题,本文从新手使用的角度,讲解如何进行诊断调试,需要帮助时如何提供有效信息给技术人员等等。 + +## 错误诊断 + +在使用OpenMLDB的过程中,除了SQL语法错误,其他错误信息可能不够直观,但很可能与集群状态有关。所以,错误诊断需要**先确认集群状态**。在发现错误时,请先使用诊断工具的一键诊断功能。一键诊断可以输出全面直观的诊断报告,如果不能使用此工具,可以手动执行`SHOW COMPONENTS;`和`SHOW TABLE STATUS LIKE '%';`提供部分信息。 + +报告将展示集群的组件、在线表等状态,也会提示用户如何修复,请按照报告内容进行操作,详情见[一键inspect](../maintain/diagnose.md#一键inspect)。 + +``` +openmldb_tool inspect [-c=0.0.0.0:2181/openmldb] +``` + +需要注意,由于离线存储只会在执行离线job时被读取,而离线job也不是一个持续的状态,所以,一键诊断只能展示TaskManager组件状态,不会诊断离线存储,也无法诊断离线job的执行错误,离线job诊断见[离线SQL执行](#离线)。 + +如果诊断报告认为集群健康,但仍然无法解决问题,请提供错误和诊断报告给我们。 ## 创建OpenMLDB与连接 @@ -8,7 +22,7 @@ docker创建OpenMLDB见[快速上手](./openmldb_quickstart.md),请注意文档中有两个版本,单机版和集群版。请清楚自己要创建哪个版本,不要混合使用。 -启动成功的标准是可以使用CLI连接上OpenMLDB服务端(即使用`/work/openmldb/bin/openmldb`连接OpenMLDB,单机或集群均可以通过CLI连接),并且执行`show components;`可以看到OpenMLDB服务端组件的运行情况。 +启动成功的标准是可以使用CLI连接上OpenMLDB服务端(即使用`/work/openmldb/bin/openmldb`连接OpenMLDB,单机或集群均可以通过CLI连接),并且执行`show components;`可以看到OpenMLDB服务端组件的运行情况。推荐使用[诊断工具](../maintain/diagnose.md),执行status和inspect,可以得到更可靠的诊断结果。 如果CLI无法连接OpenMLDB,请先确认进程是否运行正常,可以通过`ps f|grep bin/openmldb`确认nameserver和tabletserver进程,集群版还需要通过`ps f | grep zoo.cfg`来确认zk服务,`ps f | grep TaskManagerServer`来确认taskmanager进程。 @@ -18,6 +32,20 @@ docker创建OpenMLDB见[快速上手](./openmldb_quickstart.md),请注意文 如果我们还需要OpenMLDB服务端的配置和日志,可以使用诊断工具获取,见[下文](#提供配置与日志获得技术支持)。 ``` +### 运维 + +集群各组件进程启动后,在使用过程中可能遇到各种变化,比如服务进程意外退出,需要重启服务进程,或者需要扩容服务进程。 + +如果你需要保留已有的在线表,**不要主动地kill全部Tablet再重启**,保证Tablet只有单台在上下线。`stop-all.sh`和`start-all.sh`脚本是给快速重建集群用的,可能会导致在线表数据恢复失败,**不保证能修复**。 + +当你发现进程变化或者主动操作其变化后,需要使用诊断工具进行诊断,确认集群状态是否正常: +```bash +openmldb_tool inspect # 主要命令 +openmldb_tool status --diff hosts # 可检查TaskManager等是否掉线,当然,你也可以手动判断 +``` + +如果诊断出server offline,或是TaskManager等掉线,需要先启动回来。如果启动失败,请查看对应日志,提供错误信息。如果诊断结果提示需要recoverdata,请参考[OpenMLDB运维工具](../maintain/openmldb_ops.md)执行recoverdata。如果recoverdata脚本提示recover失败,或recover成功后再次inpsect的结果仍然不正常,请提供日志给我们。 + ## 源数据 ### LOAD DATA @@ -42,15 +70,51 @@ docker创建OpenMLDB见[快速上手](./openmldb_quickstart.md),请注意文 csv文件格式有诸多不便,更推荐使用parquet格式,需要OpenMLDB集群版并启动taskmanager组件。 ``` -## SQL限制 +## OpenMLDB SQL 开发和调试 OpenMLDB并不完全兼容标准SQL。所以,部分SQL执行会得不到预期结果。如果发现SQL执行不符合预期,请先查看下SQL是否满足[功能边界](./function_boundary.md)。 -## SQL执行 +为了方便使用 OpenMLDB SQL 进行开发、调试、验证,我们强烈推荐使用社区工具 [OpenMLDB SQL Emulator](https://github.com/vagetablechicken/OpenMLDBSQLEmulator) 来进行 SQL 模拟开发,可以节省大量的部署、编译、索引构建、任务运行等待时间,详见该项目 README https://github.com/vagetablechicken/OpenMLDBSQLEmulator + +### OpenMLDB SQL语法指南 + +基于 OpenMLDB SQL 的特征计算,一般比较常使用`WINDOW`(包括`WINDOW UNION`),`LAST JOIN` 等子句来完成计算逻辑,它们能保证在任何模式下使用。可以跟随教程"基于 SQL 的特征开发"[(上)](../tutorial/tutorial_sql_1.md)[(下)](../tutorial/tutorial_sql_2.md)进行学习。 + +如果使用`WHERE`,`WITH`,`HAVING`等子句,需要注意限制条件。在每个子句的详细文档中都有具体的说明,比如[`HAVING`子句](../openmldb_sql/dql/HAVING_CLAUSE.md)在在线请求模式中不支持。翻阅OpenMLDB SQL的DQL目录,或使用搜索功能,可以快速找到子句的详细文档。 + +在不熟悉OpenMLDB SQL的情况下,我们建议从子句开始编写SQL,确保每个子句都能通过,再逐步组合成完整的SQL。 + +推荐使用[OpenMLDB SQL Emulator](https://github.com/vagetablechicken/OpenMLDBSQLEmulator)进行SQL探索和验证,SQL验证完成后再去真实集群进行上线,可以避免浪费大量时间在索引构建、数据导入、任务等待等过程上。 Emulator 可以不依赖真实OpenMLDB集群,在一个交互式虚拟环境中,快速创建表、校验SQL、导出当前环境等等,详情参考该项目的 README 。使用 Emulator 不需要操作集群,也就不需要测试后清理集群,还可通过少量的数据进行SQL运行测试,比较适合SQL探索时期。 -OpenMLDB所有命令均为SQL,如果SQL执行失败或交互有问题(不知道命令是否执行成功),请先确认SQL书写是否有误,命令并未执行,还是命令进入了执行阶段。 +### OpenMLDB SQL 语法错误提示 -例如,下面提示Syntax error的是SQL书写有误,请参考[sql reference](../../openmldb_sql/)纠正错误。 +当发现SQL编译报错时,需要查看错误信息。例如`Syntax error: Expected XXX but got keyword YYY`错误,它说明SQL不符合语法,通常是某些关键字写错了位置,或并没有这种写法。详情需要查询错误的子句文档,可注意子句的`Syntax`章节,它详细说明了每个部分的组成,请检查SQL是否符合要求。 + +比如,[`WINDOW`子句](../openmldb_sql/dql/WINDOW_CLAUSE.md#syntax)中`WindowFrameClause (WindowAttribute)*`部分,我们再拆解它就是`WindowFrameUnits WindowFrameBounds [WindowFrameMaxSize] (WindowAttribute)*`。那么,`WindowFrameUnits WindowFrameBounds MAXSIZE 10 EXCLUDE CURRENT_TIME`就是符合语法的,`WindowFrameUnits WindowFrameBounds EXCLUDE CURRENT_TIME MAXSIZE 10`就是不符合语法的,不能把`WindowFrameMaxSize`放到`WindowFrameClause`外面。 + +### OpenMLDB SQL 计算正确性调试 + +SQL编译通过以后,可以基于数据进行计算。如果计算结果不符合预期,请逐步检查: +- SQL无论是一列还是多列计算结果不符合预期,建议都请选择**其中一列**进行调试。 +- 如果你的表数据较多,建议使用小数据量(几行,几十行的量级)来测试,也可以使用OpenMLDB SQL Emulator的[运行toydb](https://github.com/vagetablechicken/OpenMLDBSQLEmulator#run-in-toydb)功能,构造case进行测试。 +- 该列是不是表示了自己想表达的意思,是否使用了不符合预期的函数,或者函数参数错误。 +- 该列如果是窗口聚合的结果,是不是WINDOW定义错误,导致窗口范围不对。参考[推断窗口](../openmldb_sql/dql/WINDOW_CLAUSE.md#如何推断窗口是什么样的)进行检查,使用小数据进行验证测试。 + +如果你仍然无法解决问题,可以提供 OpenMLDB SQL Emulator 的 yaml case 。如果在集群中进行的测试,请[提供复现脚本](#提供复现脚本)。 + +### 在线请求模式测试 + +SQL上线,等价于`DEPLOY `成功。但`DEPLOY`操作是一个很“重”的操作,SQL如果可以上线,将会创建或修改索引并复制数据到新索引。所以,在SQL探索期使用`DEPLOY`测试SQL是否能上线,是比较浪费资源的,尤其是某些SQL可能需要多次修改才能上线,多次的`DEPLOY`可能产生很多无用的索引。在探索期间,可能还会修改表Schema,又需要删除和再创建。这些操作都是只能手动处理,比较繁琐。 + +如果你对OpenMLDB SQL较熟悉,一些场景下可以用“在线预览模式”进行测试,但“在线预览模式”不等于“在线请求模式”,不能保证一定可以上线。如果你对索引较为熟悉,可以通过`EXPLAIN `来确认SQL是否可以上线,但`EXPLAIN`的检查较为严格,可能因为当前表没有匹配的索引,而判定SQL无法在“在线请求模式”中执行(因为无索引而无法保证实时性能,所以被拒绝)。 + +目前只有Java SDK可以使用[validateSQLInRequest](./sdk/java_sdk.md#sql-校验)方法来检验,使用上稍麻烦。我们推荐使用 OpenMLDB SQL Emulator 来测试。在 Emulator 中,通过简单语法创建表,再使用`valreq `可以判断是否能上线。 + +## OpenMLDB SQL 执行 + +OpenMLDB 所有命令均为 SQL,如果 SQL 执行失败或交互有问题(不知道命令是否执行成功),请先确认 SQL 书写是否有误,命令并未执行,还是命令进入了执行阶段。 + +例如,下面提示Syntax error的是SQL书写有误,请参考[SQL编写指南](#sql编写指南)纠正错误。 ``` 127.0.0.1:7527/db> create table t1(c1 int; Error: Syntax error: Expected ")" or "," but got ";" [at 1:23] @@ -65,32 +129,32 @@ create table t1(c1 int; 我们需要特别注意集群版的一些使用逻辑。 -### 集群版SQL执行 - -#### 离线 +### 集群版离线 SQL 执行注意事项 如果是集群离线命令,默认异步模式下,发送命令会得到job id的返回。可使用`show job `来查询job执行情况。 -离线job如果是异步SELECT(并不INTO保存结果),也不会将结果打印在客户端(同步SELECT将会打印结果)。可以通过`show joblog `来获得结果,结果中包含stdout和stderr两部分,stdout为查询结果,stderr为job运行日志。如果发现job failed或者其他状态,不符合你的预期,请仔细查看job运行日志。 +离线job如果是异步SELECT(并不INTO保存结果),也不会将结果打印在客户端,而同步SELECT将会打印结果到控制台。可以通过`show joblog `来获得结果,结果中包含stdout和stderr两部分,stdout为查询结果,stderr为job运行日志。如果发现job failed或者其他状态,不符合你的预期,请仔细查看job运行日志。 -```{note} -日志地址由taskmanager.properties的`job.log.path`配置,如果你改变了此配置项,需要到配置的目的地寻找日志。stdout日志默认在`/work/openmldb/taskmanager/bin/logs/job_x.log`,job运行日志默认在`/work/openmldb/taskmanager/bin/logs/job_x_error.log`(注意有error后缀), +离线job日志中可能有一定的干扰日志,用户可以使用`openmldb_tool inspect job --id x`进行日志的解析提取,帮助定位错误,更多信息请参考[诊断工具job检查](../maintain/diagnose.md#job-检查)。 -如果taskmanager是yarn模式,而不是local模式,`job_x_error.log`中的信息会较少,不会有job错误的详细信息。需要通过`job_x_error.log`中记录的yarn app id,去yarn系统中查询job的真正错误原因。 +如果taskmanager是yarn模式,而不是local模式,`job_x_error.log`中的信息会较少,只会打印异常。如果异常不直观,需要更早时间的执行日志,执行日志不在`job_x_error.log`中,需要通过`job_x_error.log`中记录的yarn app id,去yarn系统中查询yarn app的container的日志。yarn app container里,执行日志也保存在stderr中。 + +```{note} +如果你无法通过show joblog获得日志,或者想要直接拿到日志文件,可以直接在TaskManager机器上获取。日志地址由taskmanager.properties的`job.log.path`配置,如果你改变了此配置项,需要到配置的目录中寻找日志。stdout查询结果默认在`/work/openmldb/taskmanager/bin/logs/job_x.log`,stderr job运行日志默认在`/work/openmldb/taskmanager/bin/logs/job_x_error.log`(注意有error后缀)。 ``` -#### 在线 +### 集群版在线 SQL 执行注意事项 -集群版在线模式下,我们通常只推荐使用`DEPLOY`创建deployment,HTTP访问APIServer执行deployment做实时特征计算。在CLI或其他客户端中,直接在在线中进行SELECT查询,称为“在线预览”。在线预览有诸多限制,详情请参考[功能边界-集群版在线预览模式](../function_boundary.md#集群版在线预览模式),请不要执行不支持的SQL。 +集群版在线模式下,我们通常只推荐两种使用,`DEPLOY`创建deployment,执行deployment做实时特征计算(SDK请求deployment,或HTTP访问APIServer请求deployment)。在CLI或其他客户端中,可以直接在“在线”中进行SELECT查询,称为“在线预览”。在线预览有诸多限制,详情请参考[功能边界-集群版在线预览模式](./function_boundary.md#集群版在线预览模式),请不要执行不支持的SQL。 -### 提供复现脚本 +### 构造 OpenMLDB SQL 复现脚本 -如果你通过自主诊断,无法解决问题,请向我们提供复现脚本。一个完整的复现脚本,如下所示: +如果你的 SQL 执行不符合预期,通过自主诊断,无法解决问题,请向我们提供复现脚本。一个完整的复现脚本。仅涉及在线SQL计算或校验SQL,推荐使用[OpenMLDB SQL Emulator](https://github.com/vagetablechicken/OpenMLDBSQLEmulator#run-in-toydb) 构造可复现的 yaml case。如果涉及到数据导入等必须使用 OpenMLDB集群,请提供可复现脚本,其结构如下所示: ``` create database db; use db; --- create youer table +-- create your table create table xx (); -- offline or online @@ -118,7 +182,7 @@ set @@execute_mode=''; 请注意离线job默认为异步。如果你需要离线导入再查询,请设置为同步模式,详情见[离线命令配置详情](../openmldb_sql/ddl/SET_STATEMENT.md#离线命令配置详情)。否则导入还未完成就进行查询,是无意义的。 ``` -## 提供配置与日志,获得技术支持 +### 提供配置与日志,获得技术支持 如果你的SQL执行问题无法通过复现脚本复现,或者并非SQL执行问题而是集群管理问题,那么请提供客户端和服务端的配置与日志,以便我们调查。 @@ -135,3 +199,11 @@ openmldb_tool --env=onebox --dist_conf=standalone_dist.yml 如果是分布式的集群,需要配置ssh免密才能顺利使用诊断工具,参考文档[诊断工具](../maintain/diagnose.md)。 如果你的环境无法做到,请手动获取配置与日志。 + +## 性能统计 + +deployment耗时统计需要开启: +``` +SET GLOBAL deploy_stats = 'on'; +``` +开启后的Deployment执行都将被统计,之前的不会被统计,表中的数据不包含集群外部的网络耗时,仅统计deployment在server端从开始执行到结束的时间。 diff --git a/docs/zh/quickstart/function_boundary.md b/docs/zh/quickstart/function_boundary.md index 801d5d1b111..5d656f8eb75 100644 --- a/docs/zh/quickstart/function_boundary.md +++ b/docs/zh/quickstart/function_boundary.md @@ -147,7 +147,7 @@ OpenMLDB CLI 中在线模式下执行 SQL,均为在线预览模式。在线预 ### 离线模式与在线请求模式 -在[特征工程开发上线全流程](../tutorial/concepts/modes.md#11-特征工程开发上线全流程)中,主要使用离线模式和在线请求模式。 +在[特征工程开发上线全流程](./concepts/modes.md)中,主要使用离线模式和在线请求模式。 - 离线模式的批查询:离线特征生成 - 在线请求模式的请求查询:实时特征计算 @@ -164,4 +164,4 @@ OpenMLDB CLI 中在线模式下执行 SQL,均为在线预览模式。在线预 - CLI是交互模式,所以将结果直接打印。 - SDK中,返回的是一行一列的ResultSet,将整个查询结果作为一个字符串返回。所以,不建议SDK使用同步模式查询,并处理其结果。 -同步模式涉及超时问题,详情见[调整配置](../../openmldb_sql/ddl/SET_STATEMENT.md#离线命令配置详情)。 +同步模式涉及超时问题,详情见[调整配置](../openmldb_sql/ddl/SET_STATEMENT.md#离线命令配置详情)。 diff --git a/docs/zh/quickstart/openmldb_quickstart.md b/docs/zh/quickstart/openmldb_quickstart.md index 6a0191b09f1..c9a0dee18a8 100644 --- a/docs/zh/quickstart/openmldb_quickstart.md +++ b/docs/zh/quickstart/openmldb_quickstart.md @@ -19,7 +19,7 @@ OpenMLDB 的主要使用场景为作为机器学习的实时特征平台。其 在命令行执行以下命令拉取 OpenMLDB 镜像,并启动 Docker 容器: ```bash -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` ```{note} diff --git a/docs/zh/quickstart/sdk/java_sdk.md b/docs/zh/quickstart/sdk/java_sdk.md index 966d50db785..37a874e4521 100644 --- a/docs/zh/quickstart/sdk/java_sdk.md +++ b/docs/zh/quickstart/sdk/java_sdk.md @@ -12,12 +12,12 @@ Java SDK中,JDBC Statement的默认执行模式为在线,SqlClusterExecutor com.4paradigm.openmldb openmldb-jdbc - 0.8.3 + 0.8.4 com.4paradigm.openmldb openmldb-native - 0.8.3 + 0.8.4 ``` @@ -29,16 +29,16 @@ Java SDK中,JDBC Statement的默认执行模式为在线,SqlClusterExecutor com.4paradigm.openmldb openmldb-jdbc - 0.8.3 + 0.8.4 com.4paradigm.openmldb openmldb-native - 0.8.3-macos + 0.8.4-macos ``` -注意:由于 openmldb-native 中包含了 OpenMLDB 编译的 C++ 静态库,默认是 Linux 静态库,macOS 上需将上述 openmldb-native 的 version 改成 `0.8.3-macos`,openmldb-jdbc 的版本保持不变。 +注意:由于 openmldb-native 中包含了 OpenMLDB 编译的 C++ 静态库,默认是 Linux 静态库,macOS 上需将上述 openmldb-native 的 version 改成 `0.8.4-macos`,openmldb-jdbc 的版本保持不变。 openmldb-native 的 macOS 版本只支持 macOS 12,如需在 macOS 11 或 macOS 10.15上运行,需在相应 OS 上源码编译 openmldb-native 包,详细编译方法见[并发编译 Java SDK](https://openmldb.ai/docs/zh/main/deploy/compile.html#java-sdk)。使用自编译的 openmldb-native 包,推荐使用`mvn install`安装到本地仓库,然后在 pom 中引用本地仓库的 openmldb-native 包,不建议用`scope=system`的方式引用。 @@ -403,7 +403,7 @@ try { "(PARTITION BY %s.c1 ORDER BY %s.c7 ROWS_RANGE BETWEEN 2d PRECEDING AND CURRENT ROW);", table, table, table); // 上线一个Deployment - String deploySql = String.format("DEPLOY %s %s", deploymentName, selectSql); + String deploySql = String.format("DEPLOY %s OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') %s", deploymentName, selectSql); // set return null rs, don't check the returned value, it's false state.execute(deploySql); } catch (Exception e) { diff --git a/docs/zh/quickstart/sdk/rest_api.md b/docs/zh/quickstart/sdk/rest_api.md index 0526127cd29..0a225e444f6 100644 --- a/docs/zh/quickstart/sdk/rest_api.md +++ b/docs/zh/quickstart/sdk/rest_api.md @@ -5,6 +5,18 @@ - REST APIs 通过 APIServer 和 OpenMLDB 的服务进行交互,因此 APIServer 模块必须被正确部署才能有效使用。APISever 在安装部署时是可选模块,参照 [APIServer 部署文档](../../deploy/install_deploy.md#部署-apiserver)。 - 现阶段,APIServer 主要用来做功能测试使用,并不推荐用来测试性能,也不推荐在生产环境使用。APIServer 的默认部署目前并没有高可用机制,并且引入了额外的网络和编解码开销。生产环境推荐使用 Java SDK,功能覆盖最完善,并且在功能、性能上都经过了充分测试。 +## JSON Body + +与APIServer的交互中,请求体均为JSON格式,并支持一定的扩展格式。注意以下几点: + +- 传入超过整型或浮点数最大值的数值,将会解析失败,比如,double类型传入`1e1000`。 +- 非数值浮点数:在传入数据时,支持传入`NaN`、`Infinity`、`-Infinity`,与缩写`Inf`、`-Inf`(注意是unquoted的,并非字符串,也不支持其他变种写法)。在返回数据时,支持返回`NaN`、`Infinity`、`-Infinity`(不支持变种写法)。如果你需要将三者转换为null,可以配置 `write_nan_and_inf_null`。 +- 可以传入整型数字到浮点数,比如,`1`可被读取为double。 +- float浮点数可能有精度损失,比如,`0.3`读取后将不会严格等于`0.3`,而是`0.30000000000000004`。我们不拒绝精度损失,请从业务层面考虑是否需要对此进行处理。传入超过float max但不超过double max的值,在读取后将成为`Inf`。 +- `true/false`、`null`并不支持大写,只支持小写。 +- timestamp类型暂不支持传入年月日字符串,只支持传入数值,比如`1635247427000`。 +- date类型请传入**年月日字符串**,中间不要包含任何空格。 + ## 数据插入 请求地址:http://ip:port/dbs/{db_name}/tables/{table_name} @@ -55,7 +67,8 @@ curl http://127.0.0.1:8080/dbs/db/tables/trans -X PUT -d '{ ```JSON { "input": [["row0_value0", "row0_value1", "row0_value2"], ["row1_value0", "row1_value1", "row1_value2"], ...], - "need_schema": false + "need_schema": false, + "write_nan_and_inf_null": false } ``` @@ -73,6 +86,7 @@ curl http://127.0.0.1:8080/dbs/db/tables/trans -X PUT -d '{ - 可以支持多行,其结果与返回的 response 中的 data.data 字段的数组一一对应。 - need_schema 可以设置为 true, 返回就会有输出结果的 schema。可选参数,默认为 false。 +- write_nan_and_inf_null 可以设置为 true,可选参数,默认为false。如果设置为 true,当输出数据中有 NaN、Inf、-Inf 时,会将其转换为 null。 - input 为 array 格式/JSON 格式时候返回结果也是 array 格式/JSON 格式,一次请求的 input 只支持一种格式,请不要混合格式。 - JSON 格式的 input 数据可以有多余列。 @@ -131,7 +145,8 @@ curl http://127.0.0.1:8080/dbs/demo_db/deployments/demo_data_service -X POST -d' "input": { "schema": [], "data": [] - } + }, + "write_nan_and_inf_null": false } ``` diff --git a/docs/zh/reference/ip_tips.md b/docs/zh/reference/ip_tips.md index fad3d3e0944..848cc59c598 100644 --- a/docs/zh/reference/ip_tips.md +++ b/docs/zh/reference/ip_tips.md @@ -52,15 +52,15 @@ curl http:///dbs/foo -X POST -d'{"mode":"online", "sql":"show component - 暴露端口,也需要修改apiserver的endpoint改为`0.0.0.0`。这样可以使用127.0.0.1或是公网ip访问到 APIServer。 单机版: ``` - docker run -p 8080:8080 -it 4pdosc/openmldb:0.8.3 bash + docker run -p 8080:8080 -it 4pdosc/openmldb:0.8.4 bash ``` 集群版: ``` - docker run -p 9080:9080 -it 4pdosc/openmldb:0.8.3 bash + docker run -p 9080:9080 -it 4pdosc/openmldb:0.8.4 bash ``` - 使用host网络,可以不用修改endpoint配置。缺点是容易引起端口冲突。 ``` - docker run --network host -it 4pdosc/openmldb:0.8.3 bash + docker run --network host -it 4pdosc/openmldb:0.8.4 bash ``` 如果是跨主机访问容器 onebox 中的 APIServer,可以**任选一种**下面的方式: @@ -126,17 +126,17 @@ cd /work/openmldb/conf/ && ls | grep -v _ | xargs sed -i s/0.0.0.0//g && cd 单机版需要暴露三个组件(nameserver,tabletserver,APIServer)的端口: ``` -docker run -p 6527:6527 -p 9921:9921 -p 8080:8080 -it 4pdosc/openmldb:0.8.3 bash +docker run -p 6527:6527 -p 9921:9921 -p 8080:8080 -it 4pdosc/openmldb:0.8.4 bash ``` 集群版需要暴露zk端口与所有组件的端口: ``` -docker run -p 2181:2181 -p 7527:7527 -p 10921:10921 -p 10922:10922 -p 8080:8080 -p 9902:9902 -it 4pdosc/openmldb:0.8.3 bash +docker run -p 2181:2181 -p 7527:7527 -p 10921:10921 -p 10922:10922 -p 8080:8080 -p 9902:9902 -it 4pdosc/openmldb:0.8.4 bash ``` - 使用host网络,可以不用修改 endpoint 配置。如果有端口冲突,请修改 server 的端口配置。 ``` -docker run --network host -it 4pdosc/openmldb:0.8.3 bash +docker run --network host -it 4pdosc/openmldb:0.8.4 bash ``` 如果是跨主机使用 CLI/SDK 访问问容器onebox,只能通过`--network host`,并更改所有endpoint为公网IP,才能顺利访问。 diff --git a/docs/zh/tutorial/index.rst b/docs/zh/tutorial/index.rst index cce68996ded..7406fda41a9 100644 --- a/docs/zh/tutorial/index.rst +++ b/docs/zh/tutorial/index.rst @@ -9,7 +9,6 @@ data_import_guide tutorial_sql_1 tutorial_sql_2 - modes openmldbspark_distribution data_import data_export diff --git a/docs/zh/tutorial/standalone_use.md b/docs/zh/tutorial/standalone_use.md index df27c8307de..dc216c75c8f 100644 --- a/docs/zh/tutorial/standalone_use.md +++ b/docs/zh/tutorial/standalone_use.md @@ -11,7 +11,7 @@ 执行以下命令拉取 OpenMLDB 镜像,并启动 Docker 容器: ```bash -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` 成功启动容器以后,本教程中的后续命令默认均在容器内执行。 diff --git a/docs/zh/use_case/JD_recommendation.md b/docs/zh/use_case/JD_recommendation.md index d4035be912a..6cf586a397f 100644 --- a/docs/zh/use_case/JD_recommendation.md +++ b/docs/zh/use_case/JD_recommendation.md @@ -74,7 +74,7 @@ docker pull oneflowinc/oneflow-serving:nightly 由于 OpenMLDB 集群需要和其他组件网络通信,我们直接使用 host 网络。本例将在容器中使用已下载的脚本,所以请将数据脚本所在目录 `demodir` 映射为容器中的目录: ```bash -docker run -dit --name=openmldb --network=host -v $demodir:/work/oneflow_demo 4pdosc/openmldb:0.8.3 bash +docker run -dit --name=openmldb --network=host -v $demodir:/work/oneflow_demo 4pdosc/openmldb:0.8.4 bash docker exec -it openmldb bash ``` @@ -393,7 +393,7 @@ bash train_deepfm.sh $demodir/feature_preprocess/out ```sql -- OpenMLDB CLI USE JD_db; - DEPLOY demo ; + DEPLOY demo OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') ; ``` 也可以在 Docker 容器内直接运行部署脚本: diff --git a/docs/zh/use_case/talkingdata_demo.md b/docs/zh/use_case/talkingdata_demo.md index c47bc9a652a..4dc0c77ceef 100755 --- a/docs/zh/use_case/talkingdata_demo.md +++ b/docs/zh/use_case/talkingdata_demo.md @@ -16,7 +16,7 @@ **启动 Docker** ``` -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` #### 1.1.2 在本地运行 diff --git a/docs/zh/use_case/taxi_tour_duration_prediction.md b/docs/zh/use_case/taxi_tour_duration_prediction.md index faaff3bf922..245ce824784 100644 --- a/docs/zh/use_case/taxi_tour_duration_prediction.md +++ b/docs/zh/use_case/taxi_tour_duration_prediction.md @@ -15,7 +15,7 @@ 在命令行执行以下命令拉取 OpenMLDB 镜像,并启动 Docker 容器: ```bash -docker run -it 4pdosc/openmldb:0.8.3 bash +docker run -it 4pdosc/openmldb:0.8.4 bash ``` 该镜像预装了OpenMLDB,并预置了本案例所需要的所有脚本、三方库、开源工具以及训练数据。 @@ -151,7 +151,7 @@ w2 AS (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN --OpenMLDB CLI USE demo_db; SET @@execute_mode='online'; - DEPLOY demo SELECT trip_duration, passenger_count, + DEPLOY demo OPTIONS(RANGE_BIAS='inf', ROWS_BIAS='inf') SELECT trip_duration, passenger_count, sum(pickup_latitude) OVER w AS vendor_sum_pl, max(pickup_latitude) OVER w AS vendor_max_pl, min(pickup_latitude) OVER w AS vendor_min_pl, @@ -167,6 +167,10 @@ w2 AS (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN w2 AS (PARTITION BY passenger_count ORDER BY pickup_datetime ROWS_RANGE BETWEEN 1d PRECEDING AND CURRENT ROW); ``` +```{note} +此处DEPLOY包含BIAS OPTIONS,是因为导入在线存储的数据文件不会更新,对于当前时间来讲,可能会超过DEPLOY后的表索引的时间TTL,导致表淘汰掉这些数据。时间淘汰,只看每个索引的ts列和ttl,只要数据中该列的值<(当前时间-abs_ttl),在该索引上就会被淘汰,与其他因素无关,各个索引也互相不影响。如果你的数据不是实时产生的新timestamp,也需要考虑带上BIAS OPTIONS。 +``` + ### 步骤 7:导入在线数据 首先,请切换到**在线**执行模式。接着在在线模式下,导入样例数据 `/work/taxi-trip/data/taxi_tour_table_train_simple.csv` 作为在线数据,用于在线特征计算。 diff --git a/hybridse/examples/toydb/src/storage/table_iterator.cc b/hybridse/examples/toydb/src/storage/table_iterator.cc index 45561cd52a1..8ea4a3e0349 100644 --- a/hybridse/examples/toydb/src/storage/table_iterator.cc +++ b/hybridse/examples/toydb/src/storage/table_iterator.cc @@ -62,7 +62,7 @@ WindowTableIterator::WindowTableIterator(Segment*** segments, uint32_t seg_cnt, seg_idx_(0), pk_it_(), table_(table) { - GoToStart(); + SeekToFirst(); } WindowTableIterator::~WindowTableIterator() {} @@ -80,7 +80,7 @@ void WindowTableIterator::Seek(const std::string& key) { pk_it_->Seek(pk); } -void WindowTableIterator::SeekToFirst() {} +void WindowTableIterator::SeekToFirst() { GoToStart(); } std::unique_ptr WindowTableIterator::GetValue() { if (!pk_it_) diff --git a/hybridse/examples/toydb/src/tablet/tablet_catalog.cc b/hybridse/examples/toydb/src/tablet/tablet_catalog.cc index feeb750ab6f..81764df9da6 100644 --- a/hybridse/examples/toydb/src/tablet/tablet_catalog.cc +++ b/hybridse/examples/toydb/src/tablet/tablet_catalog.cc @@ -19,7 +19,6 @@ #include #include #include -#include "codec/list_iterator_codec.h" #include "glog/logging.h" #include "storage/table_iterator.h" @@ -99,13 +98,6 @@ bool TabletTableHandler::Init() { return true; } -std::unique_ptr TabletTableHandler::GetIterator() { - std::unique_ptr it( - new storage::FullTableIterator(table_->GetSegments(), - table_->GetSegCnt(), table_)); - return std::move(it); -} - std::unique_ptr TabletTableHandler::GetWindowIterator( const std::string& idx_name) { auto iter = index_hint_.find(idx_name); @@ -136,22 +128,6 @@ RowIterator* TabletTableHandler::GetRawIterator() { return new storage::FullTableIterator(table_->GetSegments(), table_->GetSegCnt(), table_); } -const uint64_t TabletTableHandler::GetCount() { - auto iter = GetIterator(); - uint64_t cnt = 0; - while (iter->Valid()) { - iter->Next(); - cnt++; - } - return cnt; -} -Row TabletTableHandler::At(uint64_t pos) { - auto iter = GetIterator(); - while (pos-- > 0 && iter->Valid()) { - iter->Next(); - } - return iter->Valid() ? iter->GetValue() : Row(); -} TabletCatalog::TabletCatalog() : tables_(), db_() {} @@ -249,22 +225,6 @@ std::unique_ptr TabletSegmentHandler::GetWindowIterator( const std::string& idx_name) { return std::unique_ptr(); } -const uint64_t TabletSegmentHandler::GetCount() { - auto iter = GetIterator(); - uint64_t cnt = 0; - while (iter->Valid()) { - cnt++; - iter->Next(); - } - return cnt; -} -Row TabletSegmentHandler::At(uint64_t pos) { - auto iter = GetIterator(); - while (pos-- > 0 && iter->Valid()) { - iter->Next(); - } - return iter->Valid() ? iter->GetValue() : Row(); -} const uint64_t TabletPartitionHandler::GetCount() { auto iter = GetWindowIterator(); @@ -275,5 +235,6 @@ const uint64_t TabletPartitionHandler::GetCount() { } return cnt; } + } // namespace tablet } // namespace hybridse diff --git a/hybridse/examples/toydb/src/tablet/tablet_catalog.h b/hybridse/examples/toydb/src/tablet/tablet_catalog.h index fa41140a495..9d2e8b907e5 100644 --- a/hybridse/examples/toydb/src/tablet/tablet_catalog.h +++ b/hybridse/examples/toydb/src/tablet/tablet_catalog.h @@ -21,7 +21,6 @@ #include #include #include -#include "base/spin_lock.h" #include "storage/table_impl.h" #include "vm/catalog.h" @@ -68,8 +67,6 @@ class TabletSegmentHandler : public TableHandler { std::unique_ptr GetIterator() override; RowIterator* GetRawIterator() override; std::unique_ptr GetWindowIterator(const std::string& idx_name) override; - const uint64_t GetCount() override; - Row At(uint64_t pos) override; const std::string GetHandlerTypeName() override { return "TabletSegmentHandler"; } @@ -79,7 +76,7 @@ class TabletSegmentHandler : public TableHandler { std::string key_; }; -class TabletPartitionHandler +class TabletPartitionHandler final : public PartitionHandler, public std::enable_shared_from_this { public: @@ -91,6 +88,8 @@ class TabletPartitionHandler ~TabletPartitionHandler() {} + RowIterator* GetRawIterator() override { return table_handler_->GetRawIterator(); } + const OrderType GetOrderType() const override { return OrderType::kDescOrder; } const vm::Schema* GetSchema() override { return table_handler_->GetSchema(); } @@ -104,6 +103,7 @@ class TabletPartitionHandler std::unique_ptr GetWindowIterator() override { return table_handler_->GetWindowIterator(index_name_); } + const uint64_t GetCount() override; std::shared_ptr GetSegment(const std::string& key) override { @@ -119,7 +119,7 @@ class TabletPartitionHandler vm::IndexHint index_hint_; }; -class TabletTableHandler +class TabletTableHandler final : public vm::TableHandler, public std::enable_shared_from_this { public: @@ -135,28 +135,23 @@ class TabletTableHandler bool Init(); - inline const vm::Schema* GetSchema() { return &schema_; } + const vm::Schema* GetSchema() override { return &schema_; } - inline const std::string& GetName() { return name_; } + const std::string& GetName() override { return name_; } - inline const std::string& GetDatabase() { return db_; } + const std::string& GetDatabase() override { return db_; } - inline const vm::Types& GetTypes() { return types_; } + const vm::Types& GetTypes() override { return types_; } - inline const vm::IndexHint& GetIndex() { return index_hint_; } + const vm::IndexHint& GetIndex() override { return index_hint_; } const Row Get(int32_t pos); - inline std::shared_ptr GetTable() { return table_; } - std::unique_ptr GetIterator(); + std::shared_ptr GetTable() { return table_; } RowIterator* GetRawIterator() override; - std::unique_ptr GetWindowIterator( - const std::string& idx_name); - virtual const uint64_t GetCount(); - Row At(uint64_t pos) override; + std::unique_ptr GetWindowIterator(const std::string& idx_name) override; - virtual std::shared_ptr GetPartition( - const std::string& index_name) { + std::shared_ptr GetPartition(const std::string& index_name) override { if (index_hint_.find(index_name) == index_hint_.cend()) { LOG(WARNING) << "fail to get partition for tablet table handler, index name " @@ -169,12 +164,12 @@ class TabletTableHandler const std::string GetHandlerTypeName() override { return "TabletTableHandler"; } - virtual std::shared_ptr GetTablet( - const std::string& index_name, const std::string& pk) { + std::shared_ptr GetTablet(const std::string& index_name, + const std::string& pk) override { return tablet_; } - virtual std::shared_ptr GetTablet( - const std::string& index_name, const std::vector& pks) { + std::shared_ptr GetTablet(const std::string& index_name, + const std::vector& pks) override { return tablet_; } diff --git a/hybridse/examples/toydb/src/testing/toydb_engine_test.cc b/hybridse/examples/toydb/src/testing/toydb_engine_test.cc index a4cd2b095d8..02438aeebac 100644 --- a/hybridse/examples/toydb/src/testing/toydb_engine_test.cc +++ b/hybridse/examples/toydb/src/testing/toydb_engine_test.cc @@ -91,6 +91,13 @@ TEST_P(EngineTest, TestClusterBatchRequestEngine) { } } +// ====================================================== / +// BatchRequestEngineTest +// test batch request mode only, with yaml: +// - case/function/test_batch_request.yaml +// +// TODO(ace): merge to EngineTest above simply +// ====================================================== / TEST_P(BatchRequestEngineTest, TestBatchRequestEngine) { auto& sql_case = GetParam(); LOG(INFO) << "ID: " << sql_case.id() << ", DESC: " << sql_case.desc(); diff --git a/hybridse/examples/toydb/src/testing/toydb_engine_test_base.cc b/hybridse/examples/toydb/src/testing/toydb_engine_test_base.cc index fcaa71d8373..35a595b431e 100644 --- a/hybridse/examples/toydb/src/testing/toydb_engine_test_base.cc +++ b/hybridse/examples/toydb/src/testing/toydb_engine_test_base.cc @@ -15,8 +15,9 @@ */ #include "testing/toydb_engine_test_base.h" + +#include "absl/strings/str_join.h" #include "gtest/gtest.h" -#include "gtest/internal/gtest-param-util.h" using namespace llvm; // NOLINT (build/namespaces) using namespace llvm::orc; // NOLINT (build/namespaces) @@ -141,18 +142,12 @@ std::shared_ptr BuildOnePkTableStorage( } return catalog; } -void BatchRequestEngineCheckWithCommonColumnIndices( - const SqlCase& sql_case, const EngineOptions options, - const std::set& common_column_indices) { - std::ostringstream oss; - for (size_t index : common_column_indices) { - oss << index << ","; - } - LOG(INFO) << "BatchRequestEngineCheckWithCommonColumnIndices: " - "common_column_indices = [" - << oss.str() << "]"; - ToydbBatchRequestEngineTestRunner engine_test(sql_case, options, - common_column_indices); +// Run check with common column index info +void BatchRequestEngineCheckWithCommonColumnIndices(const SqlCase& sql_case, const EngineOptions options, + const std::set& common_column_indices) { + LOG(INFO) << "BatchRequestEngineCheckWithCommonColumnIndices: common_column_indices = [" + << absl::StrJoin(common_column_indices, ",") << "]"; + ToydbBatchRequestEngineTestRunner engine_test(sql_case, options, common_column_indices); engine_test.RunCheck(); } diff --git a/hybridse/include/codec/fe_row_codec.h b/hybridse/include/codec/fe_row_codec.h index 1e0e5b1badc..0e0b153f5a5 100644 --- a/hybridse/include/codec/fe_row_codec.h +++ b/hybridse/include/codec/fe_row_codec.h @@ -157,6 +157,9 @@ class RowView { const Schema* GetSchema() const { return &schema_; } inline bool IsNULL(const int8_t* row, uint32_t idx) const { + if (row == nullptr) { + return true; + } const int8_t* ptr = row + HEADER_LENGTH + (idx >> 3); return *(reinterpret_cast(ptr)) & (1 << (idx & 0x07)); } diff --git a/hybridse/include/codec/row.h b/hybridse/include/codec/row.h index cd6abb0a3a1..69158d41e85 100644 --- a/hybridse/include/codec/row.h +++ b/hybridse/include/codec/row.h @@ -54,7 +54,7 @@ class Row { inline int32_t size() const { return slice_.size(); } inline int32_t size(int32_t pos) const { - return 0 == pos ? slice_.size() : slices_[pos - 1].size(); + return 0 == pos ? slice_.size() : slices_.at(pos - 1).size(); } // Return true if the length of the referenced data is zero diff --git a/hybridse/include/codec/row_iterator.h b/hybridse/include/codec/row_iterator.h index 2075918666c..fa60d21a37e 100644 --- a/hybridse/include/codec/row_iterator.h +++ b/hybridse/include/codec/row_iterator.h @@ -71,7 +71,14 @@ class WindowIterator { virtual bool Valid() = 0; /// Return the RowIterator of current segment /// of dataset if Valid() return `true`. - virtual std::unique_ptr GetValue() = 0; + virtual std::unique_ptr GetValue() { + auto p = GetRawValue(); + if (!p) { + return nullptr; + } + + return std::unique_ptr(p); + } /// Return the RowIterator of current segment /// of dataset if Valid() return `true`. virtual RowIterator *GetRawValue() = 0; diff --git a/hybridse/include/codec/row_list.h b/hybridse/include/codec/row_list.h index b32ad24c3eb..f601b207b9c 100644 --- a/hybridse/include/codec/row_list.h +++ b/hybridse/include/codec/row_list.h @@ -65,7 +65,13 @@ class ListV { ListV() {} virtual ~ListV() {} /// \brief Return the const iterator - virtual std::unique_ptr> GetIterator() = 0; + virtual std::unique_ptr> GetIterator() { + auto raw = GetRawIterator(); + if (raw == nullptr) { + return {}; + } + return std::unique_ptr>(raw); + } /// \brief Return the const iterator raw pointer virtual ConstIterator *GetRawIterator() = 0; @@ -76,7 +82,7 @@ class ListV { virtual const uint64_t GetCount() { auto iter = GetIterator(); uint64_t cnt = 0; - while (iter->Valid()) { + while (iter && iter->Valid()) { iter->Next(); cnt++; } diff --git a/hybridse/include/node/node_enum.h b/hybridse/include/node/node_enum.h index 4fc914799d0..baa3bdb2afe 100644 --- a/hybridse/include/node/node_enum.h +++ b/hybridse/include/node/node_enum.h @@ -97,6 +97,7 @@ enum SqlNodeType { kWithClauseEntry, kAlterTableStmt, kShowStmt, + kCompressType, kSqlNodeTypeLast, // debug type }; @@ -251,7 +252,7 @@ enum JoinType { kJoinTypeRight, kJoinTypeInner, kJoinTypeConcat, - kJoinTypeComma + kJoinTypeCross, // AKA commma join }; enum UnionType { kUnionTypeDistinct, kUnionTypeAll }; @@ -283,6 +284,8 @@ enum CmdType { kCmdShowFunctions, kCmdDropFunction, kCmdShowJobLog, + kCmdShowCreateTable, + kCmdTruncate, kCmdFake, // not a real cmd, for testing purpose only kLastCmd = kCmdFake, }; @@ -341,6 +344,11 @@ enum StorageMode { kHDD = 3, }; +enum CompressType { + kNoCompress = 0, + kSnappy = 1, +}; + // batch plan node type enum BatchPlanNodeType { kBatchDataset, kBatchPartition, kBatchMap }; diff --git a/hybridse/include/node/node_manager.h b/hybridse/include/node/node_manager.h index 2dcd013e36f..e70f0a59564 100644 --- a/hybridse/include/node/node_manager.h +++ b/hybridse/include/node/node_manager.h @@ -151,7 +151,7 @@ class NodeManager { WindowDefNode *MergeWindow(const WindowDefNode *w1, const WindowDefNode *w2); OrderExpression* MakeOrderExpression(const ExprNode* expr, const bool is_asc); - OrderByNode *MakeOrderByNode(const ExprListNode *order_expressions); + OrderByNode *MakeOrderByNode(ExprListNode *order_expressions); FrameExtent *MakeFrameExtent(SqlNode *start, SqlNode *end); SqlNode *MakeFrameBound(BoundType bound_type); SqlNode *MakeFrameBound(BoundType bound_type, ExprNode *offset); @@ -399,8 +399,6 @@ class NodeManager { SqlNode *MakeReplicaNumNode(int num); - SqlNode *MakeStorageModeNode(StorageMode storage_mode); - SqlNode *MakePartitionNumNode(int num); SqlNode *MakeDistributionsNode(const NodePointVector& distribution_list); diff --git a/hybridse/include/node/sql_node.h b/hybridse/include/node/sql_node.h index 6118c164193..30f7a6cc34a 100644 --- a/hybridse/include/node/sql_node.h +++ b/hybridse/include/node/sql_node.h @@ -25,6 +25,7 @@ #include #include "absl/status/statusor.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "boost/algorithm/string.hpp" @@ -309,17 +310,26 @@ inline const std::string StorageModeName(StorageMode mode) { } inline const StorageMode NameToStorageMode(const std::string& name) { - if (boost::iequals(name, "memory")) { + if (absl::EqualsIgnoreCase(name, "memory")) { return kMemory; - } else if (boost::iequals(name, "hdd")) { + } else if (absl::EqualsIgnoreCase(name, "hdd")) { return kHDD; - } else if (boost::iequals(name, "ssd")) { + } else if (absl::EqualsIgnoreCase(name, "ssd")) { return kSSD; } else { return kUnknown; } } +inline absl::StatusOr NameToCompressType(const std::string& name) { + if (absl::EqualsIgnoreCase(name, "snappy")) { + return CompressType::kSnappy; + } else if (absl::EqualsIgnoreCase(name, "nocompress")) { + return CompressType::kNoCompress; + } + return absl::Status(absl::StatusCode::kInvalidArgument, absl::StrCat("invalid compress type: ", name)); +} + inline const std::string RoleTypeName(RoleType type) { switch (type) { case kLeader: @@ -476,9 +486,10 @@ class ExprNode : public SqlNode { virtual bool IsListReturn(ExprAnalysisContext *ctx) const { return false; } /** - * Default expression node deep copy implementation + * Returns new ExprNode with all of fields copyed, excepting descendants ExprNodes. */ virtual ExprNode *ShadowCopy(NodeManager *) const = 0; + ExprNode *DeepCopy(NodeManager *) const override; // Get the compatible type that lhs and rhs can both casted into @@ -581,8 +592,13 @@ class FnNodeList : public FnNode { }; class OrderExpression : public ExprNode { public: + // expr maybe null OrderExpression(const ExprNode *expr, const bool is_asc) - : ExprNode(kExprOrderExpression), expr_(expr), is_asc_(is_asc) {} + : ExprNode(kExprOrderExpression), expr_(expr), is_asc_(is_asc) { + if (expr != nullptr) { + AddChild(const_cast(expr)); + } + } ~OrderExpression() {} void Print(std::ostream &output, const std::string &org_tab) const; const std::string GetExprString() const; @@ -597,8 +613,10 @@ class OrderExpression : public ExprNode { }; class OrderByNode : public ExprNode { public: - explicit OrderByNode(const ExprListNode *order_expressions) - : ExprNode(kExprOrder), order_expressions_(order_expressions) {} + explicit OrderByNode(ExprListNode *order_expressions) + : ExprNode(kExprOrder), order_expressions_(order_expressions) { + AddChild(order_expressions); + } ~OrderByNode() {} void Print(std::ostream &output, const std::string &org_tab) const; @@ -623,7 +641,7 @@ class OrderByNode : public ExprNode { return order_expression->expr(); } bool is_asc() const { return false; } - const ExprListNode *order_expressions_; + ExprListNode *order_expressions_; }; class TableRefNode : public SqlNode { public: @@ -1158,6 +1176,9 @@ class FrameBound : public SqlNode { int64_t GetOffset() const { return offset_; } void SetOffset(int64_t v) { offset_ = v; } + // is offset [OPEN] PRECEDING/FOLLOWING + bool is_offset_bound() const; + /// \brief get the inclusive frame bound offset value that has signed symbol /// @@ -1648,7 +1669,7 @@ class ColumnRefNode : public ExprNode { static ColumnRefNode *CastFrom(ExprNode *node); void Print(std::ostream &output, const std::string &org_tab) const; - const std::string GetExprString() const; + const std::string GetExprString() const override; const std::string GenerateExpressionName() const; virtual bool Equals(const ExprNode *node) const; ColumnRefNode *ShadowCopy(NodeManager *) const override; @@ -1873,6 +1894,23 @@ class StorageModeNode : public SqlNode { StorageMode storage_mode_; }; +class CompressTypeNode : public SqlNode { + public: + CompressTypeNode() : SqlNode(kCompressType, 0, 0), compress_type_(kNoCompress) {} + + explicit CompressTypeNode(CompressType compress_type) + : SqlNode(kCompressType, 0, 0), compress_type_(compress_type) {} + + ~CompressTypeNode() {} + + CompressType GetCompressType() const { return compress_type_; } + + void Print(std::ostream &output, const std::string &org_tab) const; + + private: + CompressType compress_type_; +}; + class CreateTableLikeClause { public: CreateTableLikeClause() = default; diff --git a/hybridse/include/passes/expression/expr_pass.h b/hybridse/include/passes/expression/expr_pass.h index 1c41307c28a..c88b7ee8585 100644 --- a/hybridse/include/passes/expression/expr_pass.h +++ b/hybridse/include/passes/expression/expr_pass.h @@ -65,15 +65,15 @@ class ExprReplacer { void AddReplacement(const node::ExprIdNode* arg, node::ExprNode* repl); void AddReplacement(const node::ExprNode* expr, node::ExprNode* repl); void AddReplacement(size_t column_id, node::ExprNode* repl); - void AddReplacement(const std::string& relation_name, - const std::string& column_name, node::ExprNode* repl); + void AddReplacement(const std::string& relation_name, const std::string& column_name, node::ExprNode* repl); - hybridse::base::Status Replace(node::ExprNode* root, - node::ExprNode** output) const; + // For the given `ExprNode` tree, do the in-place replacements specified by `AddReplacement` calls. + // Returns new ExprNode if `root` is ExprIdNode/ColumnIdNode/ColumnRefNode, `root` with its descendants replaced + // otherwise + hybridse::base::Status Replace(node::ExprNode* root, node::ExprNode** output) const; private: - hybridse::base::Status DoReplace(node::ExprNode* root, - std::unordered_set* visited, + hybridse::base::Status DoReplace(node::ExprNode* root, std::unordered_set* visited, node::ExprNode** output) const; std::unordered_map arg_id_map_; diff --git a/hybridse/include/vm/catalog.h b/hybridse/include/vm/catalog.h index 30e68316606..4bd007645bd 100644 --- a/hybridse/include/vm/catalog.h +++ b/hybridse/include/vm/catalog.h @@ -217,6 +217,7 @@ class TableHandler : public DataHandler { virtual ~TableHandler() {} /// Return table column Types information. + /// TODO: rm it, never used virtual const Types& GetTypes() = 0; /// Return the index information @@ -224,8 +225,7 @@ class TableHandler : public DataHandler { /// Return WindowIterator /// so that user can use it to iterate datasets segment by segment. - virtual std::unique_ptr GetWindowIterator( - const std::string& idx_name) = 0; + virtual std::unique_ptr GetWindowIterator(const std::string& idx_name) { return nullptr; } /// Return the HandlerType of the dataset. /// Return HandlerType::kTableHandler by default @@ -254,8 +254,7 @@ class TableHandler : public DataHandler { /// Return Tablet binding to specify index and keys. /// Return `null` by default. - virtual std::shared_ptr GetTablet( - const std::string& index_name, const std::vector& pks) { + virtual std::shared_ptr GetTablet(const std::string& index_name, const std::vector& pks) { return std::shared_ptr(); } }; @@ -286,27 +285,19 @@ class ErrorTableHandler : public TableHandler { /// Return empty column Types. const Types& GetTypes() override { return types_; } /// Return empty table Schema. - inline const Schema* GetSchema() override { return schema_; } + const Schema* GetSchema() override { return schema_; } /// Return empty table name - inline const std::string& GetName() override { return table_name_; } + const std::string& GetName() override { return table_name_; } /// Return empty indexn information - inline const IndexHint& GetIndex() override { return index_hint_; } + const IndexHint& GetIndex() override { return index_hint_; } /// Return name of database - inline const std::string& GetDatabase() override { return db_; } + const std::string& GetDatabase() override { return db_; } /// Return null iterator - std::unique_ptr GetIterator() { - return std::unique_ptr(); - } - /// Return null iterator - RowIterator* GetRawIterator() { return nullptr; } - /// Return null window iterator - std::unique_ptr GetWindowIterator( - const std::string& idx_name) { - return std::unique_ptr(); - } + RowIterator* GetRawIterator() override { return nullptr; } + /// Return empty row - virtual Row At(uint64_t pos) { return Row(); } + Row At(uint64_t pos) override { return Row(); } /// Return 0 const uint64_t GetCount() override { return 0; } @@ -317,7 +308,7 @@ class ErrorTableHandler : public TableHandler { } /// Return status - virtual base::Status GetStatus() { return status_; } + base::Status GetStatus() override { return status_; } protected: base::Status status_; @@ -340,16 +331,11 @@ class PartitionHandler : public TableHandler { PartitionHandler() : TableHandler() {} ~PartitionHandler() {} - /// Return the iterator of row iterator. - /// Return null by default - virtual std::unique_ptr GetIterator() { - return std::unique_ptr(); - } - /// Return the iterator of row iterator - /// Return null by default - RowIterator* GetRawIterator() { return nullptr; } - virtual std::unique_ptr GetWindowIterator( - const std::string& idx_name) { + // Return the iterator of row iterator + // Return null by default + RowIterator* GetRawIterator() override { return nullptr; } + + std::unique_ptr GetWindowIterator(const std::string& idx_name) override { return std::unique_ptr(); } @@ -361,18 +347,15 @@ class PartitionHandler : public TableHandler { const HandlerType GetHandlerType() override { return kPartitionHandler; } /// Return empty row, cause partition dataset does not support At operation. - virtual Row At(uint64_t pos) { return Row(); } + // virtual Row At(uint64_t pos) { return Row(); } /// Return Return table handler of specific segment binding to given key. /// Return `null` by default. - virtual std::shared_ptr GetSegment(const std::string& key) { - return std::shared_ptr(); - } + virtual std::shared_ptr GetSegment(const std::string& key) = 0; /// Return a sequence of table handles of specify segments binding to given /// keys set. - virtual std::vector> GetSegments( - const std::vector& keys) { + virtual std::vector> GetSegments(const std::vector& keys) { std::vector> segments; for (auto key : keys) { segments.push_back(GetSegment(key)); @@ -383,9 +366,6 @@ class PartitionHandler : public TableHandler { const std::string GetHandlerTypeName() override { return "PartitionHandler"; } - /// Return order type of the dataset, - /// and return kNoneOrder by default. - const OrderType GetOrderType() const { return kNoneOrder; } }; /// \brief A wrapper of table handler which is used as a asynchronous row diff --git a/hybridse/include/vm/engine.h b/hybridse/include/vm/engine.h index 3cb7564be98..e552e5889c6 100644 --- a/hybridse/include/vm/engine.h +++ b/hybridse/include/vm/engine.h @@ -420,9 +420,6 @@ class Engine { EngineOptions GetEngineOptions(); private: - // Get all dependent (db, table) info from physical plan - Status GetDependentTables(const PhysicalOpNode*, std::set>*); - std::shared_ptr GetCacheLocked(const std::string& db, const std::string& sql, EngineMode engine_mode); diff --git a/hybridse/include/vm/mem_catalog.h b/hybridse/include/vm/mem_catalog.h index 2fc5df4960c..6237edd1d43 100644 --- a/hybridse/include/vm/mem_catalog.h +++ b/hybridse/include/vm/mem_catalog.h @@ -25,8 +25,6 @@ #include #include #include -#include "base/fe_slice.h" -#include "codec/list_iterator_codec.h" #include "glog/logging.h" #include "vm/catalog.h" @@ -66,11 +64,11 @@ class MemTimeTableIterator : public RowIterator { MemTimeTableIterator(const MemTimeTable* table, const vm::Schema* schema, int32_t start, int32_t end); ~MemTimeTableIterator(); - void Seek(const uint64_t& ts); - void SeekToFirst(); - const uint64_t& GetKey() const; - void Next(); - bool Valid() const; + void Seek(const uint64_t& ts) override; + void SeekToFirst() override; + const uint64_t& GetKey() const override; + void Next() override; + bool Valid() const override; const Row& GetValue() override; bool IsSeekable() const override; @@ -88,12 +86,12 @@ class MemTableIterator : public RowIterator { MemTableIterator(const MemTable* table, const vm::Schema* schema, int32_t start, int32_t end); ~MemTableIterator(); - void Seek(const uint64_t& ts); - void SeekToFirst(); - const uint64_t& GetKey() const; - const Row& GetValue(); - void Next(); - bool Valid() const; + void Seek(const uint64_t& ts) override; + void SeekToFirst() override; + const uint64_t& GetKey() const override; + const Row& GetValue() override; + void Next() override; + bool Valid() const override; bool IsSeekable() const override; private: @@ -115,7 +113,6 @@ class MemWindowIterator : public WindowIterator { void SeekToFirst(); void Next(); bool Valid(); - std::unique_ptr GetValue(); RowIterator* GetRawValue(); const Row GetKey(); @@ -157,24 +154,21 @@ class MemTableHandler : public TableHandler { ~MemTableHandler() override; const Types& GetTypes() override { return types_; } - inline const Schema* GetSchema() { return schema_; } - inline const std::string& GetName() { return table_name_; } - inline const IndexHint& GetIndex() { return index_hint_; } - inline const std::string& GetDatabase() { return db_; } + const Schema* GetSchema() override { return schema_; } + const std::string& GetName() override { return table_name_; } + const IndexHint& GetIndex() override { return index_hint_; } + const std::string& GetDatabase() override { return db_; } - std::unique_ptr GetIterator() override; RowIterator* GetRawIterator() override; - std::unique_ptr GetWindowIterator( - const std::string& idx_name); void AddRow(const Row& row); void Reverse(); - virtual const uint64_t GetCount() { return table_.size(); } - virtual Row At(uint64_t pos) { + const uint64_t GetCount() override { return table_.size(); } + Row At(uint64_t pos) override { return pos < table_.size() ? table_.at(pos) : Row(); } - const OrderType GetOrderType() const { return order_type_; } + const OrderType GetOrderType() const override { return order_type_; } void SetOrderType(const OrderType order_type) { order_type_ = order_type; } const std::string GetHandlerTypeName() override { return "MemTableHandler"; @@ -200,14 +194,11 @@ class MemTimeTableHandler : public TableHandler { const Schema* schema); const Types& GetTypes() override; ~MemTimeTableHandler() override; - inline const Schema* GetSchema() { return schema_; } - inline const std::string& GetName() { return table_name_; } - inline const IndexHint& GetIndex() { return index_hint_; } - std::unique_ptr GetIterator(); - RowIterator* GetRawIterator(); - inline const std::string& GetDatabase() { return db_; } - std::unique_ptr GetWindowIterator( - const std::string& idx_name); + const Schema* GetSchema() override { return schema_; } + const std::string& GetName() override { return table_name_; } + const IndexHint& GetIndex() override { return index_hint_; } + RowIterator* GetRawIterator() override; + const std::string& GetDatabase() override { return db_; } void AddRow(const uint64_t key, const Row& v); void AddFrontRow(const uint64_t key, const Row& v); void PopBackRow(); @@ -220,12 +211,12 @@ class MemTimeTableHandler : public TableHandler { } void Sort(const bool is_asc); void Reverse(); - virtual const uint64_t GetCount() { return table_.size(); } - virtual Row At(uint64_t pos) { + const uint64_t GetCount() override { return table_.size(); } + Row At(uint64_t pos) override { return pos < table_.size() ? table_.at(pos).second : Row(); } void SetOrderType(const OrderType order_type) { order_type_ = order_type; } - const OrderType GetOrderType() const { return order_type_; } + const OrderType GetOrderType() const override { return order_type_; } const std::string GetHandlerTypeName() override { return "MemTimeTableHandler"; } @@ -254,21 +245,11 @@ class Window : public MemTimeTableHandler { return std::make_unique(&table_, schema_); } - RowIterator* GetRawIterator() { - return new vm::MemTimeTableIterator(&table_, schema_); - } + RowIterator* GetRawIterator() override { return new vm::MemTimeTableIterator(&table_, schema_); } virtual bool BufferData(uint64_t key, const Row& row) = 0; virtual void PopBackData() { PopBackRow(); } virtual void PopFrontData() = 0; - virtual const uint64_t GetCount() { return table_.size(); } - virtual Row At(uint64_t pos) { - if (pos >= table_.size()) { - return Row(); - } else { - return table_[pos].second; - } - } const std::string GetHandlerTypeName() override { return "Window"; } bool instance_not_in_window() const { return instance_not_in_window_; } @@ -322,7 +303,7 @@ class WindowRange { return WindowRange(Window::kFrameRowsMergeRowsRange, start_offset, 0, rows_preceding, max_size); } - inline const WindowPositionStatus GetWindowPositionStatus( + const WindowPositionStatus GetWindowPositionStatus( bool out_of_rows, bool before_window, bool exceed_window) const { switch (frame_type_) { case Window::WindowFrameType::kFrameRows: @@ -531,7 +512,7 @@ class CurrentHistoryWindow : public HistoryWindow { void PopFrontData() override { PopFrontRow(); } - bool BufferData(uint64_t key, const Row& row) { + bool BufferData(uint64_t key, const Row& row) override { if (!table_.empty() && GetFrontRow().first > key) { DLOG(WARNING) << "Fail BufferData: buffer key less than latest key"; return false; @@ -560,34 +541,25 @@ class MemSegmentHandler : public TableHandler { virtual ~MemSegmentHandler() {} - inline const vm::Schema* GetSchema() { + const vm::Schema* GetSchema() override { return partition_hander_->GetSchema(); } - inline const std::string& GetName() { return partition_hander_->GetName(); } + const std::string& GetName() override { return partition_hander_->GetName(); } - inline const std::string& GetDatabase() { + const std::string& GetDatabase() override { return partition_hander_->GetDatabase(); } - inline const vm::Types& GetTypes() { return partition_hander_->GetTypes(); } + const vm::Types& GetTypes() override { return partition_hander_->GetTypes(); } - inline const vm::IndexHint& GetIndex() { + const vm::IndexHint& GetIndex() override { return partition_hander_->GetIndex(); } - const OrderType GetOrderType() const { + const OrderType GetOrderType() const override { return partition_hander_->GetOrderType(); } - std::unique_ptr GetIterator() { - auto iter = partition_hander_->GetWindowIterator(); - if (iter) { - iter->Seek(key_); - return iter->Valid() ? iter->GetValue() - : std::unique_ptr(); - } - return std::unique_ptr(); - } RowIterator* GetRawIterator() override { auto iter = partition_hander_->GetWindowIterator(); if (iter) { @@ -596,12 +568,11 @@ class MemSegmentHandler : public TableHandler { } return nullptr; } - std::unique_ptr GetWindowIterator( - const std::string& idx_name) { + std::unique_ptr GetWindowIterator(const std::string& idx_name) override { LOG(WARNING) << "SegmentHandler can't support window iterator"; return std::unique_ptr(); } - virtual const uint64_t GetCount() { + const uint64_t GetCount() override { auto iter = GetIterator(); if (!iter) { return 0; @@ -634,9 +605,7 @@ class MemSegmentHandler : public TableHandler { std::string key_; }; -class MemPartitionHandler - : public PartitionHandler, - public std::enable_shared_from_this { +class MemPartitionHandler : public PartitionHandler, public std::enable_shared_from_this { public: MemPartitionHandler(); explicit MemPartitionHandler(const Schema* schema); @@ -649,18 +618,19 @@ class MemPartitionHandler const Schema* GetSchema() override; const std::string& GetName() override; const std::string& GetDatabase() override; - virtual std::unique_ptr GetWindowIterator(); + RowIterator* GetRawIterator() override { return nullptr; } + std::unique_ptr GetWindowIterator() override; bool AddRow(const std::string& key, uint64_t ts, const Row& row); void Sort(const bool is_asc); void Reverse(); void Print(); - virtual const uint64_t GetCount() { return partitions_.size(); } - virtual std::shared_ptr GetSegment(const std::string& key) { + const uint64_t GetCount() override { return partitions_.size(); } + std::shared_ptr GetSegment(const std::string& key) override { return std::shared_ptr( new MemSegmentHandler(shared_from_this(), key)); } void SetOrderType(const OrderType order_type) { order_type_ = order_type; } - const OrderType GetOrderType() const { return order_type_; } + const OrderType GetOrderType() const override { return order_type_; } const std::string GetHandlerTypeName() override { return "MemPartitionHandler"; } @@ -674,6 +644,7 @@ class MemPartitionHandler IndexHint index_hint_; OrderType order_type_; }; + class ConcatTableHandler : public MemTimeTableHandler { public: ConcatTableHandler(std::shared_ptr left, size_t left_slices, @@ -692,19 +663,13 @@ class ConcatTableHandler : public MemTimeTableHandler { status_ = SyncValue(); return MemTimeTableHandler::At(pos); } - std::unique_ptr GetIterator() { - if (status_.isRunning()) { - status_ = SyncValue(); - } - return MemTimeTableHandler::GetIterator(); - } - RowIterator* GetRawIterator() { + RowIterator* GetRawIterator() override { if (status_.isRunning()) { status_ = SyncValue(); } return MemTimeTableHandler::GetRawIterator(); } - virtual const uint64_t GetCount() { + const uint64_t GetCount() override { if (status_.isRunning()) { status_ = SyncValue(); } @@ -757,11 +722,11 @@ class MemCatalog : public Catalog { bool Init(); - std::shared_ptr GetDatabase(const std::string& db) { + std::shared_ptr GetDatabase(const std::string& db) override { return dbs_[db]; } std::shared_ptr GetTable(const std::string& db, - const std::string& table_name) { + const std::string& table_name) override { return tables_[db][table_name]; } bool IndexSupport() override { return true; } @@ -783,17 +748,11 @@ class RequestUnionTableHandler : public TableHandler { : request_ts_(request_ts), request_row_(request_row), window_(window) {} ~RequestUnionTableHandler() {} - std::unique_ptr GetIterator() override { - return std::unique_ptr(GetRawIterator()); - } RowIterator* GetRawIterator() override; const Types& GetTypes() override { return window_->GetTypes(); } const IndexHint& GetIndex() override { return window_->GetIndex(); } - std::unique_ptr GetWindowIterator(const std::string&) { - return nullptr; - } - const OrderType GetOrderType() const { return window_->GetOrderType(); } + const OrderType GetOrderType() const override { return window_->GetOrderType(); } const Schema* GetSchema() override { return window_->GetSchema(); } const std::string& GetName() override { return window_->GetName(); } const std::string& GetDatabase() override { return window_->GetDatabase(); } diff --git a/hybridse/include/vm/physical_op.h b/hybridse/include/vm/physical_op.h index c884d0bb7e5..dd51c73bfd1 100644 --- a/hybridse/include/vm/physical_op.h +++ b/hybridse/include/vm/physical_op.h @@ -155,8 +155,10 @@ class Sort : public FnComponent { public: explicit Sort(const node::OrderByNode *orders) : orders_(orders) {} virtual ~Sort() {} + const node::OrderByNode *orders() const { return orders_; } void set_orders(const node::OrderByNode *orders) { orders_ = orders; } + const bool is_asc() const { const node::OrderExpression *first_order_expression = nullptr == orders_ ? nullptr : orders_->GetOrderExpression(0); @@ -172,18 +174,11 @@ class Sort : public FnComponent { return "sort = " + fn_info_.fn_name(); } - void ResolvedRelatedColumns( - std::vector *columns) const { + void ResolvedRelatedColumns(std::vector *columns) const { if (nullptr == orders_) { return; } - auto expr = orders_->GetOrderExpressionExpr(0); - if (nullptr != expr) { - node::ExprListNode exprs; - exprs.AddChild(const_cast(expr)); - node::ColumnOfExpression(orders_->order_expressions_, columns); - } - return; + node::ColumnOfExpression(orders_->order_expressions_, columns); } base::Status ReplaceExpr(const passes::ExprReplacer &replacer, @@ -205,9 +200,9 @@ class Range : public FnComponent { const bool Valid() const { return nullptr != range_key_; } const std::string ToString() const { std::ostringstream oss; - if (nullptr != range_key_ && nullptr != frame_) { + if (nullptr != frame_) { if (nullptr != frame_->frame_range()) { - oss << "range=(" << range_key_->GetExprString() << ", " + oss << "range=(" << node::ExprString(range_key_) << ", " << frame_->frame_range()->start()->GetExprString() << ", " << frame_->frame_range()->end()->GetExprString(); @@ -221,7 +216,7 @@ class Range : public FnComponent { if (nullptr != frame_->frame_range()) { oss << ", "; } - oss << "rows=(" << range_key_->GetExprString() << ", " + oss << "rows=(" << node::ExprString(range_key_) << ", " << frame_->frame_rows()->start()->GetExprString() << ", " << frame_->frame_rows()->end()->GetExprString() << ")"; } @@ -286,8 +281,10 @@ class Key : public FnComponent { return oss.str(); } const bool ValidKey() const { return !node::ExprListNullOrEmpty(keys_); } + const node::ExprListNode *keys() const { return keys_; } void set_keys(const node::ExprListNode *keys) { keys_ = keys; } + const node::ExprListNode *PhysicalProjectNode() const { return keys_; } const std::string FnDetail() const { return "keys=" + fn_info_.fn_name(); } @@ -555,8 +552,7 @@ class PhysicalDataProviderNode : public PhysicalOpNode { class PhysicalTableProviderNode : public PhysicalDataProviderNode { public: - explicit PhysicalTableProviderNode( - const std::shared_ptr &table_handler) + explicit PhysicalTableProviderNode(const std::shared_ptr &table_handler) : PhysicalDataProviderNode(table_handler, kProviderTypeTable) {} base::Status WithNewChildren(node::NodeManager *nm, @@ -582,7 +578,7 @@ class PhysicalRequestProviderNode : public PhysicalDataProviderNode { PhysicalOpNode **out) override; virtual ~PhysicalRequestProviderNode() {} - virtual void Print(std::ostream &output, const std::string &tab) const; + void Print(std::ostream &output, const std::string &tab) const override; }; class PhysicalRequestProviderNodeWithCommonColumn @@ -735,6 +731,7 @@ class PhysicalConstProjectNode : public PhysicalOpNode { public: explicit PhysicalConstProjectNode(const ColumnProjects &project) : PhysicalOpNode(kPhysicalOpConstProject, true), project_(project) { + output_type_ = kSchemaTypeRow; fn_infos_.push_back(&project_.fn_info()); } virtual ~PhysicalConstProjectNode() {} @@ -789,7 +786,11 @@ class PhysicalAggregationNode : public PhysicalProjectNode { public: PhysicalAggregationNode(PhysicalOpNode *node, const ColumnProjects &project, const node::ExprNode *condition) : PhysicalProjectNode(node, kAggregation, project, true), having_condition_(condition) { - output_type_ = kSchemaTypeRow; + if (node->GetOutputType() == kSchemaTypeGroup) { + output_type_ = kSchemaTypeGroup; + } else { + output_type_ = kSchemaTypeRow; + } fn_infos_.push_back(&having_condition_.fn_info()); } virtual ~PhysicalAggregationNode() {} @@ -850,9 +851,7 @@ class WindowOp { std::ostringstream oss; oss << "partition_" << partition_.ToString(); oss << ", " << sort_.ToString(); - if (range_.Valid()) { - oss << ", " << range_.ToString(); - } + oss << ", " << range_.ToString(); return oss.str(); } const std::string FnDetail() const { @@ -1071,7 +1070,7 @@ class RequestWindowUnionList { RequestWindowUnionList() : window_unions_() {} virtual ~RequestWindowUnionList() {} void AddWindowUnion(PhysicalOpNode *node, const RequestWindowOp &window) { - window_unions_.push_back(std::make_pair(node, window)); + window_unions_.emplace_back(node, window); } const PhysicalOpNode *GetKey(uint32_t index) { auto iter = window_unions_.begin(); @@ -1185,23 +1184,25 @@ class PhysicalWindowAggrerationNode : public PhysicalProjectNode { class PhysicalJoinNode : public PhysicalBinaryNode { public: + static constexpr PhysicalOpType kConcreteNodeKind = kPhysicalOpJoin; + PhysicalJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const node::JoinType join_type) - : PhysicalBinaryNode(left, right, kPhysicalOpJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = left->GetOutputType(); + InitOuptput(); } PhysicalJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const node::JoinType join_type, const node::OrderByNode *orders, const node::ExprNode *condition) - : PhysicalBinaryNode(left, right, kPhysicalOpJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type, orders, condition), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = left->GetOutputType(); + InitOuptput(); RegisterFunctionInfo(); } @@ -1210,11 +1211,11 @@ class PhysicalJoinNode : public PhysicalBinaryNode { const node::ExprNode *condition, const node::ExprListNode *left_keys, const node::ExprListNode *right_keys) - : PhysicalBinaryNode(left, right, kPhysicalOpJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type, condition, left_keys, right_keys), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = left->GetOutputType(); + InitOuptput(); RegisterFunctionInfo(); } @@ -1224,31 +1225,31 @@ class PhysicalJoinNode : public PhysicalBinaryNode { const node::ExprNode *condition, const node::ExprListNode *left_keys, const node::ExprListNode *right_keys) - : PhysicalBinaryNode(left, right, kPhysicalOpJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type, orders, condition, left_keys, right_keys), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = left->GetOutputType(); + InitOuptput(); RegisterFunctionInfo(); } PhysicalJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const Join &join) - : PhysicalBinaryNode(left, right, kPhysicalOpJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = left->GetOutputType(); + InitOuptput(); RegisterFunctionInfo(); } PhysicalJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const Join &join, const bool output_right_only) - : PhysicalBinaryNode(left, right, kPhysicalOpJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join), joined_schemas_ctx_(this), output_right_only_(output_right_only) { - output_type_ = left->GetOutputType(); + InitOuptput(); RegisterFunctionInfo(); } @@ -1277,37 +1278,59 @@ class PhysicalJoinNode : public PhysicalBinaryNode { Join join_; SchemasContext joined_schemas_ctx_; const bool output_right_only_; + + private: + void InitOuptput() { + switch (join_.join_type_) { + case node::kJoinTypeLast: + case node::kJoinTypeConcat: { + output_type_ = GetProducer(0)->GetOutputType(); + break; + } + default: { + // standard SQL JOINs, always treat as a table output + if (GetProducer(0)->GetOutputType() == kSchemaTypeGroup) { + output_type_ = kSchemaTypeGroup; + } else { + output_type_ = kSchemaTypeTable; + } + break; + } + } + } }; class PhysicalRequestJoinNode : public PhysicalBinaryNode { public: + static constexpr PhysicalOpType kConcreteNodeKind = kPhysicalOpRequestJoin; + PhysicalRequestJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const node::JoinType join_type) - : PhysicalBinaryNode(left, right, kPhysicalOpRequestJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = kSchemaTypeRow; + InitOuptput(); RegisterFunctionInfo(); } PhysicalRequestJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const node::JoinType join_type, const node::OrderByNode *orders, const node::ExprNode *condition) - : PhysicalBinaryNode(left, right, kPhysicalOpRequestJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type, orders, condition), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = kSchemaTypeRow; + InitOuptput(); RegisterFunctionInfo(); } PhysicalRequestJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, const Join &join, const bool output_right_only) - : PhysicalBinaryNode(left, right, kPhysicalOpRequestJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join), joined_schemas_ctx_(this), output_right_only_(output_right_only) { - output_type_ = kSchemaTypeRow; + InitOuptput(); RegisterFunctionInfo(); } @@ -1317,11 +1340,11 @@ class PhysicalRequestJoinNode : public PhysicalBinaryNode { const node::ExprNode *condition, const node::ExprListNode *left_keys, const node::ExprListNode *right_keys) - : PhysicalBinaryNode(left, right, kPhysicalOpRequestJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type, condition, left_keys, right_keys), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = kSchemaTypeRow; + InitOuptput(); RegisterFunctionInfo(); } PhysicalRequestJoinNode(PhysicalOpNode *left, PhysicalOpNode *right, @@ -1330,11 +1353,11 @@ class PhysicalRequestJoinNode : public PhysicalBinaryNode { const node::ExprNode *condition, const node::ExprListNode *left_keys, const node::ExprListNode *right_keys) - : PhysicalBinaryNode(left, right, kPhysicalOpRequestJoin, false), + : PhysicalBinaryNode(left, right, kConcreteNodeKind, false), join_(join_type, orders, condition, left_keys, right_keys), joined_schemas_ctx_(this), output_right_only_(false) { - output_type_ = kSchemaTypeRow; + InitOuptput(); RegisterFunctionInfo(); } @@ -1365,6 +1388,26 @@ class PhysicalRequestJoinNode : public PhysicalBinaryNode { Join join_; SchemasContext joined_schemas_ctx_; const bool output_right_only_; + + private: + void InitOuptput() { + switch (join_.join_type_) { + case node::kJoinTypeLast: + case node::kJoinTypeConcat: { + output_type_ = GetProducer(0)->GetOutputType(); + break; + } + default: { + // standard SQL JOINs, always treat as a table output + if (GetProducer(0)->GetOutputType() == kSchemaTypeGroup) { + output_type_ = kSchemaTypeGroup; + } else { + output_type_ = kSchemaTypeTable; + } + break; + } + } + } }; class PhysicalUnionNode : public PhysicalBinaryNode { @@ -1421,7 +1464,7 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { instance_not_in_window_(false), exclude_current_time_(false), output_request_row_(true) { - output_type_ = kSchemaTypeTable; + InitOuptput(); fn_infos_.push_back(&window_.partition_.fn_info()); fn_infos_.push_back(&window_.index_key_.fn_info()); @@ -1433,7 +1476,7 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { instance_not_in_window_(w_ptr->instance_not_in_window()), exclude_current_time_(w_ptr->exclude_current_time()), output_request_row_(true) { - output_type_ = kSchemaTypeTable; + InitOuptput(); fn_infos_.push_back(&window_.partition_.fn_info()); fn_infos_.push_back(&window_.sort_.fn_info()); @@ -1449,7 +1492,7 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { instance_not_in_window_(instance_not_in_window), exclude_current_time_(exclude_current_time), output_request_row_(output_request_row) { - output_type_ = kSchemaTypeTable; + InitOuptput(); fn_infos_.push_back(&window_.partition_.fn_info()); fn_infos_.push_back(&window_.sort_.fn_info()); @@ -1461,7 +1504,8 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { virtual void Print(std::ostream &output, const std::string &tab) const; const bool Valid() { return true; } static PhysicalRequestUnionNode *CastFrom(PhysicalOpNode *node); - bool AddWindowUnion(PhysicalOpNode *node) { + bool AddWindowUnion(PhysicalOpNode *node) { return AddWindowUnion(node, window_); } + bool AddWindowUnion(PhysicalOpNode *node, const RequestWindowOp& window) { if (nullptr == node) { LOG(WARNING) << "Fail to add window union : table is null"; return false; @@ -1478,9 +1522,8 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { << "Union Table and window input schema aren't consistent"; return false; } - window_unions_.AddWindowUnion(node, window_); - RequestWindowOp &window_union = - window_unions_.window_unions_.back().second; + window_unions_.AddWindowUnion(node, window); + RequestWindowOp &window_union = window_unions_.window_unions_.back().second; fn_infos_.push_back(&window_union.partition_.fn_info()); fn_infos_.push_back(&window_union.sort_.fn_info()); fn_infos_.push_back(&window_union.range_.fn_info()); @@ -1490,11 +1533,10 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { std::vector GetDependents() const override; - const bool instance_not_in_window() const { - return instance_not_in_window_; - } - const bool exclude_current_time() const { return exclude_current_time_; } - const bool output_request_row() const { return output_request_row_; } + bool instance_not_in_window() const { return instance_not_in_window_; } + bool exclude_current_time() const { return exclude_current_time_; } + bool output_request_row() const { return output_request_row_; } + void set_output_request_row(bool flag) { output_request_row_ = flag; } const RequestWindowOp &window() const { return window_; } const RequestWindowUnionList &window_unions() const { return window_unions_; @@ -1512,10 +1554,20 @@ class PhysicalRequestUnionNode : public PhysicalBinaryNode { } RequestWindowOp window_; - const bool instance_not_in_window_; - const bool exclude_current_time_; - const bool output_request_row_; + bool instance_not_in_window_; + bool exclude_current_time_; + bool output_request_row_; RequestWindowUnionList window_unions_; + + private: + void InitOuptput() { + auto left = GetProducer(0); + if (left->GetOutputType() == kSchemaTypeRow) { + output_type_ = kSchemaTypeTable; + } else { + output_type_ = kSchemaTypeGroup; + } + } }; class PhysicalRequestAggUnionNode : public PhysicalOpNode { @@ -1626,14 +1678,22 @@ class PhysicalFilterNode : public PhysicalUnaryNode { public: PhysicalFilterNode(PhysicalOpNode *node, const node::ExprNode *condition) : PhysicalUnaryNode(node, kPhysicalOpFilter, true), filter_(condition) { - output_type_ = node->GetOutputType(); + if (node->GetOutputType() == kSchemaTypeGroup && filter_.index_key_.ValidKey()) { + output_type_ = kSchemaTypeTable; + } else { + output_type_ = node->GetOutputType(); + } fn_infos_.push_back(&filter_.condition_.fn_info()); fn_infos_.push_back(&filter_.index_key_.fn_info()); } PhysicalFilterNode(PhysicalOpNode *node, Filter filter) : PhysicalUnaryNode(node, kPhysicalOpFilter, true), filter_(filter) { - output_type_ = node->GetOutputType(); + if (node->GetOutputType() == kSchemaTypeGroup && filter_.index_key_.ValidKey()) { + output_type_ = kSchemaTypeTable; + } else { + output_type_ = node->GetOutputType(); + } fn_infos_.push_back(&filter_.condition_.fn_info()); fn_infos_.push_back(&filter_.index_key_.fn_info()); diff --git a/hybridse/include/vm/schemas_context.h b/hybridse/include/vm/schemas_context.h index 43731f076cc..b2e68d9477a 100644 --- a/hybridse/include/vm/schemas_context.h +++ b/hybridse/include/vm/schemas_context.h @@ -58,7 +58,8 @@ class SchemaSource { size_t size() const; void Clear(); - std::string ToString() const; + std::string DebugString() const; + friend std::ostream& operator<<(std::ostream& os, const SchemaSource& sc) { return os << sc.DebugString(); } private: bool CheckSourceSetIndex(size_t idx) const; @@ -71,7 +72,8 @@ class SchemaSource { // column identifier of each output column std::vector column_ids_; - // trace which child and which column id each column come from + // trace which child and which column id each column comes from, index is measured + // based on the physical node tree, starts from 0. // -1 means the column is created from current node std::vector source_child_idxs_; std::vector source_child_column_ids_; @@ -126,10 +128,6 @@ class SchemasContext { base::Status ResolveColumnRefIndex(const node::ColumnRefNode* column_ref, size_t* schema_idx, size_t* col_idx) const; - /** - * Resolve column id with given column expression [ColumnRefNode, ColumnId] - */ - base::Status ResolveColumnID(const node::ExprNode* column, size_t* column_id) const; /** * Given relation name and column name, return column unique id @@ -246,6 +244,10 @@ class SchemasContext { void BuildTrivial(const std::vector& schemas); void BuildTrivial(const std::string& default_db, const std::vector& tables); + std::string DebugString() const; + + friend std::ostream& operator<<(std::ostream& os, const SchemasContext& sc) { return os << sc.DebugString(); } + private: bool IsColumnAmbiguous(const std::string& column_name) const; diff --git a/hybridse/include/vm/simple_catalog.h b/hybridse/include/vm/simple_catalog.h index 1e1cd78a2f6..fd7c2f3b952 100644 --- a/hybridse/include/vm/simple_catalog.h +++ b/hybridse/include/vm/simple_catalog.h @@ -22,7 +22,6 @@ #include #include -#include "glog/logging.h" #include "proto/fe_type.pb.h" #include "vm/catalog.h" #include "vm/mem_catalog.h" diff --git a/hybridse/src/base/fe_slice.cc b/hybridse/src/base/fe_slice.cc index 9f41c6016ca..c2ca3560741 100644 --- a/hybridse/src/base/fe_slice.cc +++ b/hybridse/src/base/fe_slice.cc @@ -25,7 +25,7 @@ void RefCountedSlice::Release() { if (this->ref_cnt_ != nullptr) { auto& cnt = *this->ref_cnt_; cnt -= 1; - if (cnt == 0) { + if (cnt == 0 && buf() != nullptr) { // memset in case the buf is still used after free memset(buf(), 0, size()); free(buf()); diff --git a/hybridse/src/codegen/array_ir_builder.cc b/hybridse/src/codegen/array_ir_builder.cc index f07f551caf1..5bf1bf06e99 100644 --- a/hybridse/src/codegen/array_ir_builder.cc +++ b/hybridse/src/codegen/array_ir_builder.cc @@ -17,6 +17,7 @@ #include "codegen/array_ir_builder.h" #include +#include "codegen/ir_base_builder.h" namespace hybridse { namespace codegen { @@ -113,5 +114,21 @@ base::Status ArrayIRBuilder::NewEmptyArray(llvm::BasicBlock* bb, NativeValue* ou return base::Status::OK(); } +bool ArrayIRBuilder::CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) { + llvm::Value* array_alloca = nullptr; + if (!Create(block, &array_alloca)) { + return false; + } + + llvm::IRBuilder<> builder(block); + ::llvm::Value* array_sz = builder.getInt64(0); + if (!Set(block, array_alloca, 2, array_sz)) { + return false; + } + + *output = array_alloca; + return true; +} + } // namespace codegen } // namespace hybridse diff --git a/hybridse/src/codegen/array_ir_builder.h b/hybridse/src/codegen/array_ir_builder.h index 38eb6eda1ad..66ef2fe05da 100644 --- a/hybridse/src/codegen/array_ir_builder.h +++ b/hybridse/src/codegen/array_ir_builder.h @@ -49,12 +49,12 @@ class ArrayIRBuilder : public StructTypeIRBuilder { void InitStructType() override; - bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) override { return true; } + bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) override; bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist) override { return true; } base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, NativeValue* output) override { - return base::Status::OK(); + CHECK_TRUE(false, common::kCodegenError, "casting to array un-implemented"); }; private: diff --git a/hybridse/src/codegen/cast_expr_ir_builder.cc b/hybridse/src/codegen/cast_expr_ir_builder.cc index 526a686ae66..57e4103cba6 100644 --- a/hybridse/src/codegen/cast_expr_ir_builder.cc +++ b/hybridse/src/codegen/cast_expr_ir_builder.cc @@ -15,12 +15,15 @@ */ #include "codegen/cast_expr_ir_builder.h" + #include "codegen/date_ir_builder.h" #include "codegen/ir_base_builder.h" #include "codegen/string_ir_builder.h" #include "codegen/timestamp_ir_builder.h" +#include "codegen/type_ir_builder.h" #include "glog/logging.h" #include "node/node_manager.h" +#include "proto/fe_common.pb.h" using hybridse::common::kCodegenError; @@ -72,93 +75,73 @@ Status CastExprIRBuilder::Cast(const NativeValue& value, } return Status::OK(); } -Status CastExprIRBuilder::SafeCast(const NativeValue& value, ::llvm::Type* type, - NativeValue* output) { + +Status CastExprIRBuilder::SafeCast(const NativeValue& value, ::llvm::Type* dst_type, NativeValue* output) { ::llvm::IRBuilder<> builder(block_); - CHECK_TRUE(IsSafeCast(value.GetType(), type), kCodegenError, - "Safe cast fail: unsafe cast"); + CHECK_TRUE(IsSafeCast(value.GetType(), dst_type), kCodegenError, "Safe cast fail: unsafe cast"); Status status; if (value.IsConstNull()) { - if (TypeIRBuilder::IsStringPtr(type)) { - StringIRBuilder string_ir_builder(block_->getModule()); - CHECK_STATUS(string_ir_builder.CreateNull(block_, output)); - return base::Status::OK(); - } else { - *output = NativeValue::CreateNull(type); - } - } else if (TypeIRBuilder::IsTimestampPtr(type)) { + auto res = CreateSafeNull(block_, dst_type); + CHECK_TRUE(res.ok(), kCodegenError, res.status().ToString()); + *output = res.value(); + } else if (TypeIRBuilder::IsTimestampPtr(dst_type)) { TimestampIRBuilder timestamp_ir_builder(block_->getModule()); CHECK_STATUS(timestamp_ir_builder.CastFrom(block_, value, output)); return Status::OK(); - } else if (TypeIRBuilder::IsDatePtr(type)) { + } else if (TypeIRBuilder::IsDatePtr(dst_type)) { DateIRBuilder date_ir_builder(block_->getModule()); CHECK_STATUS(date_ir_builder.CastFrom(block_, value, output)); return Status::OK(); - } else if (TypeIRBuilder::IsStringPtr(type)) { + } else if (TypeIRBuilder::IsStringPtr(dst_type)) { StringIRBuilder string_ir_builder(block_->getModule()); CHECK_STATUS(string_ir_builder.CastFrom(block_, value, output)); return Status::OK(); - } else if (TypeIRBuilder::IsNumber(type)) { + } else if (TypeIRBuilder::IsNumber(dst_type)) { Status status; ::llvm::Value* output_value = nullptr; - CHECK_TRUE(SafeCastNumber(value.GetValue(&builder), type, &output_value, - status), - kCodegenError); + CHECK_TRUE(SafeCastNumber(value.GetValue(&builder), dst_type, &output_value, status), kCodegenError); if (value.IsNullable()) { - *output = NativeValue::CreateWithFlag(output_value, - value.GetIsNull(&builder)); + *output = NativeValue::CreateWithFlag(output_value, value.GetIsNull(&builder)); } else { *output = NativeValue::Create(output_value); } } else { - return Status(common::kCodegenError, - "Can't cast from " + - TypeIRBuilder::TypeName(value.GetType()) + " to " + - TypeIRBuilder::TypeName(type)); + return Status(common::kCodegenError, "Can't cast from " + TypeIRBuilder::TypeName(value.GetType()) + " to " + + TypeIRBuilder::TypeName(dst_type)); } return Status::OK(); } -Status CastExprIRBuilder::UnSafeCast(const NativeValue& value, - ::llvm::Type* type, NativeValue* output) { + +Status CastExprIRBuilder::UnSafeCast(const NativeValue& value, ::llvm::Type* dst_type, NativeValue* output) { ::llvm::IRBuilder<> builder(block_); - if (value.IsConstNull()) { - if (TypeIRBuilder::IsStringPtr(type)) { - StringIRBuilder string_ir_builder(block_->getModule()); - CHECK_STATUS(string_ir_builder.CreateNull(block_, output)); - return base::Status::OK(); - } else { - *output = NativeValue::CreateNull(type); - } - } else if (TypeIRBuilder::IsTimestampPtr(type)) { + if (value.IsConstNull() || (TypeIRBuilder::IsNumber(dst_type) && TypeIRBuilder::IsDatePtr(value.GetType()))) { + // input is const null or (cast date to number) + auto res = CreateSafeNull(block_, dst_type); + CHECK_TRUE(res.ok(), kCodegenError, res.status().ToString()); + *output = res.value(); + } else if (TypeIRBuilder::IsTimestampPtr(dst_type)) { TimestampIRBuilder timestamp_ir_builder(block_->getModule()); CHECK_STATUS(timestamp_ir_builder.CastFrom(block_, value, output)); return Status::OK(); - } else if (TypeIRBuilder::IsDatePtr(type)) { + } else if (TypeIRBuilder::IsDatePtr(dst_type)) { DateIRBuilder date_ir_builder(block_->getModule()); CHECK_STATUS(date_ir_builder.CastFrom(block_, value, output)); return Status::OK(); - } else if (TypeIRBuilder::IsStringPtr(type)) { + } else if (TypeIRBuilder::IsStringPtr(dst_type)) { StringIRBuilder string_ir_builder(block_->getModule()); CHECK_STATUS(string_ir_builder.CastFrom(block_, value, output)); return Status::OK(); - } else if (TypeIRBuilder::IsNumber(type) && - TypeIRBuilder::IsStringPtr(value.GetType())) { + } else if (TypeIRBuilder::IsNumber(dst_type) && TypeIRBuilder::IsStringPtr(value.GetType())) { StringIRBuilder string_ir_builder(block_->getModule()); - CHECK_STATUS( - string_ir_builder.CastToNumber(block_, value, type, output)); + CHECK_STATUS(string_ir_builder.CastToNumber(block_, value, dst_type, output)); return Status::OK(); - } else if (TypeIRBuilder::IsNumber(type) && - TypeIRBuilder::IsDatePtr(value.GetType())) { - *output = NativeValue::CreateNull(type); } else { Status status; ::llvm::Value* output_value = nullptr; - CHECK_TRUE(UnSafeCastNumber(value.GetValue(&builder), type, - &output_value, status), - kCodegenError, status.msg); + CHECK_TRUE(UnSafeCastNumber(value.GetValue(&builder), dst_type, &output_value, status), kCodegenError, + status.msg); if (value.IsNullable()) { - *output = NativeValue::CreateWithFlag(output_value, - value.GetIsNull(&builder)); + *output = NativeValue::CreateWithFlag(output_value, value.GetIsNull(&builder)); } else { *output = NativeValue::Create(output_value); } diff --git a/hybridse/src/codegen/cast_expr_ir_builder.h b/hybridse/src/codegen/cast_expr_ir_builder.h index bb487ed1466..5adfca2bdcf 100644 --- a/hybridse/src/codegen/cast_expr_ir_builder.h +++ b/hybridse/src/codegen/cast_expr_ir_builder.h @@ -18,9 +18,6 @@ #define HYBRIDSE_SRC_CODEGEN_CAST_EXPR_IR_BUILDER_H_ #include "base/fe_status.h" #include "codegen/cond_select_ir_builder.h" -#include "codegen/scope_var.h" -#include "llvm/IR/IRBuilder.h" -#include "proto/fe_type.pb.h" namespace hybridse { namespace codegen { @@ -32,26 +29,19 @@ class CastExprIRBuilder { explicit CastExprIRBuilder(::llvm::BasicBlock* block); ~CastExprIRBuilder(); - Status Cast(const NativeValue& value, ::llvm::Type* cast_type, - NativeValue* output); // NOLINT - Status SafeCast(const NativeValue& value, ::llvm::Type* type, - NativeValue* output); // NOLINT - Status UnSafeCast(const NativeValue& value, ::llvm::Type* type, - NativeValue* output); // NOLINT + Status Cast(const NativeValue& value, ::llvm::Type* cast_type, NativeValue* output); + Status SafeCast(const NativeValue& value, ::llvm::Type* dst_type, NativeValue* output); + Status UnSafeCast(const NativeValue& value, ::llvm::Type* dst_type, NativeValue* output); static bool IsSafeCast(::llvm::Type* lhs, ::llvm::Type* rhs); - static Status InferNumberCastTypes(::llvm::Type* left_type, - ::llvm::Type* right_type); + static Status InferNumberCastTypes(::llvm::Type* left_type, ::llvm::Type* right_type); static bool IsIntFloat2PointerCast(::llvm::Type* src, ::llvm::Type* dist); bool BoolCast(llvm::Value* pValue, llvm::Value** pValue1, base::Status& status); // NOLINT - bool SafeCastNumber(::llvm::Value* value, ::llvm::Type* type, - ::llvm::Value** output, + bool SafeCastNumber(::llvm::Value* value, ::llvm::Type* type, ::llvm::Value** output, base::Status& status); // NOLINT - bool UnSafeCastNumber(::llvm::Value* value, ::llvm::Type* type, - ::llvm::Value** output, + bool UnSafeCastNumber(::llvm::Value* value, ::llvm::Type* type, ::llvm::Value** output, base::Status& status); // NOLINT - bool UnSafeCastDouble(::llvm::Value* value, ::llvm::Type* type, - ::llvm::Value** output, + bool UnSafeCastDouble(::llvm::Value* value, ::llvm::Type* type, ::llvm::Value** output, base::Status& status); // NOLINT private: diff --git a/hybridse/src/codegen/date_ir_builder.cc b/hybridse/src/codegen/date_ir_builder.cc index 65c439fd143..19bf319d7c3 100644 --- a/hybridse/src/codegen/date_ir_builder.cc +++ b/hybridse/src/codegen/date_ir_builder.cc @@ -19,6 +19,7 @@ #include #include "codegen/arithmetic_expr_ir_builder.h" #include "codegen/ir_base_builder.h" +#include "codegen/null_ir_builder.h" namespace hybridse { namespace codegen { @@ -43,6 +44,7 @@ void DateIRBuilder::InitStructType() { struct_type_ = stype; return; } + bool DateIRBuilder::CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) { return NewDate(block, output); @@ -123,11 +125,10 @@ base::Status DateIRBuilder::CastFrom(::llvm::BasicBlock* block, auto cast_func = m_->getOrInsertFunction( fn_name, ::llvm::FunctionType::get(builder.getVoidTy(), - {src.GetType(), dist->getType(), - builder.getInt1Ty()->getPointerTo()}, - false)); - builder.CreateCall(cast_func, - {src.GetValue(&builder), dist, is_null_ptr}); + {src.GetType(), dist->getType(), builder.getInt1Ty()->getPointerTo()}, false)); + + builder.CreateCall(cast_func, {src.GetValue(&builder), dist, is_null_ptr}); + ::llvm::Value* should_return_null = builder.CreateLoad(is_null_ptr); null_ir_builder.CheckAnyNull(block, src, &should_return_null); *output = NativeValue::CreateWithFlag(dist, should_return_null); diff --git a/hybridse/src/codegen/date_ir_builder.h b/hybridse/src/codegen/date_ir_builder.h index cb41dc5f263..d9004d48da1 100644 --- a/hybridse/src/codegen/date_ir_builder.h +++ b/hybridse/src/codegen/date_ir_builder.h @@ -16,13 +16,9 @@ #ifndef HYBRIDSE_SRC_CODEGEN_DATE_IR_BUILDER_H_ #define HYBRIDSE_SRC_CODEGEN_DATE_IR_BUILDER_H_ + #include "base/fe_status.h" -#include "codegen/cast_expr_ir_builder.h" -#include "codegen/null_ir_builder.h" -#include "codegen/scope_var.h" #include "codegen/struct_ir_builder.h" -#include "llvm/IR/IRBuilder.h" -#include "proto/fe_type.pb.h" namespace hybridse { namespace codegen { @@ -31,17 +27,15 @@ class DateIRBuilder : public StructTypeIRBuilder { public: explicit DateIRBuilder(::llvm::Module* m); ~DateIRBuilder(); - void InitStructType(); - bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output); + + void InitStructType() override; + bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) override; + bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist) override; + base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, NativeValue* output) override; + bool NewDate(::llvm::BasicBlock* block, ::llvm::Value** output); - bool NewDate(::llvm::BasicBlock* block, ::llvm::Value* date, - ::llvm::Value** output); - bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, - ::llvm::Value* dist); - base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, - NativeValue* output); - base::Status CastFrom(::llvm::BasicBlock* block, ::llvm::Value* src, - ::llvm::Value** output); + bool NewDate(::llvm::BasicBlock* block, ::llvm::Value* date, ::llvm::Value** output); + bool GetDate(::llvm::BasicBlock* block, ::llvm::Value* date, ::llvm::Value** output); bool SetDate(::llvm::BasicBlock* block, ::llvm::Value* date, diff --git a/hybridse/src/codegen/expr_ir_builder.cc b/hybridse/src/codegen/expr_ir_builder.cc index 1bccb6deef3..6b95bfb8ce1 100644 --- a/hybridse/src/codegen/expr_ir_builder.cc +++ b/hybridse/src/codegen/expr_ir_builder.cc @@ -26,10 +26,8 @@ #include "codegen/cond_select_ir_builder.h" #include "codegen/context.h" #include "codegen/date_ir_builder.h" -#include "codegen/fn_ir_builder.h" #include "codegen/ir_base_builder.h" #include "codegen/list_ir_builder.h" -#include "codegen/struct_ir_builder.h" #include "codegen/timestamp_ir_builder.h" #include "codegen/type_ir_builder.h" #include "codegen/udf_ir_builder.h" @@ -217,8 +215,7 @@ Status ExprIRBuilder::BuildConstExpr( ::llvm::IRBuilder<> builder(ctx_->GetCurrentBlock()); switch (const_node->GetDataType()) { case ::hybridse::node::kNull: { - *output = NativeValue::CreateNull( - llvm::Type::getTokenTy(builder.getContext())); + *output = NativeValue(nullptr, nullptr, llvm::Type::getTokenTy(builder.getContext())); break; } case ::hybridse::node::kBool: { diff --git a/hybridse/src/codegen/ir_base_builder.cc b/hybridse/src/codegen/ir_base_builder.cc index d1c7e153dd6..992d41d0998 100644 --- a/hybridse/src/codegen/ir_base_builder.cc +++ b/hybridse/src/codegen/ir_base_builder.cc @@ -17,7 +17,6 @@ #include "codegen/ir_base_builder.h" #include -#include #include #include @@ -625,21 +624,25 @@ bool GetBaseType(::llvm::Type* type, ::hybridse::node::DataType* output) { return false; } - if (pointee_ty->getStructName().startswith("fe.list_ref_")) { + auto struct_name = pointee_ty->getStructName(); + if (struct_name.startswith("fe.list_ref_")) { *output = hybridse::node::kList; return true; - } else if (pointee_ty->getStructName().startswith("fe.iterator_ref_")) { + } else if (struct_name.startswith("fe.iterator_ref_")) { *output = hybridse::node::kIterator; return true; - } else if (pointee_ty->getStructName().equals("fe.string_ref")) { + } else if (struct_name.equals("fe.string_ref")) { *output = hybridse::node::kVarchar; return true; - } else if (pointee_ty->getStructName().equals("fe.timestamp")) { + } else if (struct_name.equals("fe.timestamp")) { *output = hybridse::node::kTimestamp; return true; - } else if (pointee_ty->getStructName().equals("fe.date")) { + } else if (struct_name.equals("fe.date")) { *output = hybridse::node::kDate; return true; + } else if (struct_name.startswith("fe.array_")) { + *output = hybridse::node::kArray; + return true; } LOG(WARNING) << "no mapping pointee_ty for llvm pointee_ty " << pointee_ty->getStructName().str(); diff --git a/hybridse/src/codegen/ir_base_builder.h b/hybridse/src/codegen/ir_base_builder.h index c52bba23431..db2075289cf 100644 --- a/hybridse/src/codegen/ir_base_builder.h +++ b/hybridse/src/codegen/ir_base_builder.h @@ -19,7 +19,6 @@ #include #include -#include "glog/logging.h" #include "llvm/IR/IRBuilder.h" #include "node/sql_node.h" #include "node/type_node.h" diff --git a/hybridse/src/codegen/native_value.cc b/hybridse/src/codegen/native_value.cc index c4c6e2e562a..fce4f0bb5bb 100644 --- a/hybridse/src/codegen/native_value.cc +++ b/hybridse/src/codegen/native_value.cc @@ -17,7 +17,6 @@ #include "codegen/native_value.h" #include #include -#include #include "codegen/context.h" #include "codegen/ir_base_builder.h" diff --git a/hybridse/src/codegen/native_value.h b/hybridse/src/codegen/native_value.h index 52b0453c743..4bb756e3c3b 100644 --- a/hybridse/src/codegen/native_value.h +++ b/hybridse/src/codegen/native_value.h @@ -21,9 +21,7 @@ #include #include -#include "glog/logging.h" #include "llvm/IR/IRBuilder.h" -#include "llvm/IR/Module.h" namespace hybridse { namespace codegen { @@ -93,9 +91,9 @@ class NativeValue { NativeValue WithFlag(::llvm::Value*) const; NativeValue() : raw_(nullptr), flag_(nullptr), type_(nullptr) {} + NativeValue(::llvm::Value* raw, ::llvm::Value* flag, ::llvm::Type* type); private: - NativeValue(::llvm::Value* raw, ::llvm::Value* flag, ::llvm::Type* type); ::llvm::Value* raw_; ::llvm::Value* flag_; ::llvm::Type* type_; diff --git a/hybridse/src/codegen/predicate_expr_ir_builder.cc b/hybridse/src/codegen/predicate_expr_ir_builder.cc index aaf0fb0753c..45ed8f7ec21 100644 --- a/hybridse/src/codegen/predicate_expr_ir_builder.cc +++ b/hybridse/src/codegen/predicate_expr_ir_builder.cc @@ -17,6 +17,7 @@ #include "codegen/predicate_expr_ir_builder.h" #include "codegen/date_ir_builder.h" #include "codegen/ir_base_builder.h" +#include "codegen/null_ir_builder.h" #include "codegen/string_ir_builder.h" #include "codegen/timestamp_ir_builder.h" #include "codegen/type_ir_builder.h" diff --git a/hybridse/src/codegen/string_ir_builder.cc b/hybridse/src/codegen/string_ir_builder.cc index bb69f529f2b..8c41d326ee0 100644 --- a/hybridse/src/codegen/string_ir_builder.cc +++ b/hybridse/src/codegen/string_ir_builder.cc @@ -63,17 +63,7 @@ bool StringIRBuilder::CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) { return NewString(block, output); } -/// Create Const String Null -/// \param block -/// \param output -/// \return -base::Status StringIRBuilder::CreateNull(::llvm::BasicBlock* block, NativeValue* output) { - ::llvm::Value* value = nullptr; - CHECK_TRUE(NewString(block, &value), kCodegenError, "Fail to construct string") - ::llvm::IRBuilder<> builder(block); - *output = NativeValue::CreateWithFlag(value, builder.getInt1(true)); - return base::Status::OK(); -} + bool StringIRBuilder::NewString(::llvm::BasicBlock* block, ::llvm::Value** output) { if (!Create(block, output)) { diff --git a/hybridse/src/codegen/string_ir_builder.h b/hybridse/src/codegen/string_ir_builder.h index fb81872599a..84f73d2822d 100644 --- a/hybridse/src/codegen/string_ir_builder.h +++ b/hybridse/src/codegen/string_ir_builder.h @@ -16,14 +16,12 @@ #ifndef HYBRIDSE_SRC_CODEGEN_STRING_IR_BUILDER_H_ #define HYBRIDSE_SRC_CODEGEN_STRING_IR_BUILDER_H_ + #include #include + #include "base/fe_status.h" -#include "codegen/cast_expr_ir_builder.h" -#include "codegen/scope_var.h" #include "codegen/struct_ir_builder.h" -#include "llvm/IR/IRBuilder.h" -#include "proto/fe_type.pb.h" namespace hybridse { namespace codegen { @@ -32,16 +30,18 @@ class StringIRBuilder : public StructTypeIRBuilder { public: explicit StringIRBuilder(::llvm::Module* m); ~StringIRBuilder(); + void InitStructType() override; - bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output); - base::Status CreateNull(::llvm::BasicBlock* block, NativeValue* output); + bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) override; + bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist) override; + base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, NativeValue* output) override; + base::Status CastFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value** output); + bool NewString(::llvm::BasicBlock* block, ::llvm::Value** output); bool NewString(::llvm::BasicBlock* block, const std::string& str, ::llvm::Value** output); bool NewString(::llvm::BasicBlock* block, ::llvm::Value* size, ::llvm::Value* data, ::llvm::Value** output); - bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, - ::llvm::Value* dist); bool GetSize(::llvm::BasicBlock* block, ::llvm::Value* str, ::llvm::Value** output); bool SetSize(::llvm::BasicBlock* block, ::llvm::Value* str, @@ -50,8 +50,6 @@ class StringIRBuilder : public StructTypeIRBuilder { ::llvm::Value** output); bool SetData(::llvm::BasicBlock* block, ::llvm::Value* str, ::llvm::Value* data); - base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, - NativeValue* output); base::Status Compare(::llvm::BasicBlock* block, const NativeValue& s1, const NativeValue& s2, NativeValue* output); @@ -62,8 +60,6 @@ class StringIRBuilder : public StructTypeIRBuilder { const std::vector& strs, NativeValue* output); - base::Status CastFrom(::llvm::BasicBlock* block, ::llvm::Value* src, - ::llvm::Value** output); base::Status CastToNumber(::llvm::BasicBlock* block, const NativeValue& src, ::llvm::Type* type, NativeValue* output); }; diff --git a/hybridse/src/codegen/struct_ir_builder.cc b/hybridse/src/codegen/struct_ir_builder.cc index 3a8e3336936..7adfb5d950f 100644 --- a/hybridse/src/codegen/struct_ir_builder.cc +++ b/hybridse/src/codegen/struct_ir_builder.cc @@ -25,17 +25,14 @@ StructTypeIRBuilder::StructTypeIRBuilder(::llvm::Module* m) : TypeIRBuilder(), m_(m), struct_type_(nullptr) {} StructTypeIRBuilder::~StructTypeIRBuilder() {} -bool StructTypeIRBuilder::StructCopyFrom(::llvm::BasicBlock* block, - ::llvm::Value* src, - ::llvm::Value* dist) { - StructTypeIRBuilder* struct_builder = - CreateStructTypeIRBuilder(block->getModule(), src->getType()); +bool StructTypeIRBuilder::StructCopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist) { + StructTypeIRBuilder* struct_builder = CreateStructTypeIRBuilder(block->getModule(), src->getType()); bool ok = struct_builder->CopyFrom(block, src, dist); delete struct_builder; return ok; } -StructTypeIRBuilder* StructTypeIRBuilder::CreateStructTypeIRBuilder( - ::llvm::Module* m, ::llvm::Type* type) { + +StructTypeIRBuilder* StructTypeIRBuilder::CreateStructTypeIRBuilder(::llvm::Module* m, ::llvm::Type* type) { node::DataType base_type; if (!GetBaseType(type, &base_type)) { return nullptr; @@ -49,14 +46,24 @@ StructTypeIRBuilder* StructTypeIRBuilder::CreateStructTypeIRBuilder( case node::kVarchar: return new StringIRBuilder(m); default: { - LOG(WARNING) << "fail to create struct type ir builder for " - << DataTypeName(base_type); + LOG(WARNING) << "fail to create struct type ir builder for " << DataTypeName(base_type); return nullptr; } } return nullptr; } + +absl::StatusOr StructTypeIRBuilder::CreateNull(::llvm::BasicBlock* block) { + ::llvm::Value* value = nullptr; + if (!CreateDefault(block, &value)) { + return absl::InternalError(absl::StrCat("fail to construct ", GetLlvmObjectString(GetType()))); + } + ::llvm::IRBuilder<> builder(block); + return NativeValue::CreateWithFlag(value, builder.getInt1(true)); +} + ::llvm::Type* StructTypeIRBuilder::GetType() { return struct_type_; } + bool StructTypeIRBuilder::Create(::llvm::BasicBlock* block, ::llvm::Value** output) const { if (block == NULL || output == NULL) { diff --git a/hybridse/src/codegen/struct_ir_builder.h b/hybridse/src/codegen/struct_ir_builder.h index 2f1f94d036c..e197665855b 100644 --- a/hybridse/src/codegen/struct_ir_builder.h +++ b/hybridse/src/codegen/struct_ir_builder.h @@ -16,12 +16,11 @@ #ifndef HYBRIDSE_SRC_CODEGEN_STRUCT_IR_BUILDER_H_ #define HYBRIDSE_SRC_CODEGEN_STRUCT_IR_BUILDER_H_ + +#include "absl/status/statusor.h" #include "base/fe_status.h" -#include "codegen/cast_expr_ir_builder.h" -#include "codegen/scope_var.h" +#include "codegen/native_value.h" #include "codegen/type_ir_builder.h" -#include "llvm/IR/IRBuilder.h" -#include "proto/fe_type.pb.h" namespace hybridse { namespace codegen { @@ -30,15 +29,18 @@ class StructTypeIRBuilder : public TypeIRBuilder { public: explicit StructTypeIRBuilder(::llvm::Module*); ~StructTypeIRBuilder(); - static StructTypeIRBuilder* CreateStructTypeIRBuilder(::llvm::Module*, - ::llvm::Type*); - static bool StructCopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, - ::llvm::Value* dist); + + static StructTypeIRBuilder* CreateStructTypeIRBuilder(::llvm::Module*, ::llvm::Type*); + static bool StructCopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist); + virtual void InitStructType() = 0; + virtual bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist) = 0; + virtual base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, NativeValue* output) = 0; + virtual bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output) = 0; + + absl::StatusOr CreateNull(::llvm::BasicBlock* block); ::llvm::Type* GetType(); bool Create(::llvm::BasicBlock* block, ::llvm::Value** output) const; - virtual bool CreateDefault(::llvm::BasicBlock* block, - ::llvm::Value** output) = 0; // Load the 'idx' th field into ''*output' // NOTE: not all types are loaded correctly, e.g for array type @@ -48,12 +50,6 @@ class StructTypeIRBuilder : public TypeIRBuilder { // Get the address of 'idx' th field bool Get(::llvm::BasicBlock* block, ::llvm::Value* struct_value, unsigned int idx, ::llvm::Value** output) const; - virtual bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, - ::llvm::Value* dist) = 0; - virtual base::Status CastFrom(::llvm::BasicBlock* block, - const NativeValue& src, - NativeValue* output) = 0; - protected: ::llvm::Module* m_; ::llvm::Type* struct_type_; diff --git a/hybridse/src/codegen/timestamp_ir_builder.cc b/hybridse/src/codegen/timestamp_ir_builder.cc index 13d6e065f39..c3a8054e1cd 100644 --- a/hybridse/src/codegen/timestamp_ir_builder.cc +++ b/hybridse/src/codegen/timestamp_ir_builder.cc @@ -15,14 +15,15 @@ */ #include "codegen/timestamp_ir_builder.h" + #include #include + #include "codegen/arithmetic_expr_ir_builder.h" #include "codegen/ir_base_builder.h" #include "codegen/null_ir_builder.h" #include "codegen/predicate_expr_ir_builder.h" #include "glog/logging.h" -#include "node/sql_node.h" using hybridse::common::kCodegenError; @@ -43,9 +44,7 @@ void TimestampIRBuilder::InitStructType() { return; } stype = ::llvm::StructType::create(m_->getContext(), name); - ::llvm::Type* ts_ty = (::llvm::Type::getInt64Ty(m_->getContext())); - std::vector<::llvm::Type*> elements; - elements.push_back(ts_ty); + std::vector<::llvm::Type*> elements = {::llvm::Type::getInt64Ty(m_->getContext())}; stype->setBody(::llvm::ArrayRef<::llvm::Type*>(elements)); struct_type_ = stype; return; @@ -60,39 +59,36 @@ base::Status TimestampIRBuilder::CastFrom(::llvm::BasicBlock* block, return Status::OK(); } - if (src.IsConstNull()) { - *output = NativeValue::CreateNull(GetType()); - return Status::OK(); - } ::llvm::IRBuilder<> builder(block); NativeValue ts; CastExprIRBuilder cast_builder(block); CondSelectIRBuilder cond_ir_builder; PredicateIRBuilder predicate_ir_builder(block); NullIRBuilder null_ir_builder; + + // always allocate for returned timestmap even it is null + ::llvm::Value* dist = nullptr; + if (!CreateDefault(block, &dist)) { + status.code = common::kCodegenError; + status.msg = "Fail to cast date: create default date fail"; + return status; + } + if (IsNumber(src.GetType())) { CHECK_STATUS(cast_builder.Cast(src, builder.getInt64Ty(), &ts)); NativeValue cond; CHECK_STATUS(predicate_ir_builder.BuildGeExpr( ts, NativeValue::Create(builder.getInt64(0)), &cond)); - ::llvm::Value* timestamp; - CHECK_TRUE(NewTimestamp(block, ts.GetValue(&builder), ×tamp), + CHECK_TRUE(SetTs(block, dist, ts.GetValue(&builder)), kCodegenError, "Fail to cast timestamp: new timestamp(ts) fail"); - CHECK_STATUS( - cond_ir_builder.Select(block, cond, NativeValue::Create(timestamp), - NativeValue::CreateNull(GetType()), output)); + CHECK_STATUS(cond_ir_builder.Select(block, cond, NativeValue::Create(dist), + NativeValue::CreateWithFlag(dist, builder.getInt1(true)), output)); } else if (IsStringPtr(src.GetType()) || IsDatePtr(src.GetType())) { ::llvm::IRBuilder<> builder(block); - ::llvm::Value* dist = nullptr; ::llvm::Value* is_null_ptr = CreateAllocaAtHead( &builder, builder.getInt1Ty(), "timestamp_is_null_alloca"); - if (!CreateDefault(block, &dist)) { - status.code = common::kCodegenError; - status.msg = "Fail to cast date: create default date fail"; - return status; - } ::std::string fn_name = "timestamp." + TypeName(src.GetType()); auto cast_func = m_->getOrInsertFunction( diff --git a/hybridse/src/codegen/timestamp_ir_builder.h b/hybridse/src/codegen/timestamp_ir_builder.h index 33de3cce2e5..84051979597 100644 --- a/hybridse/src/codegen/timestamp_ir_builder.h +++ b/hybridse/src/codegen/timestamp_ir_builder.h @@ -16,12 +16,9 @@ #ifndef HYBRIDSE_SRC_CODEGEN_TIMESTAMP_IR_BUILDER_H_ #define HYBRIDSE_SRC_CODEGEN_TIMESTAMP_IR_BUILDER_H_ + #include "base/fe_status.h" -#include "codegen/cast_expr_ir_builder.h" -#include "codegen/scope_var.h" #include "codegen/struct_ir_builder.h" -#include "llvm/IR/IRBuilder.h" -#include "proto/fe_type.pb.h" namespace hybridse { namespace codegen { @@ -33,8 +30,8 @@ class TimestampIRBuilder : public StructTypeIRBuilder { void InitStructType(); bool CreateDefault(::llvm::BasicBlock* block, ::llvm::Value** output); bool NewTimestamp(::llvm::BasicBlock* block, ::llvm::Value** output); - bool NewTimestamp(::llvm::BasicBlock* block, ::llvm::Value* ts, - ::llvm::Value** output); + bool NewTimestamp(::llvm::BasicBlock* block, ::llvm::Value* ts, ::llvm::Value** output); + bool CopyFrom(::llvm::BasicBlock* block, ::llvm::Value* src, ::llvm::Value* dist); base::Status CastFrom(::llvm::BasicBlock* block, const NativeValue& src, diff --git a/hybridse/src/codegen/type_ir_builder.cc b/hybridse/src/codegen/type_ir_builder.cc index 3fcd5891c4c..07adfb21855 100644 --- a/hybridse/src/codegen/type_ir_builder.cc +++ b/hybridse/src/codegen/type_ir_builder.cc @@ -15,8 +15,12 @@ */ #include "codegen/type_ir_builder.h" + +#include "absl/status/status.h" +#include "codegen/date_ir_builder.h" #include "codegen/ir_base_builder.h" -#include "glog/logging.h" +#include "codegen/string_ir_builder.h" +#include "codegen/timestamp_ir_builder.h" #include "node/node_manager.h" namespace hybridse { @@ -101,13 +105,7 @@ bool TypeIRBuilder::IsStringPtr(::llvm::Type* type) { bool TypeIRBuilder::IsStructPtr(::llvm::Type* type) { if (type->getTypeID() == ::llvm::Type::PointerTyID) { type = reinterpret_cast<::llvm::PointerType*>(type)->getElementType(); - if (type->isStructTy()) { - DLOG(INFO) << "Struct Name " << type->getStructName().str(); - return true; - } else { - DLOG(INFO) << "Isn't Struct Type"; - return false; - } + return type->isStructTy(); } return false; } @@ -138,5 +136,37 @@ base::Status TypeIRBuilder::BinaryOpTypeInfer( return base::Status::OK(); } +absl::StatusOr CreateSafeNull(::llvm::BasicBlock* block, ::llvm::Type* type) { + node::DataType data_type; + if (!GetBaseType(type, &data_type)) { + return absl::InvalidArgumentError(absl::StrCat("can't get base type for: ", GetLlvmObjectString(type))); + } + + if (TypeIRBuilder::IsStructPtr(type)) { + std::unique_ptr builder = nullptr; + + switch (data_type) { + case node::DataType::kTimestamp: { + builder.reset(new TimestampIRBuilder(block->getModule())); + break; + } + case node::DataType::kDate: { + builder.reset(new DateIRBuilder(block->getModule())); + break; + } + case node::DataType::kVarchar: { + builder.reset(new StringIRBuilder(block->getModule())); + break; + } + default: + return absl::InvalidArgumentError(absl::StrCat("invalid struct type: ", GetLlvmObjectString(type))); + } + + return builder->CreateNull(block); + } + + return NativeValue(nullptr, nullptr, type); +} + } // namespace codegen } // namespace hybridse diff --git a/hybridse/src/codegen/type_ir_builder.h b/hybridse/src/codegen/type_ir_builder.h index e06e77244e6..e68d7f0233b 100644 --- a/hybridse/src/codegen/type_ir_builder.h +++ b/hybridse/src/codegen/type_ir_builder.h @@ -18,11 +18,12 @@ #define HYBRIDSE_SRC_CODEGEN_TYPE_IR_BUILDER_H_ #include -#include + +#include "absl/status/statusor.h" #include "base/fe_status.h" -#include "codec/fe_row_codec.h" -#include "codegen/ir_base_builder.h" -#include "node/node_enum.h" +#include "codegen/native_value.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Type.h" #include "node/sql_node.h" #include "node/type_node.h" @@ -91,6 +92,10 @@ class BoolIRBuilder : public TypeIRBuilder { } }; +// construct a safe null value for type +// returns NativeValue{raw, is_null=true} on success, raw is ensured to be not nullptr +absl::StatusOr CreateSafeNull(::llvm::BasicBlock* block, ::llvm::Type* type); + } // namespace codegen } // namespace hybridse #endif // HYBRIDSE_SRC_CODEGEN_TYPE_IR_BUILDER_H_ diff --git a/hybridse/src/codegen/udf_ir_builder.cc b/hybridse/src/codegen/udf_ir_builder.cc index 6d6f967a83e..5030f3cd8ae 100644 --- a/hybridse/src/codegen/udf_ir_builder.cc +++ b/hybridse/src/codegen/udf_ir_builder.cc @@ -15,19 +15,17 @@ */ #include "codegen/udf_ir_builder.h" -#include -#include + #include + #include "codegen/context.h" -#include "codegen/date_ir_builder.h" #include "codegen/fn_ir_builder.h" +#include "codegen/ir_base_builder.h" #include "codegen/list_ir_builder.h" #include "codegen/null_ir_builder.h" -#include "codegen/timestamp_ir_builder.h" +#include "codegen/type_ir_builder.h" #include "llvm/IR/Attributes.h" -#include "node/node_manager.h" #include "node/sql_node.h" -#include "udf/udf.h" #include "udf/udf_registry.h" using ::hybridse::common::kCodegenError; @@ -162,7 +160,7 @@ Status UdfIRBuilder::BuildLambdaCall( Status UdfIRBuilder::BuildCodeGenUdfCall( const node::UdfByCodeGenDefNode* fn, const std::vector& args, NativeValue* output) { - auto gen_impl = fn->GetGenImpl(); + std::shared_ptr gen_impl = fn->GetGenImpl(); ::llvm::Value* ret_null = nullptr; for (size_t i = 0; i < fn->GetArgSize(); ++i) { diff --git a/hybridse/src/codegen/udf_ir_builder.h b/hybridse/src/codegen/udf_ir_builder.h index ed15b6432c7..9e33837bf96 100644 --- a/hybridse/src/codegen/udf_ir_builder.h +++ b/hybridse/src/codegen/udf_ir_builder.h @@ -17,13 +17,9 @@ #ifndef HYBRIDSE_SRC_CODEGEN_UDF_IR_BUILDER_H_ #define HYBRIDSE_SRC_CODEGEN_UDF_IR_BUILDER_H_ -#include -#include #include #include "base/fe_status.h" #include "codegen/expr_ir_builder.h" -#include "codegen/scope_var.h" -#include "llvm/IR/Module.h" #include "node/sql_node.h" namespace hybridse { diff --git a/hybridse/src/codegen/udf_ir_builder_test.cc b/hybridse/src/codegen/udf_ir_builder_test.cc index 13a82a1a925..6cd82be7859 100644 --- a/hybridse/src/codegen/udf_ir_builder_test.cc +++ b/hybridse/src/codegen/udf_ir_builder_test.cc @@ -1078,14 +1078,21 @@ TEST_F(UdfIRBuilderTest, DateDiff) { CheckUdf, Nullable, Nullable>(func_name, 44924, "2022-12-31", "1900-01-01"); CheckUdf, Nullable, Nullable>(func_name, 50, "20220620", "2022-05-01 11:11:11"); - CheckUdf, Nullable, Nullable>(func_name, 0, "2022-05-01", "20220501"); + CheckUdf, Nullable, Nullable>(func_name, 0, + "2022-05-01", "20220501"); CheckUdf, Nullable, Nullable>(func_name, nullptr, "2022-02-29", "20220501"); - CheckUdf, Nullable, Nullable>(func_name, nullptr, "1899-05-20", - "2020-05-20"); + CheckUdf, Nullable, Nullable>(func_name, 9, "1899-05-20", "1899-05-11"); CheckUdf, Nullable, Nullable>(func_name, nullptr, "2022-05-40", "2020-05-20"); - CheckUdf, Nullable, Nullable>(func_name, nullptr, "2020-05-20", - "1899-05-20"); + CheckUdf, Nullable, Nullable>(func_name, -30, "1199-10-12", "1199-11-11"); + // rfc3399 full format + CheckUdf, Nullable, Nullable>( + func_name, 20, "2000-01-01t00:12:00.1+08:00", "1999-12-12T12:12:12+08:00"); + CheckUdf, Nullable, Nullable>( + func_name, 19, "2000-01-01t00:12:00.1+08:00", "1999-12-12T20:12:12Z"); + CheckUdf, Nullable, Nullable>( + func_name, 20, "2000-01-01t06:12:00.1+08:00", "1999-12-12T12:12:12Z"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, nullptr, "20220501"); CheckUdf, Nullable, Nullable>(func_name, nullptr, "2022-05-01", nullptr); CheckUdf, Nullable, Nullable>(func_name, nullptr, nullptr, nullptr); @@ -1093,6 +1100,8 @@ TEST_F(UdfIRBuilderTest, DateDiff) { // mix types CheckUdf, Nullable, Nullable>(func_name, -19, "2022-05-01", Date(2022, 5, 20)); CheckUdf, Nullable, Nullable>(func_name, 19, Date(2022, 5, 20), "2022-05-01"); + CheckUdf, Nullable, Nullable>(func_name, 3, Date(1900, 1, 1), "1899-12-29"); + CheckUdf, Nullable, Nullable>(func_name, -3, "1899-12-29", Date(1900, 1, 1)); CheckUdf, Nullable, Nullable>(func_name, nullptr, nullptr, "2022-05-01"); CheckUdf, Nullable, Nullable>(func_name, nullptr, Date(2022, 5, 20), nullptr); CheckUdf, Nullable, Nullable>(func_name, nullptr, nullptr, nullptr); @@ -1101,6 +1110,29 @@ TEST_F(UdfIRBuilderTest, DateDiff) { CheckUdf, Nullable, Nullable>(func_name, nullptr, nullptr, nullptr); } +TEST_F(UdfIRBuilderTest, DateDiffNull) { + auto func_name = "datediff"; + + // out-of-range format + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1900-01-00", + "1999-12-12T12:12:12Z"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1977-13-01", + "1999-12-12T12:12:12Z"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, "19771232", + "1999-12-12T12:12:12Z"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1999-12-12T25:12:12Z", + "1999-12-12T12:12:12Z"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1999-12-12T12:66:12Z", + "1999-12-12T12:12:12Z"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1999-12-12T12:00:61Z", + "1999-12-12T12:12:12Z"); + + // invalid format + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1999-12-12T12:12:12Z", + "202 2-12-2 9"); + CheckUdf, Nullable, Nullable>(func_name, nullptr, "1999-12-12T12:12:12Z", + "12:30:30"); +} class UdfIRCastTest : public ::testing::TestWithParam>> {}; diff --git a/hybridse/src/node/node_manager.cc b/hybridse/src/node/node_manager.cc index 3c5b8ffd36a..f60ba20d6b2 100644 --- a/hybridse/src/node/node_manager.cc +++ b/hybridse/src/node/node_manager.cc @@ -301,7 +301,7 @@ OrderExpression *NodeManager::MakeOrderExpression(const ExprNode *expr, const bo OrderExpression *node_ptr = new OrderExpression(expr, is_asc); return RegisterNode(node_ptr); } -OrderByNode *NodeManager::MakeOrderByNode(const ExprListNode *order_expressions) { +OrderByNode *NodeManager::MakeOrderByNode(ExprListNode *order_expressions) { OrderByNode *node_ptr = new OrderByNode(order_expressions); return RegisterNode(node_ptr); } @@ -1031,11 +1031,6 @@ SqlNode *NodeManager::MakeReplicaNumNode(int num) { return RegisterNode(node_ptr); } -SqlNode *NodeManager::MakeStorageModeNode(StorageMode storage_mode) { - SqlNode *node_ptr = new StorageModeNode(storage_mode); - return RegisterNode(node_ptr); -} - SqlNode *NodeManager::MakePartitionNumNode(int num) { SqlNode *node_ptr = new PartitionNumNode(num); return RegisterNode(node_ptr); diff --git a/hybridse/src/node/plan_node_test.cc b/hybridse/src/node/plan_node_test.cc index 4f0d55d0166..5ffb76142a7 100644 --- a/hybridse/src/node/plan_node_test.cc +++ b/hybridse/src/node/plan_node_test.cc @@ -239,7 +239,8 @@ TEST_F(PlanNodeTest, ExtractColumnsAndIndexsTest) { manager_->MakeColumnDescNode("col3", node::kFloat, true), manager_->MakeColumnDescNode("col4", node::kVarchar, true), manager_->MakeColumnDescNode("col5", node::kTimestamp, true), index_node}, - {manager_->MakeReplicaNumNode(3), manager_->MakePartitionNumNode(8), manager_->MakeStorageModeNode(kMemory)}, + {manager_->MakeReplicaNumNode(3), manager_->MakePartitionNumNode(8), + manager_->MakeNode(kMemory)}, false); ASSERT_TRUE(nullptr != node); std::vector columns; diff --git a/hybridse/src/node/sql_node.cc b/hybridse/src/node/sql_node.cc index bc5aec55cf9..a0e8e0bec8f 100644 --- a/hybridse/src/node/sql_node.cc +++ b/hybridse/src/node/sql_node.cc @@ -58,6 +58,7 @@ static absl::flat_hash_map CreateCmdTypeNamesMap() { {CmdType::kCmdDropTable, "drop table"}, {CmdType::kCmdShowProcedures, "show procedures"}, {CmdType::kCmdShowCreateSp, "show create procedure"}, + {CmdType::kCmdShowCreateTable, "show create table"}, {CmdType::kCmdDropSp, "drop procedure"}, {CmdType::kCmdDropIndex, "drop index"}, {CmdType::kCmdExit, "exit"}, @@ -75,6 +76,7 @@ static absl::flat_hash_map CreateCmdTypeNamesMap() { {CmdType::kCmdDropFunction, "drop function"}, {CmdType::kCmdShowFunctions, "show functions"}, {CmdType::kCmdShowJobLog, "show joblog"}, + {CmdType::kCmdTruncate, "truncate table"}, }; for (auto kind = 0; kind < CmdType::kLastCmd; ++kind) { DCHECK(map.find(static_cast(kind)) != map.end()); @@ -1167,6 +1169,7 @@ static absl::flat_hash_map CreateSqlNodeTypeToNa {kReplicaNum, "kReplicaNum"}, {kPartitionNum, "kPartitionNum"}, {kStorageMode, "kStorageMode"}, + {kCompressType, "kCompressType"}, {kFn, "kFn"}, {kFnParaList, "kFnParaList"}, {kCreateSpStmt, "kCreateSpStmt"}, @@ -2099,6 +2102,11 @@ void FrameBound::Print(std::ostream &output, const std::string &org_tab) const { } } +bool FrameBound::is_offset_bound() const { + return bound_type_ == kPreceding || bound_type_ == kOpenPreceding || bound_type_ == kFollowing || + bound_type_ == kOpenFollowing; +} + int FrameBound::Compare(const FrameBound *bound1, const FrameBound *bound2) { if (SqlEquals(bound1, bound2)) { return 0; @@ -2597,6 +2605,17 @@ void StorageModeNode::Print(std::ostream &output, const std::string &org_tab) co PrintValue(output, tab, StorageModeName(storage_mode_), "storage_mode", true); } +void CompressTypeNode::Print(std::ostream &output, const std::string &org_tab) const { + SqlNode::Print(output, org_tab); + const std::string tab = org_tab + INDENT + SPACE_ED; + output << "\n"; + if (compress_type_ == CompressType::kSnappy) { + PrintValue(output, tab, "snappy", "compress_type", true); + } else { + PrintValue(output, tab, "nocompress", "compress_type", true); + } +} + void PartitionNumNode::Print(std::ostream &output, const std::string &org_tab) const { SqlNode::Print(output, org_tab); const std::string tab = org_tab + INDENT + SPACE_ED; diff --git a/hybridse/src/node/sql_node_test.cc b/hybridse/src/node/sql_node_test.cc index 545d9b647fd..227cb80dcea 100644 --- a/hybridse/src/node/sql_node_test.cc +++ b/hybridse/src/node/sql_node_test.cc @@ -676,7 +676,7 @@ TEST_F(SqlNodeTest, CreateIndexNodeTest) { node_manager_->MakeColumnDescNode("col4", node::kVarchar, true), node_manager_->MakeColumnDescNode("col5", node::kTimestamp, true), index_node}, {node_manager_->MakeReplicaNumNode(3), node_manager_->MakePartitionNumNode(8), - node_manager_->MakeStorageModeNode(kMemory)}, + node_manager_->MakeNode(kMemory)}, false); ASSERT_TRUE(nullptr != node); std::vector columns; diff --git a/hybridse/src/passes/physical/batch_request_optimize.cc b/hybridse/src/passes/physical/batch_request_optimize.cc index 52488e6a981..86fdfee92c5 100644 --- a/hybridse/src/passes/physical/batch_request_optimize.cc +++ b/hybridse/src/passes/physical/batch_request_optimize.cc @@ -269,6 +269,7 @@ static Status UpdateProjectExpr( return replacer.Replace(expr->DeepCopy(ctx->node_manager()), output); } +// simplify simple project, remove orphan descendant producer nodes static Status CreateSimplifiedProject(PhysicalPlanContext* ctx, PhysicalOpNode* input, const ColumnProjects& projects, @@ -279,8 +280,7 @@ static Status CreateSimplifiedProject(PhysicalPlanContext* ctx, can_project = false; for (size_t i = 0; i < cur_input->producers().size(); ++i) { auto cand_input = cur_input->GetProducer(i); - if (cand_input->GetOutputType() != - PhysicalSchemaType::kSchemaTypeRow) { + if (cand_input->GetOutputType() != PhysicalSchemaType::kSchemaTypeRow) { continue; } bool is_valid = true; @@ -949,21 +949,16 @@ Status CommonColumnOptimize::ProcessJoin(PhysicalPlanContext* ctx, } } else if (is_non_common_join) { // join only depend on non-common left part - if (left_state->non_common_op == join_op->GetProducer(0) && - right == join_op->GetProducer(1)) { + if (left_state->non_common_op == join_op->GetProducer(0) && right == join_op->GetProducer(1)) { state->common_op = nullptr; state->non_common_op = join_op; } else { PhysicalRequestJoinNode* new_join = nullptr; - CHECK_STATUS(ctx->CreateOp( - &new_join, left_state->non_common_op, right, join_op->join(), - join_op->output_right_only())); - CHECK_STATUS(ReplaceComponentExpr( - join_op->join(), join_op->joined_schemas_ctx(), - new_join->joined_schemas_ctx(), ctx->node_manager(), - &new_join->join_)); - state->common_op = - join_op->output_right_only() ? nullptr : left_state->common_op; + CHECK_STATUS(ctx->CreateOp(&new_join, left_state->non_common_op, right, + join_op->join(), join_op->output_right_only())); + CHECK_STATUS(ReplaceComponentExpr(join_op->join(), join_op->joined_schemas_ctx(), + new_join->joined_schemas_ctx(), ctx->node_manager(), &new_join->join_)); + state->common_op = join_op->output_right_only() ? nullptr : left_state->common_op; state->non_common_op = new_join; if (!join_op->output_right_only()) { for (size_t left_idx : left_state->common_column_indices) { diff --git a/hybridse/src/passes/physical/batch_request_optimize_test.cc b/hybridse/src/passes/physical/batch_request_optimize_test.cc index e53b7c377e2..48259b68ed4 100644 --- a/hybridse/src/passes/physical/batch_request_optimize_test.cc +++ b/hybridse/src/passes/physical/batch_request_optimize_test.cc @@ -54,6 +54,9 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( BatchRequestLastJoinQuery, BatchRequestOptimizeTest, testing::ValuesIn(sqlcase::InitCases("cases/query/last_join_query.yaml"))); +INSTANTIATE_TEST_SUITE_P( + BatchRequestLeftJoin, BatchRequestOptimizeTest, + testing::ValuesIn(sqlcase::InitCases("cases/query/left_join.yml"))); INSTANTIATE_TEST_SUITE_P( BatchRequestLastJoinWindowQuery, BatchRequestOptimizeTest, testing::ValuesIn(sqlcase::InitCases("cases/query/last_join_window_query.yaml"))); diff --git a/hybridse/src/passes/physical/cluster_optimized.cc b/hybridse/src/passes/physical/cluster_optimized.cc index d17928742dc..ae53f588930 100644 --- a/hybridse/src/passes/physical/cluster_optimized.cc +++ b/hybridse/src/passes/physical/cluster_optimized.cc @@ -41,7 +41,7 @@ bool ClusterOptimized::SimplifyJoinLeftInput( for (auto column : columns) { oss << node::ExprString(column) << ","; } - LOG(INFO) << "join resolved related columns: \n" << oss.str(); + LOG(INFO) << "join resolved related columns: " << oss.str(); // find columns belong to left side std::vector left_indices; @@ -116,7 +116,7 @@ bool ClusterOptimized::SimplifyJoinLeftInput( << status; return false; } - DLOG(INFO) << "apply root node simplify!"; + DLOG(INFO) << "apply root node simplify: " << root_simplify_project_op->GetTreeString(); *output = root_simplify_project_op; return true; } @@ -134,10 +134,13 @@ bool ClusterOptimized::Transform(PhysicalOpNode* in, PhysicalOpNode** output) { case node::kJoinTypeLast: { auto left = join_op->producers()[0]; auto right = join_op->producers()[1]; - if (vm::PhysicalSchemaType::kSchemaTypeRow == - right->GetOutputType()) { - DLOG(INFO) - << "request join optimized skip: row and row join"; + if (vm::PhysicalSchemaType::kSchemaTypeRow != left->GetOutputType()) { + DLOG(INFO) << "request join optimized skip: left source is not a row"; + return false; + } + + if (vm::PhysicalSchemaType::kSchemaTypeRow == right->GetOutputType()) { + DLOG(INFO) << "request join optimized skip: row and row join"; return false; } auto simplify_left = left; diff --git a/hybridse/src/passes/physical/group_and_sort_optimized.cc b/hybridse/src/passes/physical/group_and_sort_optimized.cc index 2c10529fcc7..2d51b336167 100644 --- a/hybridse/src/passes/physical/group_and_sort_optimized.cc +++ b/hybridse/src/passes/physical/group_and_sort_optimized.cc @@ -15,16 +15,17 @@ */ #include "passes/physical/group_and_sort_optimized.h" -#include #include #include -#include #include #include #include #include +#include "absl/cleanup/cleanup.h" +#include "absl/status/status.h" #include "absl/strings/string_view.h" +#include "node/node_enum.h" #include "vm/physical_op.h" namespace hybridse { @@ -47,9 +48,47 @@ using hybridse::vm::PhysicalSimpleProjectNode; using hybridse::vm::PhysicalWindowAggrerationNode; using hybridse::vm::ProjectType; -static bool ResolveColumnToSourceColumnName(const node::ColumnRefNode* col, - const SchemasContext* schemas_ctx, - std::string* source_name); +static bool ResolveColumnToSourceColumnName(const node::ColumnRefNode* col, const SchemasContext* schemas_ctx, + std::string* db, std::string* table, std::string* source_col); + +// ExprNode may be resolving under different SchemasContext later (say one of its descendants context), +// with column name etc it may not able to resvole since a column rename may happen in SimpleProject node. +// With the column id hint written to corresponding ColumnRefNode earlier, resolving issue can be mitigated. +absl::Status GroupAndSortOptimized::BuildExprCache(const node::ExprNode* node, const SchemasContext* sc) { + if (node == nullptr) { + return {}; + } + + switch (node->GetExprType()) { + case node::kExprColumnRef: { + auto ref = dynamic_cast(node); + + if (expr_cache_.find(ref) != expr_cache_.end()) { + break; + } + + std::string source_col; + std::string source_db; + std::string source_table; + if (!ResolveColumnToSourceColumnName(ref, sc, &source_db, &source_table, &source_col)) { + return absl::InternalError(absl::StrCat("unable to resolve ", ref->GetExprString())); + } + + expr_cache_.emplace(ref, SrcColInfo{source_col, source_table, source_db}); + break; + } + default: + break; + } + + for (uint32_t i = 0; i < node->GetChildNum(); ++i) { + auto s = BuildExprCache(node->GetChild(i), sc); + if (!s.ok()) { + return s; + } + } + return {}; +} bool GroupAndSortOptimized::Transform(PhysicalOpNode* in, PhysicalOpNode** output) { @@ -239,6 +278,49 @@ bool GroupAndSortOptimized::Transform(PhysicalOpNode* in, return false; } +bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx, + PhysicalOpNode* in, + Key* left_key, + Key* index_key, + Key* right_key, + Sort* sort, + PhysicalOpNode** new_in) { + if (nullptr == left_key || nullptr == index_key || !left_key->ValidKey()) { + return false; + } + + if (right_key != nullptr && !right_key->ValidKey()) { + return false; + } + + absl::Cleanup clean = [&]() { + expr_cache_.clear(); + optimize_info_ = nullptr; + }; + + auto s = BuildExprCache(left_key->keys(), root_schemas_ctx); + if (!s.ok()) { + return false; + } + s = BuildExprCache(index_key->keys(), root_schemas_ctx); + if (!s.ok()) { + return false; + } + if (right_key != nullptr) { + s = BuildExprCache(right_key->keys(), root_schemas_ctx); + if (!s.ok()) { + return false; + } + } + if (sort != nullptr) { + s = BuildExprCache(sort->orders(), root_schemas_ctx); + if (!s.ok()) { + return false; + } + } + return KeysOptimizedImpl(root_schemas_ctx, in, left_key, index_key, right_key, sort, new_in); +} + /** * optimize keys on condition. Remove keys from upper node if key match indexes * defined in table schema `left_key` & `index_key` is required, `right_key` is @@ -249,7 +331,7 @@ bool GroupAndSortOptimized::Transform(PhysicalOpNode* in, * otherwise: * - `left_key`, `index_key` corresponding to Key group & Key hash */ -bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx, +bool GroupAndSortOptimized::KeysOptimizedImpl(const SchemasContext* root_schemas_ctx, PhysicalOpNode* in, Key* left_key, Key* index_key, @@ -258,15 +340,6 @@ bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx PhysicalOpNode** new_in) { TransformCxtGuard guard(&ctx_, KeysInfo(in->GetOpType(), left_key, right_key, index_key, sort)); - if (nullptr == left_key || nullptr == index_key || !left_key->ValidKey()) { - return false; - } - - if (right_key != nullptr && !right_key->ValidKey()) { - return false; - } - - if (PhysicalOpType::kPhysicalOpDataProvider == in->GetOpType()) { auto scan_op = dynamic_cast(in); // Do not optimize with Request DataProvider (no index has been provided) @@ -276,6 +349,18 @@ bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx if (DataProviderType::kProviderTypeTable == scan_op->provider_type_ || DataProviderType::kProviderTypePartition == scan_op->provider_type_) { + auto* table_node = dynamic_cast(scan_op); + if (optimize_info_) { + if (optimize_info_->left_key == left_key && optimize_info_->index_key == index_key && + optimize_info_->right_key == right_key && optimize_info_->sort_key == sort) { + if (optimize_info_->optimized != nullptr && + table_node->GetDb() == optimize_info_->optimized->GetDb() && + table_node->GetName() == optimize_info_->optimized->GetName()) { + *new_in = optimize_info_->optimized; + return true; + } + } + } const node::ExprListNode* right_partition = right_key == nullptr ? left_key->keys() : right_key->keys(); @@ -382,19 +467,22 @@ bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx dynamic_cast(node_manager_->MakeOrderByNode(node_manager_->MakeExprList( node_manager_->MakeOrderExpression(nullptr, first_order_expression->is_asc()))))); } + + optimize_info_.reset(new OptimizeInfo(left_key, index_key, right_key, sort, partition_op)); *new_in = partition_op; return true; } } else if (PhysicalOpType::kPhysicalOpSimpleProject == in->GetOpType()) { - auto simple_project = dynamic_cast(in); PhysicalOpNode* new_depend; - if (!KeysOptimized(root_schemas_ctx, simple_project->producers()[0], - left_key, index_key, right_key, sort, &new_depend)) { + if (!KeysOptimizedImpl(in->GetProducer(0)->schemas_ctx(), in->GetProducer(0), left_key, index_key, right_key, + sort, &new_depend)) { return false; } + + auto simple_project = dynamic_cast(in); PhysicalSimpleProjectNode* new_simple_op = nullptr; - Status status = plan_ctx_->CreateOp( - &new_simple_op, new_depend, simple_project->project()); + Status status = + plan_ctx_->CreateOp(&new_simple_op, new_depend, simple_project->project()); if (!status.isOK()) { LOG(WARNING) << "Fail to create simple project op: " << status; return false; @@ -403,7 +491,7 @@ bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx return true; } else if (PhysicalOpType::kPhysicalOpRename == in->GetOpType()) { PhysicalOpNode* new_depend; - if (!KeysOptimized(root_schemas_ctx, in->producers()[0], left_key, + if (!KeysOptimizedImpl(in->GetProducer(0)->schemas_ctx(), in->producers()[0], left_key, index_key, right_key, sort, &new_depend)) { return false; } @@ -421,7 +509,8 @@ bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx PhysicalFilterNode* filter_op = dynamic_cast(in); PhysicalOpNode* new_depend; - if (!KeysOptimized(root_schemas_ctx, in->producers()[0], left_key, index_key, right_key, sort, &new_depend)) { + if (!KeysOptimizedImpl(root_schemas_ctx, in->producers()[0], left_key, index_key, right_key, sort, + &new_depend)) { return false; } PhysicalFilterNode* new_filter = nullptr; @@ -438,15 +527,99 @@ bool GroupAndSortOptimized::KeysOptimized(const SchemasContext* root_schemas_ctx // try optimze left source of request join with window definition // window partition by and order by columns must refer to the left most table only PhysicalOpNode* new_depend = nullptr; - if (!KeysOptimized(request_join->GetProducer(0)->schemas_ctx(), request_join->GetProducer(0), left_key, - index_key, right_key, sort, &new_depend)) { + auto* rebase_sc = in->GetProducer(0)->schemas_ctx(); + if (!KeysOptimizedImpl(rebase_sc, in->GetProducer(0), left_key, index_key, right_key, sort, + &new_depend)) { + return false; + } + PhysicalOpNode* new_right = in->GetProducer(1); + if (request_join->join_.join_type_ == node::kJoinTypeConcat) { + // for concat join, only acceptable if the two inputs (of course same table) optimized by the same index + auto* rebase_sc = in->GetProducer(1)->schemas_ctx(); + if (!KeysOptimizedImpl(rebase_sc, in->GetProducer(1), left_key, index_key, right_key, sort, &new_right)) { + return false; + } + } + PhysicalRequestJoinNode* new_join = nullptr; + auto s = plan_ctx_->CreateOp(&new_join, new_depend, new_right, + request_join->join(), request_join->output_right_only()); + if (!s.isOK()) { + LOG(WARNING) << "Fail to create new request join op: " << s; + return false; + } + + *new_in = new_join; + return true; + } else if (PhysicalOpType::kPhysicalOpJoin == in->GetOpType()) { + auto* join = dynamic_cast(in); + // try optimze left source of request join with window definition + // window partition by and order by columns must refer to the left most table only + PhysicalOpNode* new_depend = nullptr; + auto* rebase_sc = in->GetProducer(0)->schemas_ctx(); + if (!KeysOptimizedImpl(rebase_sc, in->GetProducer(0), left_key, index_key, right_key, sort, + &new_depend)) { + return false; + } + PhysicalJoinNode* new_join = nullptr; + auto s = plan_ctx_->CreateOp(&new_join, new_depend, join->GetProducer(1), + join->join(), join->output_right_only()); + if (!s.isOK()) { + LOG(WARNING) << "Fail to create new join op: " << s; + return false; + } + + *new_in = new_join; + return true; + } else if (PhysicalOpType::kPhysicalOpProject == in->GetOpType()) { + auto * project = dynamic_cast(in); + if (project == nullptr || project->project_type_ != vm::kAggregation) { return false; } - if (!ResetProducer(plan_ctx_, request_join, 0, new_depend)) { + + auto * agg_project = dynamic_cast(in); + + PhysicalOpNode* new_depend = nullptr; + auto* rebase_sc = in->GetProducer(0)->schemas_ctx(); + if (!KeysOptimizedImpl(rebase_sc, in->GetProducer(0), left_key, index_key, right_key, sort, + &new_depend)) { return false; } - *new_in = request_join; + vm::PhysicalAggregationNode* new_agg = nullptr; + if (!plan_ctx_ + ->CreateOp(&new_agg, new_depend, agg_project->project(), + agg_project->having_condition_.condition()) + .isOK()) { + return false; + } + *new_in = new_agg; + return true; + } else if (PhysicalOpType::kPhysicalOpRequestUnion == in->GetOpType()) { + // JOIN (..., AGG(REQUEST_UNION(left, ...))): JOIN condition optimizing left + PhysicalOpNode* new_left_depend = nullptr; + auto* rebase_sc = in->GetProducer(0)->schemas_ctx(); + if (!KeysOptimizedImpl(rebase_sc, in->GetProducer(0), left_key, index_key, right_key, sort, + &new_left_depend)) { + return false; + } + + auto * request_union = dynamic_cast(in); + + vm::PhysicalRequestUnionNode* new_union = nullptr; + if (!plan_ctx_ + ->CreateOp( + &new_union, new_left_depend, in->GetProducer(1), request_union->window(), + request_union->instance_not_in_window(), request_union->exclude_current_time(), + request_union->output_request_row()) + .isOK()) { + return false; + } + for (auto& pair : request_union->window_unions().window_unions_) { + if (!new_union->AddWindowUnion(pair.first, pair.second)) { + return false; + } + } + *new_in = new_union; return true; } return false; @@ -517,39 +690,6 @@ bool GroupAndSortOptimized::KeyAndOrderOptimized( sort, new_in); } -bool GroupAndSortOptimized::SortOptimized( - const SchemasContext* root_schemas_ctx, PhysicalOpNode* in, Sort* sort) { - if (nullptr == sort) { - return false; - } - if (PhysicalOpType::kPhysicalOpDataProvider == in->GetOpType()) { - auto scan_op = dynamic_cast(in); - if (DataProviderType::kProviderTypePartition != - scan_op->provider_type_) { - return false; - } - auto partition_provider = - dynamic_cast(scan_op); - const node::OrderByNode* new_orders = nullptr; - - auto& index_hint = partition_provider->table_handler_->GetIndex(); - std::string index_name = partition_provider->index_name_; - auto index_st = index_hint.at(index_name); - TransformOrderExpr(root_schemas_ctx, sort->orders(), - *(scan_op->table_handler_->GetSchema()), index_st, - &new_orders); - sort->set_orders(new_orders); - return true; - } else if (PhysicalOpType::kPhysicalOpSimpleProject == in->GetOpType()) { - auto simple_project = dynamic_cast(in); - return SortOptimized(root_schemas_ctx, simple_project->producers()[0], - sort); - } else if (PhysicalOpType::kPhysicalOpRename == in->GetOpType()) { - return SortOptimized(root_schemas_ctx, in->producers()[0], sort); - } - return false; -} - bool GroupAndSortOptimized::TransformKeysAndOrderExpr(const SchemasContext* root_schemas_ctx, const node::ExprListNode* groups, const node::OrderByNode* order, @@ -577,13 +717,18 @@ bool GroupAndSortOptimized::TransformKeysAndOrderExpr(const SchemasContext* root switch (group->expr_type_) { case node::kExprColumnRef: { auto column = dynamic_cast(group); - std::string source_column_name; - if (!ResolveColumnToSourceColumnName(column, root_schemas_ctx, &source_column_name)) { + auto op = expr_cache_.find(column); + if (op == expr_cache_.end()) { + return false; + } + + if (table_handler->GetName() != op->second.tb_name || + table_handler->GetDatabase() != op->second.db_name) { return false; } result_bitmap_mapping[columns.size()] = i; - columns.push_back(source_column_name); + columns.emplace_back(op->second.col_name); break; } default: { @@ -597,11 +742,17 @@ bool GroupAndSortOptimized::TransformKeysAndOrderExpr(const SchemasContext* root auto expr = order->GetOrderExpressionExpr(i); if (nullptr != expr && expr->GetExprType() == node::kExprColumnRef) { auto column = dynamic_cast(expr); - std::string source_column_name; - if (!ResolveColumnToSourceColumnName(column, root_schemas_ctx, &source_column_name)) { + auto op = expr_cache_.find(column); + if (op == expr_cache_.end()) { return false; } - order_columns.push_back(source_column_name); + + if (table_handler->GetName() != op->second.tb_name || + table_handler->GetDatabase() != op->second.db_name) { + return false; + } + + order_columns.emplace_back(op->second.col_name); } } } @@ -749,75 +900,21 @@ bool GroupAndSortOptimized::MatchBestIndex(const std::vector& colum return succ; } -bool GroupAndSortOptimized::TransformOrderExpr( - const SchemasContext* schemas_ctx, const node::OrderByNode* order, - const Schema& schema, const IndexSt& index_st, - const node::OrderByNode** output) { - *output = order; - if (nullptr == order || nullptr == output) { - DLOG(WARNING) - << "fail to optimize order expr : order expr or output is null"; - return false; - } - if (index_st.ts_pos == INVALID_POS) { - DLOG(WARNING) << "not set ts col"; - return false; - } - auto& ts_column = schema.Get(index_st.ts_pos); - *output = order; - int succ_match = -1; - for (size_t i = 0; i < order->order_expressions()->GetChildNum(); ++i) { - auto expr = order->GetOrderExpressionExpr(i); - if (nullptr != expr && expr->GetExprType() == node::kExprColumnRef) { - auto column = dynamic_cast(expr); - std::string source_column_name; - if (ResolveColumnToSourceColumnName(column, schemas_ctx, - &source_column_name)) { - if (ts_column.name() == source_column_name) { - succ_match = i; - break; - } - } - } - } - if (succ_match >= 0) { - node::ExprListNode* expr_list = node_manager_->MakeExprList(); - for (size_t i = 0; i < order->order_expressions()->GetChildNum(); ++i) { - if (static_cast(succ_match) != i) { - expr_list->AddChild(order->order_expressions()->GetChild(i)); - } - } - *output = dynamic_cast( - node_manager_->MakeOrderByNode(expr_list)); - return true; - } else { - return false; - } -} - /** * Resolve column reference to possible source table's column name */ -static bool ResolveColumnToSourceColumnName(const node::ColumnRefNode* col, - const SchemasContext* schemas_ctx, - std::string* source_name) { - // use detailed column resolve utility +static bool ResolveColumnToSourceColumnName(const node::ColumnRefNode* col, const SchemasContext* schemas_ctx, + std::string* src_db, std::string* src_table, std::string* src_col) { + auto db = col->GetDBName(); + auto rel = col->GetRelationName(); + auto col_name = col->GetColumnName(); size_t column_id; int path_idx; size_t child_column_id; size_t source_column_id; const PhysicalOpNode* source; - Status status = schemas_ctx->ResolveColumnID(col->GetDBName(), - col->GetRelationName(), col->GetColumnName(), &column_id, &path_idx, - &child_column_id, &source_column_id, &source); - - // try loose the relation - if (!status.isOK() && !col->GetRelationName().empty()) { - status = schemas_ctx->ResolveColumnID( - col->GetDBName(), col->GetRelationName(), col->GetColumnName(), - &column_id, &path_idx, &child_column_id, - &source_column_id, &source); - } + Status status = schemas_ctx->ResolveColumnID(db, rel, col_name, &column_id, &path_idx, &child_column_id, + &source_column_id, &source); if (!status.isOK()) { LOG(WARNING) << "Illegal index column: " << col->GetExprString(); @@ -829,13 +926,17 @@ static bool ResolveColumnToSourceColumnName(const node::ColumnRefNode* col, << col->GetExprString(); return false; } - status = source->schemas_ctx()->ResolveColumnNameByID(source_column_id, - source_name); + + std::string sc_db, sc_table, sc_column; + status = source->schemas_ctx()->ResolveDbTableColumnByID(source_column_id, &sc_db, &sc_table, &sc_column); if (!status.isOK()) { - LOG(WARNING) << "Illegal source column id #" << source_column_id - << " for index column " << col->GetExprString(); + LOG(WARNING) << "Illegal source column id #" << source_column_id << " for index column " + << col->GetExprString(); return false; } + *src_db = sc_db; + *src_table = sc_table; + *src_col = sc_column; return true; } diff --git a/hybridse/src/passes/physical/group_and_sort_optimized.h b/hybridse/src/passes/physical/group_and_sort_optimized.h index 612b9cdaa67..2e50571b29d 100644 --- a/hybridse/src/passes/physical/group_and_sort_optimized.h +++ b/hybridse/src/passes/physical/group_and_sort_optimized.h @@ -16,12 +16,13 @@ #ifndef HYBRIDSE_SRC_PASSES_PHYSICAL_GROUP_AND_SORT_OPTIMIZED_H_ #define HYBRIDSE_SRC_PASSES_PHYSICAL_GROUP_AND_SORT_OPTIMIZED_H_ +#include #include #include #include -#include -#include +#include #include +#include #include "passes/physical/transform_up_physical_pass.h" @@ -85,12 +86,33 @@ class GroupAndSortOptimized : public TransformUpPysicalPass { Container* c_; }; + // info to a physical table + struct SrcColInfo { + std::string col_name; + std::string tb_name; + std::string db_name; + }; + + struct OptimizeInfo { + OptimizeInfo(const Key* left_key, const Key* index_key, const Key* right_key, const Sort* s, + vm::PhysicalPartitionProviderNode* optimized) + : left_key(left_key), index_key(index_key), right_key(right_key), sort_key(s), optimized(optimized) {} + const Key* left_key; + const Key* index_key; + const Key* right_key; + const Sort* sort_key; + vm::PhysicalPartitionProviderNode* optimized; + }; + private: bool Transform(PhysicalOpNode* in, PhysicalOpNode** output); bool KeysOptimized(const SchemasContext* root_schemas_ctx, PhysicalOpNode* in, Key* left_key, Key* index_key, Key* right_key, Sort* sort, PhysicalOpNode** new_in); + bool KeysOptimizedImpl(const SchemasContext* root_schemas_ctx, PhysicalOpNode* in, Key* left_key, Key* index_key, + Key* right_key, Sort* sort, PhysicalOpNode** new_in); + bool FilterAndOrderOptimized(const SchemasContext* root_schemas_ctx, PhysicalOpNode* in, Filter* filter, Sort* sort, PhysicalOpNode** new_in); @@ -115,12 +137,7 @@ class GroupAndSortOptimized : public TransformUpPysicalPass { bool GroupOptimized(const SchemasContext* root_schemas_ctx, PhysicalOpNode* in, Key* group, PhysicalOpNode** new_in); - bool SortOptimized(const SchemasContext* root_schemas_ctx, - PhysicalOpNode* in, Sort* sort); - bool TransformOrderExpr(const SchemasContext* schemas_ctx, - const node::OrderByNode* order, - const Schema& schema, const IndexSt& index_st, - const node::OrderByNode** output); + bool TransformKeysAndOrderExpr(const SchemasContext* schemas_ctx, const node::ExprListNode* groups, const node::OrderByNode* order, @@ -134,8 +151,17 @@ class GroupAndSortOptimized : public TransformUpPysicalPass { std::string* index_name, IndexBitMap* best_bitmap); + absl::Status BuildExprCache(const node::ExprNode* node, const SchemasContext* sc); + private: std::list ctx_; + + // Map ExprNode to source column name + // A source column name is the column name in string that refers to a physical table, + // only one table got optimized each time + std::unordered_map expr_cache_; + + std::unique_ptr optimize_info_; }; } // namespace passes } // namespace hybridse diff --git a/hybridse/src/passes/physical/transform_up_physical_pass.h b/hybridse/src/passes/physical/transform_up_physical_pass.h index fed721d4c66..a9a80bd90b4 100644 --- a/hybridse/src/passes/physical/transform_up_physical_pass.h +++ b/hybridse/src/passes/physical/transform_up_physical_pass.h @@ -17,7 +17,6 @@ #define HYBRIDSE_SRC_PASSES_PHYSICAL_TRANSFORM_UP_PHYSICAL_PASS_H_ #include -#include #include #include diff --git a/hybridse/src/plan/planner.cc b/hybridse/src/plan/planner.cc index c0a68e3104e..fc350d1ffb6 100644 --- a/hybridse/src/plan/planner.cc +++ b/hybridse/src/plan/planner.cc @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -273,7 +272,7 @@ base::Status Planner::CreateSelectQueryPlan(const node::SelectQueryNode *root, n auto first_window_project = dynamic_cast(project_list_vec[1]); node::ProjectListNode *merged_project = node_manager_->MakeProjectListPlanNode(first_window_project->GetW(), true); - if (!is_cluster_optimized_ && !enable_batch_window_parallelization_ && + if (!is_cluster_optimized_ && !enable_batch_window_parallelization_ && node::ProjectListNode::MergeProjectList(simple_project, first_window_project, merged_project)) { project_list_vec[0] = nullptr; project_list_vec[1] = merged_project; diff --git a/hybridse/src/planv2/ast_node_converter.cc b/hybridse/src/planv2/ast_node_converter.cc index 19bb0ccfc6c..2592c19fb99 100644 --- a/hybridse/src/planv2/ast_node_converter.cc +++ b/hybridse/src/planv2/ast_node_converter.cc @@ -611,6 +611,16 @@ base::Status ConvertStatement(const zetasql::ASTStatement* statement, node::Node *output = node; break; } + case zetasql::AST_TRUNCATE_STATEMENT: { + const zetasql::ASTTruncateStatement* truncate_statement = + statement->GetAsOrNull(); + std::vector names; + CHECK_STATUS(AstPathExpressionToStringList(truncate_statement->target_path(), names)); + auto node = + dynamic_cast(node_manager->MakeCmdNode(node::CmdType::kCmdTruncate, names)); + *output = node; + break; + } case zetasql::AST_DROP_FUNCTION_STATEMENT: { const zetasql::ASTDropFunctionStatement* drop_fun_statement = statement->GetAsOrNull(); @@ -1113,13 +1123,13 @@ base::Status ConvertTableExpressionNode(const zetasql::ASTTableExpression* root, node::TableRefNode* right = nullptr; node::OrderByNode* order_by = nullptr; node::ExprNode* condition = nullptr; - node::JoinType join_type = node::JoinType::kJoinTypeInner; CHECK_STATUS(ConvertTableExpressionNode(join->lhs(), node_manager, &left)) CHECK_STATUS(ConvertTableExpressionNode(join->rhs(), node_manager, &right)) CHECK_STATUS(ConvertOrderBy(join->order_by(), node_manager, &order_by)) if (nullptr != join->on_clause()) { CHECK_STATUS(ConvertExprNode(join->on_clause()->expression(), node_manager, &condition)) } + node::JoinType join_type = node::JoinType::kJoinTypeInner; switch (join->join_type()) { case zetasql::ASTJoin::JoinType::FULL: { join_type = node::JoinType::kJoinTypeFull; @@ -1137,12 +1147,14 @@ base::Status ConvertTableExpressionNode(const zetasql::ASTTableExpression* root, join_type = node::JoinType::kJoinTypeLast; break; } - case zetasql::ASTJoin::JoinType::INNER: { + case zetasql::ASTJoin::JoinType::INNER: + case zetasql::ASTJoin::JoinType::DEFAULT_JOIN_TYPE: { join_type = node::JoinType::kJoinTypeInner; break; } - case zetasql::ASTJoin::JoinType::COMMA: { - join_type = node::JoinType::kJoinTypeComma; + case zetasql::ASTJoin::JoinType::COMMA: + case zetasql::ASTJoin::JoinType::CROSS: { + join_type = node::JoinType::kJoinTypeCross; break; } default: { @@ -1290,6 +1302,7 @@ base::Status ConvertQueryExpr(const zetasql::ASTQueryExpression* query_expressio if (nullptr != select_query->from_clause()) { CHECK_STATUS(ConvertTableExpressionNode(select_query->from_clause()->table_expression(), node_manager, &table_ref_node)) + // TODO(.): dont mark table ref as a list, it never happens if (nullptr != table_ref_node) { tableref_list_ptr = node_manager->MakeNodeList(); tableref_list_ptr->PushBack(table_ref_node); @@ -1761,8 +1774,18 @@ base::Status ConvertTableOption(const zetasql::ASTOptionsEntry* entry, node::Nod } else if (absl::EqualsIgnoreCase("storage_mode", identifier_v)) { std::string storage_mode; CHECK_STATUS(AstStringLiteralToString(entry->value(), &storage_mode)); - boost::to_lower(storage_mode); - *output = node_manager->MakeStorageModeNode(node::NameToStorageMode(storage_mode)); + absl::AsciiStrToLower(&storage_mode); + *output = node_manager->MakeNode(node::NameToStorageMode(storage_mode)); + } else if (absl::EqualsIgnoreCase("compress_type", identifier_v)) { + std::string compress_type; + CHECK_STATUS(AstStringLiteralToString(entry->value(), &compress_type)); + absl::AsciiStrToLower(&compress_type); + auto ret = node::NameToCompressType(compress_type); + if (ret.ok()) { + *output = node_manager->MakeNode(*ret); + } else { + return base::Status(common::kSqlAstError, ret.status().ToString()); + } } else { return base::Status(common::kSqlAstError, absl::StrCat("invalid option ", identifier)); } @@ -2172,6 +2195,7 @@ static const absl::flat_hash_map showTargetMap {"SESSION VARIABLES", {node::CmdType::kCmdShowSessionVariables}}, {"GLOBAL VARIABLES", {node::CmdType::kCmdShowGlobalVariables}}, {"CREATE PROCEDURE", {node::CmdType::kCmdShowCreateSp, true}}, + {"CREATE TABLE", {node::CmdType::kCmdShowCreateTable, true}}, {"DEPLOYMENT", {node::CmdType::kCmdShowDeployment, true}}, {"JOB", {node::CmdType::kCmdShowJob, true}}, {"COMPONENTS", {node::CmdType::kCmdShowComponents}}, diff --git a/hybridse/src/testing/engine_test_base.cc b/hybridse/src/testing/engine_test_base.cc index ca1af237936..4992b6b5018 100644 --- a/hybridse/src/testing/engine_test_base.cc +++ b/hybridse/src/testing/engine_test_base.cc @@ -240,8 +240,7 @@ void DoEngineCheckExpect(const SqlCase& sql_case, std::shared_ptr se if (!output_common_column_indices.empty() && output_common_column_indices.size() != static_cast(schema.size()) && sql_ctx.is_batch_request_optimized) { - LOG(INFO) << "Reorder batch request outputs for non-trival common " - "columns"; + DLOG(INFO) << "Reorder batch request outputs for non-trival columns"; auto& expect_common_column_indices = sql_case.expect().common_column_indices_; if (!expect_common_column_indices.empty()) { @@ -375,7 +374,7 @@ Status EngineTestRunner::Compile() { std::string placeholder = "{" + std::to_string(j) + "}"; boost::replace_all(sql_str, placeholder, sql_case_.inputs_[j].name_); } - LOG(INFO) << "Compile SQL:\n" << sql_str; + DLOG(INFO) << "Compile SQL:\n" << sql_str; CHECK_TRUE(session_ != nullptr, common::kTestEngineError, "Session is not set"); if (hybridse::sqlcase::SqlCase::IsDebug() || sql_case_.debug()) { session_->EnableDebug(); @@ -395,22 +394,23 @@ Status EngineTestRunner::Compile() { bool ok = engine_->Get(sql_str, sql_case_.db(), *session_, status); gettimeofday(&et, nullptr); double mill = (et.tv_sec - st.tv_sec) * 1000 + (et.tv_usec - st.tv_usec) / 1000.0; - LOG(INFO) << "SQL Compile take " << mill << " milliseconds"; + DLOG(INFO) << "SQL Compile take " << mill << " milliseconds"; if (!ok || !status.isOK()) { - LOG(INFO) << status; + DLOG(INFO) << status; + if (!sql_case_.expect().msg_.empty()) { + EXPECT_EQ(sql_case_.expect().msg_, status.msg); + } return_code_ = ENGINE_TEST_RET_COMPILE_ERROR; } else { - LOG(INFO) << "SQL output schema:"; + DLOG(INFO) << "SQL output schema:"; std::ostringstream oss; std::dynamic_pointer_cast(session_->GetCompileInfo())->GetPhysicalPlan()->Print(oss, ""); - LOG(INFO) << "Physical plan:"; - std::cerr << oss.str() << std::endl; + DLOG(INFO) << "Physical plan:\n" << oss.str(); std::ostringstream runner_oss; std::dynamic_pointer_cast(session_->GetCompileInfo())->GetClusterJob().Print(runner_oss, ""); - LOG(INFO) << "Runner plan:"; - std::cerr << runner_oss.str() << std::endl; + DLOG(INFO) << "Runner plan:\n" << runner_oss.str(); } return status; } @@ -533,9 +533,13 @@ INSTANTIATE_TEST_SUITE_P(EngineExtreamQuery, EngineTest, INSTANTIATE_TEST_SUITE_P(EngineLastJoinQuery, EngineTest, testing::ValuesIn(sqlcase::InitCases("cases/query/last_join_query.yaml"))); +INSTANTIATE_TEST_SUITE_P(EngineLeftJoin, EngineTest, + testing::ValuesIn(sqlcase::InitCases("cases/query/left_join.yml"))); INSTANTIATE_TEST_SUITE_P(EngineLastJoinWindowQuery, EngineTest, testing::ValuesIn(sqlcase::InitCases("cases/query/last_join_window_query.yaml"))); +INSTANTIATE_TEST_SUITE_P(EngineLastJoinSubqueryWindow, EngineTest, + testing::ValuesIn(sqlcase::InitCases("cases/query/last_join_subquery_window.yml"))); INSTANTIATE_TEST_SUITE_P(EngineLastJoinWhere, EngineTest, testing::ValuesIn(sqlcase::InitCases("cases/query/last_join_where.yaml"))); INSTANTIATE_TEST_SUITE_P(EngineWindowQuery, EngineTest, diff --git a/hybridse/src/testing/engine_test_base.h b/hybridse/src/testing/engine_test_base.h index e759169f0fd..0805ff1b3c5 100644 --- a/hybridse/src/testing/engine_test_base.h +++ b/hybridse/src/testing/engine_test_base.h @@ -318,8 +318,7 @@ class BatchRequestEngineTestRunner : public EngineTestRunner { bool has_batch_request = !sql_case_.batch_request().columns_.empty(); if (!has_batch_request) { - LOG(WARNING) << "No batch request field in case, " - << "try use last row from primary input"; + LOG(WARNING) << "No batch request field in case, try use last row from primary input"; } std::vector original_request_data; diff --git a/hybridse/src/udf/default_udf_library.cc b/hybridse/src/udf/default_udf_library.cc index f2c9bc1afd8..e6a546095ec 100644 --- a/hybridse/src/udf/default_udf_library.cc +++ b/hybridse/src/udf/default_udf_library.cc @@ -26,7 +26,6 @@ #include #include -#include "absl/strings/str_cat.h" #include "codegen/date_ir_builder.h" #include "codegen/string_ir_builder.h" #include "codegen/timestamp_ir_builder.h" @@ -2169,11 +2168,7 @@ void DefaultUdfLibrary::InitTypeUdf() { )"); RegisterExternal("date") - .args(reinterpret_cast( - static_cast( - v1::timestamp_to_date))) - .return_by_arg(true) - .returns>() + .args(v1::timestamp_to_date) .doc(R"( @brief Cast timestamp or string expression to date (date >= 1900-01-01) @@ -2192,18 +2187,10 @@ void DefaultUdfLibrary::InitTypeUdf() { @endcode @since 0.1.0)"); RegisterExternal("date") - .args(reinterpret_cast( - static_cast( - v1::string_to_date))) - .return_by_arg(true) - .returns>(); + .args(v1::string_to_date); RegisterExternal("timestamp") - .args(reinterpret_cast( - static_cast( - v1::date_to_timestamp))) - .return_by_arg(true) - .returns>() + .args(v1::date_to_timestamp) .doc(R"( @brief Cast int64, date or string expression to timestamp @@ -2226,11 +2213,7 @@ void DefaultUdfLibrary::InitTypeUdf() { @endcode @since 0.1.0)"); RegisterExternal("timestamp") - .args(reinterpret_cast( - static_cast( - v1::string_to_timestamp))) - .return_by_arg(true) - .returns>(); + .args(v1::string_to_timestamp); } void DefaultUdfLibrary::InitTimeAndDateUdf() { @@ -2591,16 +2574,21 @@ void DefaultUdfLibrary::InitTimeAndDateUdf() { }); RegisterExternal("datediff") - .args(reinterpret_cast(static_cast(v1::date_diff))) - .return_by_arg(true) - .returns>() + .args(static_cast(v1::date_diff)) .doc(R"( @brief days difference from date1 to date2 Supported date string style: - yyyy-mm-dd - yyyymmdd - - yyyy-mm-dd hh:mm:ss + - yyyy-mm-dd HH:MM:SS + - yyyy-mm-ddTHH:MM:SS.fff+HH:MM (RFC3399 format) + + Dates from string are transformed into the same time zone (which is currently always UTC+8) before differentiation, + dates from date type by default is at UTC+8, you may see a +1/-1 difference if the two date string have different time zones. + + Hint: since openmldb date type limits range from year 1900, to datadiff from/to a date before + 1900, pass it as string. Example: @@ -2614,20 +2602,11 @@ void DefaultUdfLibrary::InitTimeAndDateUdf() { @endcode @since 0.7.0)"); RegisterExternal("datediff") - .args( - reinterpret_cast(static_cast(v1::date_diff))) - .return_by_arg(true) - .returns>(); + .args(static_cast(v1::date_diff)); RegisterExternal("datediff") - .args( - reinterpret_cast(static_cast(v1::date_diff))) - .return_by_arg(true) - .returns>(); + .args(static_cast(v1::date_diff)); RegisterExternal("datediff") - .args( - reinterpret_cast(static_cast(v1::date_diff))) - .return_by_arg(true) - .returns>(); + .args(static_cast(v1::date_diff)); RegisterExternal("unix_timestamp") .args(reinterpret_cast(static_cast(v1::date_to_unix_timestamp))) diff --git a/hybridse/src/udf/udf.cc b/hybridse/src/udf/udf.cc index c32a58b8adb..9326d576685 100644 --- a/hybridse/src/udf/udf.cc +++ b/hybridse/src/udf/udf.cc @@ -16,33 +16,28 @@ #include "udf/udf.h" -#include #include #include #include -#include -#include #include #include "absl/strings/ascii.h" #include "absl/strings/str_replace.h" #include "absl/time/civil_time.h" +#include "absl/time/time.h" #include "base/iterator.h" -#include "boost/date_time.hpp" +#include "boost/date_time/gregorian/conversion.hpp" #include "boost/date_time/gregorian/parsers.hpp" -#include "boost/date_time/posix_time/posix_time.hpp" #include "bthread/types.h" -#include "codec/list_iterator_codec.h" #include "codec/row.h" #include "codec/type_codec.h" -#include "codegen/fn_ir_builder.h" -#include "farmhash.h" // NOLINT +#include "farmhash.h" #include "node/node_manager.h" #include "node/sql_node.h" #include "re2/re2.h" -#include "udf/default_udf_library.h" #include "udf/literal_traits.h" +#include "udf/udf_library.h" #include "vm/jit_runtime.h" namespace hybridse { @@ -57,6 +52,20 @@ using openmldb::base::StringRef; using openmldb::base::Timestamp; using openmldb::base::Date; +// strftime()-like formatting options with extensions +// ref absl::FormatTime +static constexpr char DATE_FMT_YMD_1[] = "%E4Y-%m-%d"; +static constexpr char DATE_FMT_YMD_2[] = "%E4Y%m%d"; +static constexpr char DATE_FMT_YMDHMS[] = "%E4Y-%m-%d %H:%M:%S"; +static constexpr char DATE_FMT_RF3399_FULL[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez"; + +// TODO(chenjing): 时区统一配置 +static constexpr int32_t TZ = 8; +static const absl::TimeZone DEFAULT_TZ = absl::FixedTimeZone(TZ * 60 * 60); +static constexpr time_t TZ_OFFSET = TZ * 3600000; +static constexpr int MAX_ALLOC_SIZE = 2 * 1024 * 1024; // 2M +bthread_key_t B_THREAD_LOCAL_MEM_POOL_KEY; + void hex(StringRef *str, StringRef *output) { std::ostringstream ss; for (uint32_t i=0; i < str->size_; i++) { @@ -104,12 +113,6 @@ void unhex(StringRef *str, StringRef *output, bool* is_null) { } } -// TODO(chenjing): 时区统一配置 -constexpr int32_t TZ = 8; -constexpr time_t TZ_OFFSET = TZ * 3600000; -constexpr int MAX_ALLOC_SIZE = 2 * 1024 * 1024; // 2M -bthread_key_t B_THREAD_LOCAL_MEM_POOL_KEY; - void trivial_fun() {} void dayofyear(int64_t ts, int32_t* out, bool* is_null) { @@ -386,8 +389,7 @@ void bool_to_string(bool v, StringRef *output) { } } -void timestamp_to_date(Timestamp *timestamp, - Date *output, bool *is_null) { +void timestamp_to_date(Timestamp *timestamp, Date *output, bool *is_null) { time_t time = (timestamp->ts_ + TZ_OFFSET) / 1000; struct tm t; memset(&t, 0, sizeof(struct tm)); @@ -763,8 +765,7 @@ void string_to_double(StringRef *str, double *out, bool *is_null_ptr) { } return; } -void string_to_date(StringRef *str, Date *output, - bool *is_null) { +void string_to_date(StringRef *str, Date *output, bool *is_null) { if (19 == str->size_) { struct tm timeinfo; memset(&timeinfo, 0, sizeof(struct tm)); @@ -818,7 +819,26 @@ void string_to_date(StringRef *str, Date *output, return; } -void date_diff(Date *date1, Date *date2, int *diff, bool *is_null) { +absl::StatusOr string_to_time(absl::string_view ref) { + absl::string_view fmt = DATE_FMT_RF3399_FULL; + if (19 == ref.size()) { + fmt = DATE_FMT_YMDHMS; + } else if (10 == ref.size()) { + fmt = DATE_FMT_YMD_1; + } else if (8 == ref.size()) { + fmt = DATE_FMT_YMD_2; + } + absl::Time tm; + std::string err; + bool ret = absl::ParseTime(fmt, ref, &tm, &err); + + if (!ret) { + return absl::InvalidArgumentError(err); + } + return tm; +} + +void date_diff(Date *date1, Date *date2, int32_t *diff, bool *is_null) { if (date1 == nullptr || date2 == nullptr || date1->date_ <= 0 || date2->date_ <= 0) { *is_null = true; return; @@ -838,36 +858,61 @@ void date_diff(Date *date1, Date *date2, int *diff, bool *is_null) { *is_null = false; } -void date_diff(StringRef *date1, StringRef *date2, int *diff, bool *is_null) { - Date d1; - string_to_date(date1, &d1, is_null); - if (*is_null) { +void date_diff(StringRef *date1, StringRef *date2, int32_t *diff, bool *is_null) { + auto t1 = string_to_time(absl::string_view(date1->data_, date1->size_)); + if (!t1.ok()) { + *is_null = true; return; } - Date d2; - string_to_date(date2, &d2, is_null); - if (*is_null) { + auto t2 = string_to_time(absl::string_view(date2->data_, date2->size_)); + if (!t2.ok()) { + *is_null = true; return; } - date_diff(&d1, &d2, diff, is_null); + + auto d1 = absl::ToCivilDay(t1.value(), DEFAULT_TZ); + auto d2 = absl::ToCivilDay(t2.value(), DEFAULT_TZ); + + *diff = d1 - d2; + *is_null = false; } -void date_diff(StringRef *date1, Date *date2, int *diff, bool *is_null) { - Date d1; - string_to_date(date1, &d1, is_null); - if (*is_null) { +void date_diff(StringRef *date1, Date *date2, int32_t *diff, bool *is_null) { + auto t1 = string_to_time(absl::string_view(date1->data_, date1->size_)); + if (!t1.ok()) { + *is_null = true; return; } - date_diff(&d1, date2, diff, is_null); + auto d1 = absl::ToCivilDay(t1.value(), DEFAULT_TZ); + + int32_t year, month, day; + if (!Date::Decode(date2->date_, &year, &month, &day)) { + *is_null = true; + return; + } + auto d2 = absl::CivilDay(year, month, day); + + *diff = d1 - d2; + *is_null = false; } -void date_diff(Date *date1, StringRef *date2, int *diff, bool *is_null) { - Date d2; - string_to_date(date2, &d2, is_null); - if (*is_null) { +void date_diff(Date *date1, StringRef *date2, int32_t *diff, bool *is_null) { + auto t2 = string_to_time(absl::string_view(date2->data_, date2->size_)); + if (!t2.ok()) { + *is_null = true; return; } - date_diff(date1, &d2, diff, is_null); + auto d2 = absl::ToCivilDay(t2.value(), DEFAULT_TZ); + + int32_t year, month, day; + if (!Date::Decode(date1->date_, &year, &month, &day)) { + *is_null = true; + return; + } + auto d1 = absl::CivilDay(year, month, day); + + *diff = d1 - d2; + *is_null = false; } // cast string to timestamp with yyyy-mm-dd or YYYY-mm-dd HH:MM:SS diff --git a/hybridse/src/udf/udf.h b/hybridse/src/udf/udf.h index c2f2d2dc6f0..c91b78d1c90 100644 --- a/hybridse/src/udf/udf.h +++ b/hybridse/src/udf/udf.h @@ -26,10 +26,6 @@ #include "absl/strings/ascii.h" #include "base/string_ref.h" #include "base/type.h" -#include "codec/list_iterator_codec.h" -#include "codec/type_codec.h" -#include "node/node_manager.h" -#include "proto/fe_type.pb.h" #include "udf/literal_traits.h" #include "udf/openmldb_udf.h" @@ -367,10 +363,10 @@ void timestamp_to_date(Timestamp *timestamp, Date *output, bool *is_null); void date_to_string(Date *date, StringRef *output); -void date_diff(Date *date1, Date *date2, int *diff, bool *is_null); -void date_diff(StringRef *date1, StringRef *date2, int *diff, bool *is_null); -void date_diff(StringRef *date1, Date *date2, int *diff, bool *is_null); -void date_diff(Date *date1, StringRef *date2, int *diff, bool *is_null); +void date_diff(Date *date1, Date *date2, int32_t *diff, bool *is_null); +void date_diff(StringRef *date1, StringRef *date2, int32_t *diff, bool *is_null); +void date_diff(StringRef *date1, Date *date2, int32_t *diff, bool *is_null); +void date_diff(Date *date1, StringRef *date2, int32_t *diff, bool *is_null); void like(StringRef *name, StringRef *pattern, StringRef *escape, bool *out, bool *is_null); @@ -384,6 +380,8 @@ void regexp_like(StringRef *name, StringRef *pattern, bool *out, bool *is_null); void date_to_timestamp(Date *date, Timestamp *output, bool *is_null); void string_to_date(StringRef *str, Date *output, bool *is_null); +absl::StatusOr string_to_time(absl::string_view str); + void string_to_timestamp(StringRef *str, Timestamp *output, bool *is_null); void date_to_unix_timestamp(Date *date, int64_t *output, bool *is_null); void string_to_unix_timestamp(StringRef *str, int64_t *output, bool *is_null); diff --git a/hybridse/src/vm/catalog_wrapper.cc b/hybridse/src/vm/catalog_wrapper.cc index d134a92e51b..fbdd337e869 100644 --- a/hybridse/src/vm/catalog_wrapper.cc +++ b/hybridse/src/vm/catalog_wrapper.cc @@ -28,7 +28,7 @@ std::shared_ptr PartitionProjectWrapper::GetSegment( new TableProjectWrapper(segment, parameter_, fun_)); } } -base::ConstIterator* PartitionProjectWrapper::GetRawIterator() { +codec::RowIterator* PartitionProjectWrapper::GetRawIterator() { auto iter = partition_handler_->GetIterator(); if (!iter) { return nullptr; @@ -47,7 +47,7 @@ std::shared_ptr PartitionFilterWrapper::GetSegment( new TableFilterWrapper(segment, parameter_, fun_)); } } -base::ConstIterator* PartitionFilterWrapper::GetRawIterator() { +codec::RowIterator* PartitionFilterWrapper::GetRawIterator() { auto iter = partition_handler_->GetIterator(); if (!iter) { return nullptr; @@ -76,10 +76,6 @@ std::shared_ptr TableFilterWrapper::GetPartition( } } -LazyLastJoinIterator::LazyLastJoinIterator(std::unique_ptr&& left, std::shared_ptr right, - const Row& param, std::shared_ptr join) - : left_it_(std::move(left)), right_(right), parameter_(param), join_(join) {} - void LazyLastJoinIterator::Seek(const uint64_t& key) { left_it_->Seek(key); } void LazyLastJoinIterator::SeekToFirst() { left_it_->SeekToFirst(); } @@ -90,49 +86,36 @@ void LazyLastJoinIterator::Next() { left_it_->Next(); } bool LazyLastJoinIterator::Valid() const { return left_it_ && left_it_->Valid(); } -LazyLastJoinTableHandler::LazyLastJoinTableHandler(std::shared_ptr left, - std::shared_ptr right, const Row& param, +LazyJoinPartitionHandler::LazyJoinPartitionHandler(std::shared_ptr left, + std::shared_ptr right, const Row& param, std::shared_ptr join) : left_(left), right_(right), parameter_(param), join_(join) {} -LazyLastJoinPartitionHandler::LazyLastJoinPartitionHandler(std::shared_ptr left, - std::shared_ptr right, const Row& param, - std::shared_ptr join) - : left_(left), right_(right), parameter_(param), join_(join) {} - -std::shared_ptr LazyLastJoinPartitionHandler::GetSegment(const std::string& key) { +std::shared_ptr LazyJoinPartitionHandler::GetSegment(const std::string& key) { auto left_seg = left_->GetSegment(key); - return std::shared_ptr(new LazyLastJoinTableHandler(left_seg, right_, parameter_, join_)); + return std::shared_ptr(new LazyJoinTableHandler(left_seg, right_, parameter_, join_)); } -std::shared_ptr LazyLastJoinTableHandler::GetPartition(const std::string& index_name) { +std::shared_ptr LazyJoinTableHandler::GetPartition(const std::string& index_name) { return std::shared_ptr( - new LazyLastJoinPartitionHandler(left_->GetPartition(index_name), right_, parameter_, join_)); + new LazyJoinPartitionHandler(left_->GetPartition(index_name), right_, parameter_, join_)); } -std::unique_ptr LazyLastJoinTableHandler::GetIterator() { - auto iter = left_->GetIterator(); - if (!iter) { - return std::unique_ptr(); - } - - return std::unique_ptr(new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_)); -} -std::unique_ptr LazyLastJoinPartitionHandler::GetIterator() { +codec::RowIterator* LazyJoinPartitionHandler::GetRawIterator() { auto iter = left_->GetIterator(); if (!iter) { - return std::unique_ptr(); + return nullptr; } - return std::unique_ptr(new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_)); + return new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_); } -std::unique_ptr LazyLastJoinPartitionHandler::GetWindowIterator() { +std::unique_ptr LazyJoinPartitionHandler::GetWindowIterator() { auto wi = left_->GetWindowIterator(); if (wi == nullptr) { return std::unique_ptr(); } - return std::unique_ptr(new LazyLastJoinWindowIterator(std::move(wi), right_, parameter_, join_)); + return std::unique_ptr(new LazyJoinWindowIterator(std::move(wi), right_, parameter_, join_)); } const Row& LazyLastJoinIterator::GetValue() { @@ -140,29 +123,279 @@ const Row& LazyLastJoinIterator::GetValue() { return value_; } -std::unique_ptr LazyLastJoinTableHandler::GetWindowIterator(const std::string& idx_name) { - return nullptr; +codec::RowIterator* LazyJoinTableHandler::GetRawIterator() { + auto iter = left_->GetIterator(); + if (!iter) { + return {}; + } + + switch (join_->join_type_) { + case node::kJoinTypeLast: + return new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_); + case node::kJoinTypeLeft: + return new LazyLeftJoinIterator(std::move(iter), right_, parameter_, join_); + default: + return {}; + } } -LazyLastJoinWindowIterator::LazyLastJoinWindowIterator(std::unique_ptr&& iter, - std::shared_ptr right, const Row& param, - std::shared_ptr join) +LazyJoinWindowIterator::LazyJoinWindowIterator(std::unique_ptr&& iter, + std::shared_ptr right, const Row& param, + std::shared_ptr join) : left_(std::move(iter)), right_(right), parameter_(param), join_(join) {} -std::unique_ptr LazyLastJoinWindowIterator::GetValue() { + +codec::RowIterator* LazyJoinWindowIterator::GetRawValue() { auto iter = left_->GetValue(); if (!iter) { - return std::unique_ptr(); + return nullptr; } - return std::unique_ptr(new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_)); + switch (join_->join_type_) { + case node::kJoinTypeLast: + return new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_); + case node::kJoinTypeLeft: + return new LazyLeftJoinIterator(std::move(iter), right_, parameter_, join_); + default: + return {}; + } } -RowIterator* LazyLastJoinWindowIterator::GetRawValue() { - auto iter = left_->GetValue(); - if (!iter) { + +std::shared_ptr ConcatPartitionHandler::GetSegment(const std::string& key) { + auto left_seg = left_->GetSegment(key); + auto right_seg = right_->GetSegment(key); + return std::shared_ptr( + new SimpleConcatTableHandler(left_seg, left_slices_, right_seg, right_slices_)); +} + +RowIterator* ConcatPartitionHandler::GetRawIterator() { + auto li = left_->GetIterator(); + if (!li) { return nullptr; } + auto ri = right_->GetIterator(); + return new ConcatIterator(std::move(li), left_slices_, std::move(ri), right_slices_); +} + +std::unique_ptr LazyRequestUnionPartitionHandler::GetWindowIterator() { + auto w = left_->GetWindowIterator(); + if (!w) { + return {}; + } - return new LazyLastJoinIterator(std::move(iter), right_, parameter_, join_); + return std::unique_ptr(new LazyRequestUnionWindowIterator(std::move(w), func_)); +} + +std::shared_ptr LazyRequestUnionPartitionHandler::GetSegment(const std::string& key) { + return nullptr; +} + +const IndexHint& LazyRequestUnionPartitionHandler::GetIndex() { return left_->GetIndex(); } + +const Types& LazyRequestUnionPartitionHandler::GetTypes() { return left_->GetTypes(); } + +codec::RowIterator* LazyRequestUnionPartitionHandler::GetRawIterator() { return nullptr; } + +bool LazyAggIterator::Valid() const { return it_->Valid(); } +void LazyAggIterator::Next() { it_->Next(); } +const uint64_t& LazyAggIterator::GetKey() const { return it_->GetKey(); } +const Row& LazyAggIterator::GetValue() { + if (Valid()) { + auto request = it_->GetValue(); + auto window = func_(request); + if (window) { + buf_ = agg_gen_->Gen(parameter_, window); + return buf_; + } + } + + buf_ = Row(); + return buf_; +} + +void LazyAggIterator::Seek(const uint64_t& key) { it_->Seek(key); } +void LazyAggIterator::SeekToFirst() { it_->SeekToFirst(); } + +codec::RowIterator* LazyAggTableHandler::GetRawIterator() { + auto it = left_->GetIterator(); + if (!it) { + return nullptr; + } + return new LazyAggIterator(std::move(it), func_, agg_gen_, parameter_); +} + +const Types& LazyAggTableHandler::GetTypes() { return left_->GetTypes(); } +const IndexHint& LazyAggTableHandler::GetIndex() { return left_->GetIndex(); } +const Schema* LazyAggTableHandler::GetSchema() { return nullptr; } +const std::string& LazyAggTableHandler::GetName() { return left_->GetName(); } +const std::string& LazyAggTableHandler::GetDatabase() { return left_->GetDatabase(); } +std::shared_ptr LazyAggPartitionHandler::GetSegment(const std::string& key) { + auto seg = input_->Left()->GetSegment(key); + return std::shared_ptr(new LazyAggTableHandler(seg, input_->Func(), agg_gen_, parameter_)); +} +const std::string LazyAggPartitionHandler::GetHandlerTypeName() { return "LazyLastJoinPartitionHandler"; } + +codec::RowIterator* LazyAggPartitionHandler::GetRawIterator() { + auto it = input_->Left()->GetIterator(); + return new LazyAggIterator(std::move(it), input_->Func(), agg_gen_, parameter_); +} + +bool ConcatIterator::Valid() const { return left_ && left_->Valid(); } +void ConcatIterator::Next() { + left_->Next(); + if (right_ && right_->Valid()) { + right_->Next(); + } +} +const uint64_t& ConcatIterator::GetKey() const { return left_->GetKey(); } +const Row& ConcatIterator::GetValue() { + if (!right_ || !right_->Valid()) { + buf_ = Row(left_slices_, left_->GetValue(), right_slices_, Row()); + } else { + buf_ = Row(left_slices_, left_->GetValue(), right_slices_, right_->GetValue()); + } + return buf_; +} +void ConcatIterator::Seek(const uint64_t& key) { + left_->Seek(key); + if (right_ && right_->Valid()) { + right_->Seek(key); + } +} +void ConcatIterator::SeekToFirst() { + left_->SeekToFirst(); + if (right_) { + right_->SeekToFirst(); + } +} +RowIterator* SimpleConcatTableHandler::GetRawIterator() { + auto li = left_->GetIterator(); + if (!li) { + return nullptr; + } + auto ri = right_->GetIterator(); + return new ConcatIterator(std::move(li), left_slices_, std::move(ri), right_slices_); +} +std::unique_ptr ConcatPartitionHandler::GetWindowIterator() { return nullptr; } + +std::unique_ptr LazyAggPartitionHandler::GetWindowIterator() { + auto w = input_->Left()->GetWindowIterator(); + return std::unique_ptr( + new LazyAggWindowIterator(std::move(w), input_->Func(), agg_gen_, parameter_)); +} + +RowIterator* LazyAggWindowIterator::GetRawValue() { + auto w = left_->GetValue(); + if (!w) { + return nullptr; + } + + return new LazyAggIterator(std::move(w), func_, agg_gen_, parameter_); +} +void LazyRequestUnionIterator::Next() { + if (Valid()) { + cur_iter_->Next(); + } + if (!Valid()) { + left_->Next(); + OnNewRow(); + } +} +bool LazyRequestUnionIterator::Valid() const { return cur_iter_ && cur_iter_->Valid(); } +void LazyRequestUnionIterator::Seek(const uint64_t& key) { + left_->Seek(key); + OnNewRow(false); +} +void LazyRequestUnionIterator::SeekToFirst() { + left_->SeekToFirst(); + OnNewRow(); +} +void LazyRequestUnionIterator::OnNewRow(bool continue_on_empty) { + while (left_->Valid()) { + auto row = left_->GetValue(); + auto tb = func_(row); + if (tb) { + auto it = tb->GetIterator(); + if (it) { + it->SeekToFirst(); + if (it->Valid()) { + cur_window_ = tb; + cur_iter_ = std::move(it); + break; + } + } + } + + if (continue_on_empty) { + left_->Next(); + } else { + cur_window_ = {}; + cur_iter_ = {}; + break; + } + } +} +const uint64_t& LazyRequestUnionIterator::GetKey() const { return cur_iter_->GetKey(); } +const Row& LazyRequestUnionIterator::GetValue() { return cur_iter_->GetValue(); } +RowIterator* LazyRequestUnionWindowIterator::GetRawValue() { + auto rows = left_->GetValue(); + if (!rows) { + return {}; + } + + return new LazyRequestUnionIterator(std::move(rows), func_); +} +bool LazyRequestUnionWindowIterator::Valid() { return left_ && left_->Valid(); } +const Row LazyRequestUnionWindowIterator::GetKey() { return left_->GetKey(); } +void LazyRequestUnionWindowIterator::SeekToFirst() { left_->SeekToFirst(); } +void LazyRequestUnionWindowIterator::Seek(const std::string& key) { left_->Seek(key); } +void LazyRequestUnionWindowIterator::Next() { left_->Next(); } +const std::string LazyJoinPartitionHandler::GetHandlerTypeName() { + return "LazyJoinPartitionHandler(" + node::JoinTypeName(join_->join_type_) + ")"; +} +const std::string LazyJoinTableHandler::GetHandlerTypeName() { + return "LazyJoinTableHandler(" + node::JoinTypeName(join_->join_type_) + ")"; +} +void LazyLeftJoinIterator::Next() { + if (right_it_ && right_it_->Valid()) { + right_it_->Next(); + auto res = join_->RowJoinIterator(left_value_, right_it_, parameter_); + matches_right_ |= res.second; + if (matches_right_ && !right_it_->Valid()) { + // matched from right somewhere, skip the NULL match + left_it_->Next(); + onNewLeftRow(); + } else { + // RowJoinIterator returns NULL match by default + value_ = res.first; + } + } else { + left_it_->Next(); + onNewLeftRow(); + } +} +void LazyLeftJoinIterator::onNewLeftRow() { + // reset + right_it_ = nullptr; + left_value_ = Row(); + value_ = Row(); + matches_right_ = false; + + if (!left_it_->Valid()) { + // end of iterator + return; + } + + left_value_ = left_it_->GetValue(); + if (right_partition_) { + right_it_ = join_->InitRight(left_value_, right_partition_, parameter_); + } else { + right_it_ = right_->GetIterator(); + right_it_->SeekToFirst(); + } + + auto res = join_->RowJoinIterator(left_value_, right_it_, parameter_); + value_ = res.first; + matches_right_ |= res.second; } } // namespace vm } // namespace hybridse diff --git a/hybridse/src/vm/catalog_wrapper.h b/hybridse/src/vm/catalog_wrapper.h index 11441b4bf54..bfd1265aa82 100644 --- a/hybridse/src/vm/catalog_wrapper.h +++ b/hybridse/src/vm/catalog_wrapper.h @@ -17,10 +17,13 @@ #ifndef HYBRIDSE_SRC_VM_CATALOG_WRAPPER_H_ #define HYBRIDSE_SRC_VM_CATALOG_WRAPPER_H_ +#include #include #include #include +#include "absl/base/attributes.h" +#include "codec/row_iterator.h" #include "vm/catalog.h" #include "vm/generator.h" @@ -142,15 +145,6 @@ class WindowIteratorProjectWrapper : public WindowIterator { const ProjectFun* fun) : WindowIterator(), iter_(std::move(iter)), parameter_(parameter), fun_(fun) {} virtual ~WindowIteratorProjectWrapper() {} - std::unique_ptr GetValue() override { - auto iter = iter_->GetValue(); - if (!iter) { - return std::unique_ptr(); - } else { - return std::unique_ptr( - new IteratorProjectWrapper(std::move(iter), parameter_, fun_)); - } - } RowIterator* GetRawValue() override { auto iter = iter_->GetValue(); if (!iter) { @@ -176,15 +170,6 @@ class WindowIteratorFilterWrapper : public WindowIterator { const PredicateFun* fun) : WindowIterator(), iter_(std::move(iter)), parameter_(parameter), fun_(fun) {} virtual ~WindowIteratorFilterWrapper() {} - std::unique_ptr GetValue() override { - auto iter = iter_->GetValue(); - if (!iter) { - return std::unique_ptr(); - } else { - return std::unique_ptr( - new IteratorFilterWrapper(std::move(iter), parameter_, fun_)); - } - } RowIterator* GetRawValue() override { auto iter = iter_->GetValue(); if (!iter) { @@ -240,16 +225,7 @@ class PartitionProjectWrapper : public PartitionHandler { const std::string& GetDatabase() override { return partition_handler_->GetDatabase(); } - std::unique_ptr> GetIterator() override { - auto iter = partition_handler_->GetIterator(); - if (!iter) { - return std::unique_ptr(); - } else { - return std::unique_ptr( - new IteratorProjectWrapper(std::move(iter), parameter_, fun_)); - } - } - base::ConstIterator* GetRawIterator() override; + codec::RowIterator* GetRawIterator() override; Row At(uint64_t pos) override { value_ = fun_->operator()(partition_handler_->At(pos), parameter_); return value_; @@ -303,16 +279,8 @@ class PartitionFilterWrapper : public PartitionHandler { const std::string& GetDatabase() override { return partition_handler_->GetDatabase(); } - std::unique_ptr> GetIterator() override { - auto iter = partition_handler_->GetIterator(); - if (!iter) { - return std::unique_ptr>(); - } else { - return std::unique_ptr( - new IteratorFilterWrapper(std::move(iter), parameter_, fun_)); - } - } - base::ConstIterator* GetRawIterator() override; + + codec::RowIterator* GetRawIterator() override; std::shared_ptr GetSegment(const std::string& key) override; @@ -334,15 +302,6 @@ class TableProjectWrapper : public TableHandler { : TableHandler(), table_hander_(table_handler), parameter_(parameter), value_(), fun_(fun) {} virtual ~TableProjectWrapper() {} - std::unique_ptr GetIterator() override { - auto iter = table_hander_->GetIterator(); - if (!iter) { - return std::unique_ptr(); - } else { - return std::unique_ptr( - new IteratorProjectWrapper(std::move(iter), parameter_, fun_)); - } - } const Types& GetTypes() override { return table_hander_->GetTypes(); } const IndexHint& GetIndex() override { return table_hander_->GetIndex(); } std::unique_ptr GetWindowIterator( @@ -360,7 +319,7 @@ class TableProjectWrapper : public TableHandler { const std::string& GetDatabase() override { return table_hander_->GetDatabase(); } - base::ConstIterator* GetRawIterator() override { + codec::RowIterator* GetRawIterator() override { auto iter = table_hander_->GetIterator(); if (!iter) { return nullptr; @@ -389,14 +348,6 @@ class TableFilterWrapper : public TableHandler { : TableHandler(), table_hander_(table_handler), parameter_(parameter), fun_(fun) {} virtual ~TableFilterWrapper() {} - std::unique_ptr GetIterator() override { - auto iter = table_hander_->GetIterator(); - if (!iter) { - return std::unique_ptr(); - } else { - return std::make_unique(std::move(iter), parameter_, fun_); - } - } const Types& GetTypes() override { return table_hander_->GetTypes(); } const IndexHint& GetIndex() override { return table_hander_->GetIndex(); } @@ -412,9 +363,13 @@ class TableFilterWrapper : public TableHandler { const Schema* GetSchema() override { return table_hander_->GetSchema(); } const std::string& GetName() override { return table_hander_->GetName(); } const std::string& GetDatabase() override { return table_hander_->GetDatabase(); } - base::ConstIterator* GetRawIterator() override { - return new IteratorFilterWrapper(static_cast>(table_hander_->GetRawIterator()), - parameter_, fun_); + codec::RowIterator* GetRawIterator() override { + auto iter = table_hander_->GetIterator(); + if (!iter) { + return nullptr; + } else { + return new IteratorFilterWrapper(std::move(iter), parameter_, fun_); + } } std::shared_ptr GetPartition(const std::string& index_name) override; const OrderType GetOrderType() const override { return table_hander_->GetOrderType(); } @@ -426,29 +381,25 @@ class TableFilterWrapper : public TableHandler { const PredicateFun* fun_; }; -class LimitTableHandler : public TableHandler { +class LimitTableHandler final : public TableHandler { public: explicit LimitTableHandler(std::shared_ptr table, int32_t limit) : TableHandler(), table_hander_(table), limit_(limit) {} virtual ~LimitTableHandler() {} - std::unique_ptr GetIterator() override { - auto iter = table_hander_->GetIterator(); - if (!iter) { - return std::unique_ptr(); - } else { - return std::make_unique(std::move(iter), limit_); - } - } - // FIXME(ace): do not use this, not implemented std::unique_ptr GetWindowIterator(const std::string& idx_name) override { LOG(ERROR) << "window iterator for LimitTableHandler is not implemented, don't use"; return table_hander_->GetWindowIterator(idx_name); } - base::ConstIterator* GetRawIterator() override { - return new LimitIterator(static_cast>(table_hander_->GetRawIterator()), limit_); + codec::RowIterator* GetRawIterator() override { + auto iter = table_hander_->GetIterator(); + if (!iter) { + return nullptr; + } else { + return new LimitIterator(std::move(iter), limit_); + } } const Types& GetTypes() override { return table_hander_->GetTypes(); } @@ -562,10 +513,15 @@ class RowCombineWrapper : public RowHandler { const ProjectFun* fun_; }; +// Last Join iterator on demand +// for request mode, right source must be a PartitionHandler class LazyLastJoinIterator : public RowIterator { public: - LazyLastJoinIterator(std::unique_ptr&& left, std::shared_ptr right, const Row& param, - std::shared_ptr join); + LazyLastJoinIterator(std::unique_ptr&& left, std::shared_ptr right, const Row& param, + std::shared_ptr join) ABSL_ATTRIBUTE_NONNULL() + : left_it_(std::move(left)), right_(right), parameter_(param), join_(join) { + SeekToFirst(); + } ~LazyLastJoinIterator() override {} @@ -582,30 +538,82 @@ class LazyLastJoinIterator : public RowIterator { private: std::unique_ptr left_it_; - std::shared_ptr right_; + std::shared_ptr right_; const Row& parameter_; std::shared_ptr join_; Row value_; }; +class LazyLeftJoinIterator : public RowIterator { + public: + LazyLeftJoinIterator(std::unique_ptr&& left, std::shared_ptr right, const Row& param, + std::shared_ptr join) + : left_it_(std::move(left)), right_(right), parameter_(param), join_(join) { + if (right_->GetHandlerType() == kPartitionHandler) { + right_partition_ = std::dynamic_pointer_cast(right_); + } + SeekToFirst(); + } + + ~LazyLeftJoinIterator() override {} + + bool Valid() const override { return left_it_->Valid(); } + + // actual compute performed here, left_it_ and right_it_ is updated to the next position of join + void Next() override; + + const uint64_t& GetKey() const override { + return left_it_->GetKey(); + } -class LazyLastJoinPartitionHandler final : public PartitionHandler { + const Row& GetValue() override { + return value_; + } + + bool IsSeekable() const override { return true; }; + + void Seek(const uint64_t& key) override { + left_it_->Seek(key); + onNewLeftRow(); + } + + void SeekToFirst() override { + left_it_->SeekToFirst(); + onNewLeftRow(); + } + + private: + // left_value_ changed, update right_it_ based on join condition + void onNewLeftRow(); + + std::unique_ptr left_it_; + std::shared_ptr right_; + std::shared_ptr right_partition_; + const Row parameter_; + std::shared_ptr join_; + + // whether current left row has any rows from right joined, left join fallback to NULL if non matches + bool matches_right_ = false; + std::unique_ptr right_it_; + Row left_value_; + Row value_; +}; + +class LazyJoinPartitionHandler final : public PartitionHandler { public: - LazyLastJoinPartitionHandler(std::shared_ptr left, std::shared_ptr right, - const Row& param, std::shared_ptr join); - ~LazyLastJoinPartitionHandler() override {} + LazyJoinPartitionHandler(std::shared_ptr left, std::shared_ptr right, + const Row& param, std::shared_ptr join); + ~LazyJoinPartitionHandler() override {} // NOTE: only support get segement by key from left source std::shared_ptr GetSegment(const std::string& key) override; - const std::string GetHandlerTypeName() override { - return "LazyLastJoinPartitionHandler"; - } - - std::unique_ptr GetIterator() override; + const std::string GetHandlerTypeName() override; std::unique_ptr GetWindowIterator() override; + codec::RowIterator* GetRawIterator() override; + const IndexHint& GetIndex() override { return left_->GetIndex(); } // unimplemented @@ -613,54 +621,36 @@ class LazyLastJoinPartitionHandler final : public PartitionHandler { // unimplemented const Schema* GetSchema() override { return nullptr; } - const std::string& GetName() override { return name_; } - const std::string& GetDatabase() override { return db_; } - - // unimplemented - base::ConstIterator* GetRawIterator() override { - return nullptr; - } + const std::string& GetName() override { return left_->GetName(); } + const std::string& GetDatabase() override { return left_->GetDatabase(); } private: std::shared_ptr left_; - std::shared_ptr right_; + std::shared_ptr right_; const Row& parameter_; std::shared_ptr join_; - - std::string name_ = ""; - std::string db_ = ""; }; -class LazyLastJoinTableHandler final : public TableHandler { +class LazyJoinTableHandler final : public TableHandler { public: - LazyLastJoinTableHandler(std::shared_ptr left, std::shared_ptr right, - const Row& param, std::shared_ptr join); - ~LazyLastJoinTableHandler() override {} + LazyJoinTableHandler(std::shared_ptr left, std::shared_ptr right, const Row& param, + std::shared_ptr join) + : left_(left), right_(right), parameter_(param), join_(join) { + } - std::unique_ptr GetIterator() override; + ~LazyJoinTableHandler() override {} // unimplemented const Types& GetTypes() override { return left_->GetTypes(); } const IndexHint& GetIndex() override { return left_->GetIndex(); } - // unimplemented - std::unique_ptr GetWindowIterator(const std::string& idx_name) override; - // unimplemented const Schema* GetSchema() override { return nullptr; } - const std::string& GetName() override { return name_; } - const std::string& GetDatabase() override { return db_; } + const std::string& GetName() override { return left_->GetName(); } + const std::string& GetDatabase() override { return left_->GetDatabase(); } - base::ConstIterator* GetRawIterator() override { - // unimplemented - return nullptr; - } - - Row At(uint64_t pos) override { - // unimplemented - return value_; - } + codec::RowIterator* GetRawIterator() override; const uint64_t GetCount() override { return left_->GetCount(); } @@ -668,29 +658,183 @@ class LazyLastJoinTableHandler final : public TableHandler { const OrderType GetOrderType() const override { return left_->GetOrderType(); } - const std::string GetHandlerTypeName() override { - return "LazyLastJoinTableHandler"; - } + const std::string GetHandlerTypeName() override; private: std::shared_ptr left_; - std::shared_ptr right_; + std::shared_ptr right_; + const Row parameter_; + std::shared_ptr join_; +}; + +class LazyJoinWindowIterator final : public codec::WindowIterator { + public: + LazyJoinWindowIterator(std::unique_ptr&& iter, std::shared_ptr right, const Row& param, + std::shared_ptr join); + + ~LazyJoinWindowIterator() override {} + + codec::RowIterator* GetRawValue() override; + + void Seek(const std::string& key) override { left_->Seek(key); } + void SeekToFirst() override { left_->SeekToFirst(); } + void Next() override { left_->Next(); } + bool Valid() override { return left_ && left_->Valid(); } + const Row GetKey() override { return left_->GetKey(); } + + std::shared_ptr left_; + std::shared_ptr right_; const Row& parameter_; std::shared_ptr join_; +}; - Row value_; - std::string name_ = ""; - std::string db_ = ""; +class LazyRequestUnionIterator final : public RowIterator { + public: + LazyRequestUnionIterator(std::unique_ptr&& left, + std::function(const Row&)> func) + : left_(std::move(left)), func_(func) { + SeekToFirst(); + } + ~LazyRequestUnionIterator() override {} + + bool Valid() const override; + void Next() override; + const uint64_t& GetKey() const override; + const Row& GetValue() override; + bool IsSeekable() const override { return true; } + + void Seek(const uint64_t& key) override; + void SeekToFirst() override; + + private: + void OnNewRow(bool continue_on_empty = true); + + private: + // all same keys from left form a window, although it is better that every row be a partition + std::unique_ptr left_; + std::function(const Row&)> func_; + + std::shared_ptr cur_window_; + std::unique_ptr cur_iter_; +}; + +class LazyRequestUnionWindowIterator final : public codec::WindowIterator { + public: + LazyRequestUnionWindowIterator(std::unique_ptr&& left, + std::function(const Row&)> func) + : left_(std::move(left)), func_(func) { + SeekToFirst(); + } + ~LazyRequestUnionWindowIterator() override {} + + RowIterator* GetRawValue() override; + + void Seek(const std::string& key) override; + void SeekToFirst() override; + void Next() override; + bool Valid() override; + const Row GetKey() override; + + private: + std::unique_ptr left_; + std::function(const Row&)> func_; +}; + +class LazyRequestUnionPartitionHandler final : public PartitionHandler { + public: + LazyRequestUnionPartitionHandler(std::shared_ptr left, + std::function(const Row&)> func) + : left_(left), func_(func) {} + ~LazyRequestUnionPartitionHandler() override {} + + std::unique_ptr GetWindowIterator() override; + + std::shared_ptr GetSegment(const std::string& key) override; + + const std::string GetHandlerTypeName() override { return "LazyRequestUnionPartitiontHandler"; } + + codec::RowIterator* GetRawIterator() override; + + const IndexHint& GetIndex() override; + + // unimplemented + const Types& GetTypes() override; + + // unimplemented + const Schema* GetSchema() override { return nullptr; } + const std::string& GetName() override { return left_->GetName(); } + const std::string& GetDatabase() override { return left_->GetDatabase(); } + + auto Left() const { return left_; } + auto Func() const { return func_; } + + private: + std::shared_ptr left_; + std::function(const Row&)> func_; +}; + +class LazyAggIterator final : public RowIterator { + public: + LazyAggIterator(std::unique_ptr&& it, std::function(const Row&)> func, + std::shared_ptr agg_gen, const Row& param) + : it_(std::move(it)), func_(func), agg_gen_(agg_gen), parameter_(param) { + SeekToFirst(); + } + + ~LazyAggIterator() override {} + + bool Valid() const override; + void Next() override; + const uint64_t& GetKey() const override; + const Row& GetValue() override; + bool IsSeekable() const override { return true; } + + void Seek(const uint64_t& key) override; + void SeekToFirst() override; + + private: + std::unique_ptr it_; + std::function(const Row&)> func_; + std::shared_ptr agg_gen_; + const Row& parameter_; + + Row buf_; }; -class LazyLastJoinWindowIterator final : public codec::WindowIterator { +class LazyAggTableHandler final : public TableHandler { public: - LazyLastJoinWindowIterator(std::unique_ptr&& iter, std::shared_ptr right, - const Row& param, std::shared_ptr join); + LazyAggTableHandler(std::shared_ptr left, + std::function(const Row&)> func, + std::shared_ptr agg_gen, const Row& param) + : left_(left), func_(func), agg_gen_(agg_gen), parameter_(param) { + DLOG(INFO) << "iterator count = " << left_->GetCount(); + } + ~LazyAggTableHandler() override {} + + RowIterator* GetRawIterator() override; + + // unimplemented + const Types& GetTypes() override; + const IndexHint& GetIndex() override; + const Schema* GetSchema() override; + const std::string& GetName() override; + const std::string& GetDatabase() override; - ~LazyLastJoinWindowIterator() override {} + private: + std::shared_ptr left_; + std::function(const Row&)> func_; + std::shared_ptr agg_gen_; + const Row& parameter_; +}; + +class LazyAggWindowIterator final : public codec::WindowIterator { + public: + LazyAggWindowIterator(std::unique_ptr left, + std::function(const Row&)> func, + std::shared_ptr gen, const Row& p) + : left_(std::move(left)), func_(func), agg_gen_(gen), parameter_(p) {} + ~LazyAggWindowIterator() override {} - std::unique_ptr GetValue() override; RowIterator* GetRawValue() override; void Seek(const std::string& key) override { left_->Seek(key); } @@ -699,10 +843,123 @@ class LazyLastJoinWindowIterator final : public codec::WindowIterator { bool Valid() override { return left_ && left_->Valid(); } const Row GetKey() override { return left_->GetKey(); } - std::shared_ptr left_; - std::shared_ptr right_; + private: + std::unique_ptr left_; + std::function(const Row&)> func_; + std::shared_ptr agg_gen_; const Row& parameter_; - std::shared_ptr join_; +}; + +class LazyAggPartitionHandler final : public PartitionHandler { + public: + LazyAggPartitionHandler(std::shared_ptr input, + std::shared_ptr agg_gen, const Row& param) + : input_(input), agg_gen_(agg_gen), parameter_(param) {} + ~LazyAggPartitionHandler() override {} + + std::shared_ptr GetSegment(const std::string& key) override; + + const std::string GetHandlerTypeName() override; + + codec::RowIterator* GetRawIterator() override; + + std::unique_ptr GetWindowIterator() override; + + const IndexHint& GetIndex() override { return input_->GetIndex(); } + + // unimplemented + const Types& GetTypes() override { return input_->GetTypes(); } + const Schema* GetSchema() override { return nullptr; } + const std::string& GetName() override { return input_->GetName(); } + const std::string& GetDatabase() override { return input_->GetDatabase(); } + + private: + std::shared_ptr input_; + std::shared_ptr agg_gen_; + const Row& parameter_; +}; + +class ConcatIterator final : public RowIterator { + public: + ConcatIterator(std::unique_ptr&& left, size_t left_slices, std::unique_ptr&& right, + size_t right_slices) + : left_(std::move(left)), left_slices_(left_slices), right_(std::move(right)), right_slices_(right_slices) { + SeekToFirst(); + } + ~ConcatIterator() override {} + + bool Valid() const override; + void Next() override; + const uint64_t& GetKey() const override; + const Row& GetValue() override; + + bool IsSeekable() const override { return true; }; + + void Seek(const uint64_t& key) override; + + void SeekToFirst() override; + + private: + std::unique_ptr left_; + size_t left_slices_; + std::unique_ptr right_; + size_t right_slices_; + + Row buf_; +}; + +class SimpleConcatTableHandler final : public TableHandler { + public: + SimpleConcatTableHandler(std::shared_ptr left, size_t left_slices, + std::shared_ptr right, size_t right_slices) + : left_(left), left_slices_(left_slices), right_(right), right_slices_(right_slices) {} + ~SimpleConcatTableHandler() override {} + + RowIterator* GetRawIterator() override; + + const Types& GetTypes() override { return left_->GetTypes(); } + + const IndexHint& GetIndex() override { return left_->GetIndex(); } + + // unimplemented + const Schema* GetSchema() override { return left_->GetSchema(); } + const std::string& GetName() override { return left_->GetName(); } + const std::string& GetDatabase() override { return left_->GetDatabase(); } + + private: + std::shared_ptr left_; + size_t left_slices_; + std::shared_ptr right_; + size_t right_slices_; +}; + +class ConcatPartitionHandler final : public PartitionHandler { + public: + ConcatPartitionHandler(std::shared_ptr left, size_t left_slices, + std::shared_ptr right, size_t right_slices) + : left_(left), left_slices_(left_slices), right_(right), right_slices_(right_slices) {} + ~ConcatPartitionHandler() override {} + + RowIterator* GetRawIterator() override; + + std::unique_ptr GetWindowIterator() override; + + std::shared_ptr GetSegment(const std::string& key) override; + + const Types& GetTypes() override { return left_->GetTypes(); } + + const IndexHint& GetIndex() override { return left_->GetIndex(); } + + // unimplemented + const Schema* GetSchema() override { return nullptr; } + const std::string& GetName() override { return left_->GetName(); } + const std::string& GetDatabase() override { return left_->GetDatabase(); } + + private: + std::shared_ptr left_; + size_t left_slices_; + std::shared_ptr right_; + size_t right_slices_; }; } // namespace vm diff --git a/hybridse/src/vm/cluster_task.cc b/hybridse/src/vm/cluster_task.cc new file mode 100644 index 00000000000..25b4afb1281 --- /dev/null +++ b/hybridse/src/vm/cluster_task.cc @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "vm/cluster_task.h" + +namespace hybridse { +namespace vm { +const bool RouteInfo::IsCompleted() const { return table_handler_ && !index_.empty() && index_key_.ValidKey(); } +const bool RouteInfo::EqualWith(const RouteInfo& info1, const RouteInfo& info2) { + return info1.input_ == info2.input_ && info1.table_handler_ == info2.table_handler_ && + info1.index_ == info2.index_ && node::ExprEquals(info1.index_key_.keys_, info2.index_key_.keys_); +} +const std::string RouteInfo::ToString() const { + if (IsCompleted()) { + std::ostringstream oss; + if (lazy_route_) { + oss << "[LAZY]"; + } + oss << ", routing index = " << table_handler_->GetDatabase() << "." << table_handler_->GetName() << "." + << index_ << ", " << index_key_.ToString(); + return oss.str(); + } else { + return ""; + } +} +const bool RouteInfo::IsCluster() const { return table_handler_ && !index_.empty(); } +void ClusterTask::Print(std::ostream& output, const std::string& tab) const { + output << route_info_.ToString() << "\n"; + if (nullptr == root_) { + output << tab << "NULL RUNNER\n"; + } else { + std::set visited_ids; + root_->Print(output, tab, &visited_ids); + } +} +void ClusterTask::ResetInputs(std::shared_ptr input) { + for (auto input_runner : input_runners_) { + input_runner->SetProducer(0, route_info_.input_->GetRoot()); + } + route_info_.index_key_input_runner_ = route_info_.input_->GetRoot(); + route_info_.input_ = input; +} +Runner* ClusterTask::GetInputRunner(size_t idx) const { + return idx >= input_runners_.size() ? nullptr : input_runners_[idx]; +} +const bool ClusterTask::TaskCanBeMerge(const ClusterTask& task1, const ClusterTask& task2) { + return RouteInfo::EqualWith(task1.route_info_, task2.route_info_); +} +const ClusterTask ClusterTask::TaskMerge(Runner* root, const ClusterTask& task1, const ClusterTask& task2) { + return TaskMergeToLeft(root, task1, task2); +} +const ClusterTask ClusterTask::TaskMergeToLeft(Runner* root, const ClusterTask& task1, const ClusterTask& task2) { + std::vector input_runners; + for (auto runner : task1.input_runners_) { + input_runners.push_back(runner); + } + for (auto runner : task2.input_runners_) { + input_runners.push_back(runner); + } + return ClusterTask(root, input_runners, task1.route_info_); +} +const ClusterTask ClusterTask::TaskMergeToRight(Runner* root, const ClusterTask& task1, const ClusterTask& task2) { + std::vector input_runners; + for (auto runner : task1.input_runners_) { + input_runners.push_back(runner); + } + for (auto runner : task2.input_runners_) { + input_runners.push_back(runner); + } + return ClusterTask(root, input_runners, task2.route_info_); +} +const Runner* ClusterTask::GetRequestInput(const ClusterTask& task) { + if (!task.IsValid()) { + return nullptr; + } + auto input_task = task.GetInput(); + if (input_task) { + return input_task->GetRoot(); + } + return nullptr; +} +ClusterTask ClusterJob::GetTask(int32_t id) { + if (id < 0 || id >= static_cast(tasks_.size())) { + LOG(WARNING) << "fail get task: task " << id << " not exist"; + return ClusterTask(); + } + return tasks_[id]; +} +int32_t ClusterJob::AddTask(const ClusterTask& task) { + if (!task.IsValid()) { + LOG(WARNING) << "fail to add invalid task"; + return -1; + } + tasks_.push_back(task); + return tasks_.size() - 1; +} +bool ClusterJob::AddRunnerToTask(Runner* runner, const int32_t id) { + if (id < 0 || id >= static_cast(tasks_.size())) { + LOG(WARNING) << "fail update task: task " << id << " not exist"; + return false; + } + runner->AddProducer(tasks_[id].GetRoot()); + tasks_[id].SetRoot(runner); + return true; +} +void ClusterJob::Print(std::ostream& output, const std::string& tab) const { + if (tasks_.empty()) { + output << "EMPTY CLUSTER JOB\n"; + return; + } + for (size_t i = 0; i < tasks_.size(); i++) { + if (main_task_id_ == static_cast(i)) { + output << "MAIN TASK ID " << i; + } else { + output << "TASK ID " << i; + } + tasks_[i].Print(output, tab); + output << "\n"; + } +} +void ClusterJob::Print() const { this->Print(std::cout, " "); } +} // namespace vm +} // namespace hybridse diff --git a/hybridse/src/vm/cluster_task.h b/hybridse/src/vm/cluster_task.h new file mode 100644 index 00000000000..6b34d2a55d3 --- /dev/null +++ b/hybridse/src/vm/cluster_task.h @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HYBRIDSE_SRC_VM_CLUSTER_TASK_H_ +#define HYBRIDSE_SRC_VM_CLUSTER_TASK_H_ + +#include +#include +#include +#include + +#include "vm/catalog.h" +#include "vm/physical_op.h" +#include "vm/runner.h" + +namespace hybridse { +namespace vm { + +class ClusterTask; + +class RouteInfo { + public: + RouteInfo() + : index_(), + index_key_(), + index_key_input_runner_(nullptr), + input_(), + table_handler_() {} + RouteInfo(const std::string index, + std::shared_ptr table_handler) + : index_(index), + index_key_(), + index_key_input_runner_(nullptr), + input_(), + table_handler_(table_handler) {} + RouteInfo(const std::string index, const Key& index_key, + std::shared_ptr input, + std::shared_ptr table_handler) + : index_(index), + index_key_(index_key), + index_key_input_runner_(nullptr), + input_(input), + table_handler_(table_handler) {} + ~RouteInfo() {} + const bool IsCompleted() const; + const bool IsCluster() const; + static const bool EqualWith(const RouteInfo& info1, const RouteInfo& info2); + + const std::string ToString() const; + std::string index_; + Key index_key_; + Runner* index_key_input_runner_; + std::shared_ptr input_; + std::shared_ptr table_handler_; + + // if true: generate the complete ClusterTask only when requires + bool lazy_route_ = false; +}; + +// task info of cluster job +// partitoin/index info +// index key generator +// request generator +class ClusterTask { + public: + // common tasks + ClusterTask() : root_(nullptr), input_runners_(), route_info_() {} + explicit ClusterTask(Runner* root) + : root_(root), input_runners_(), route_info_() {} + + // cluster task with explicit routeinfo + ClusterTask(Runner* root, const std::shared_ptr table_handler, + std::string index) + : root_(root), input_runners_(), route_info_(index, table_handler) {} + ClusterTask(Runner* root, const std::vector& input_runners, + const RouteInfo& route_info) + : root_(root), input_runners_(input_runners), route_info_(route_info) {} + ~ClusterTask() {} + + void Print(std::ostream& output, const std::string& tab) const; + + friend std::ostream& operator<<(std::ostream& os, const ClusterTask& output) { + output.Print(os, ""); + return os; + } + + void ResetInputs(std::shared_ptr input); + Runner* GetRoot() const { return root_; } + void SetRoot(Runner* root) { root_ = root; } + Runner* GetInputRunner(size_t idx) const; + Runner* GetIndexKeyInput() const { + return route_info_.index_key_input_runner_; + } + std::shared_ptr GetInput() const { return route_info_.input_; } + Key GetIndexKey() const { return route_info_.index_key_; } + void SetIndexKey(const Key& key) { route_info_.index_key_ = key; } + void SetInput(std::shared_ptr input) { + route_info_.input_ = input; + } + + const bool IsValid() const { return nullptr != root_; } + + const bool IsCompletedClusterTask() const { + return IsValid() && route_info_.IsCompleted(); + } + const bool IsUnCompletedClusterTask() const { + return IsClusterTask() && !route_info_.IsCompleted(); + } + const bool IsClusterTask() const { return route_info_.IsCluster(); } + const std::string& index() { return route_info_.index_; } + std::shared_ptr table_handler() { + return route_info_.table_handler_; + } + + // Cluster tasks with same input runners and index keys can be merged + static const bool TaskCanBeMerge(const ClusterTask& task1, const ClusterTask& task2); + static const ClusterTask TaskMerge(Runner* root, const ClusterTask& task1, const ClusterTask& task2); + static const ClusterTask TaskMergeToLeft(Runner* root, const ClusterTask& task1, const ClusterTask& task2); + static const ClusterTask TaskMergeToRight(Runner* root, const ClusterTask& task1, const ClusterTask& task2); + static const Runner* GetRequestInput(const ClusterTask& task); + + const RouteInfo& GetRouteInfo() const { return route_info_; } + + protected: + Runner* root_; + std::vector input_runners_; + RouteInfo route_info_; +}; + +class ClusterJob { + public: + ClusterJob() + : tasks_(), main_task_id_(-1), sql_(""), common_column_indices_() {} + explicit ClusterJob(const std::string& sql, const std::string& db, + const std::set& common_column_indices) + : tasks_(), + main_task_id_(-1), + sql_(sql), + db_(db), + common_column_indices_(common_column_indices) {} + ClusterTask GetTask(int32_t id); + + ClusterTask GetMainTask() { return GetTask(main_task_id_); } + int32_t AddTask(const ClusterTask& task); + bool AddRunnerToTask(Runner* runner, const int32_t id); + + void AddMainTask(const ClusterTask& task) { main_task_id_ = AddTask(task); } + void Reset() { tasks_.clear(); } + const size_t GetTaskSize() const { return tasks_.size(); } + const bool IsValid() const { return !tasks_.empty(); } + const int32_t main_task_id() const { return main_task_id_; } + const std::string& sql() const { return sql_; } + const std::string& db() const { return db_; } + const std::set& common_column_indices() const { return common_column_indices_; } + void Print(std::ostream& output, const std::string& tab) const; + void Print() const; + + private: + std::vector tasks_; + int32_t main_task_id_; + std::string sql_; + std::string db_; + std::set common_column_indices_; +}; + +} // namespace vm +} // namespace hybridse + +#endif // HYBRIDSE_SRC_VM_CLUSTER_TASK_H_ diff --git a/hybridse/src/vm/engine.cc b/hybridse/src/vm/engine.cc index 6fd0f14a904..97eae8a9062 100644 --- a/hybridse/src/vm/engine.cc +++ b/hybridse/src/vm/engine.cc @@ -18,13 +18,8 @@ #include #include #include -#include "base/fe_strings.h" #include "boost/none.hpp" -#include "boost/optional.hpp" #include "codec/fe_row_codec.h" -#include "codec/fe_schema_codec.h" -#include "codec/list_iterator_codec.h" -#include "codegen/buf_ir_builder.h" #include "gflags/gflags.h" #include "llvm-c/Target.h" #include "udf/default_udf_library.h" @@ -32,6 +27,7 @@ #include "vm/mem_catalog.h" #include "vm/sql_compiler.h" #include "vm/internal/node_helper.h" +#include "vm/runner_ctx.h" DECLARE_bool(enable_spark_unsaferow_format); @@ -94,27 +90,10 @@ bool Engine::GetDependentTables(const std::string& sql, const std::string& db, return false; } - status = GetDependentTables(physical_plan, db_tables); + status = internal::GetDependentTables(physical_plan, db_tables); return status.isOK(); } -Status Engine::GetDependentTables(const PhysicalOpNode* root, std::set>* db_tbs) { - using OUT = std::set>; - *db_tbs = internal::ReduceNode( - root, OUT{}, - [](OUT init, const PhysicalOpNode* node) { - if (node->GetOpType() == kPhysicalOpDataProvider) { - auto* data_op = dynamic_cast(node); - if (data_op != nullptr) { - init.emplace(data_op->GetDb(), data_op->GetName()); - } - } - return init; - }, - [](const PhysicalOpNode* node) { return node->GetDependents(); }); - return Status::OK(); -} - bool Engine::IsCompatibleCache(RunSession& session, // NOLINT std::shared_ptr info, base::Status& status) { // NOLINT @@ -170,7 +149,7 @@ bool Engine::Get(const std::string& sql, const std::string& db, RunSession& sess DLOG(INFO) << "Compile Engine ..."; status = base::Status::OK(); std::shared_ptr info = std::make_shared(); - auto& sql_context = std::dynamic_pointer_cast(info)->get_sql_context(); + auto& sql_context = info->get_sql_context(); sql_context.sql = sql; sql_context.db = db; sql_context.engine_mode = session.engine_mode(); @@ -271,7 +250,7 @@ bool Engine::Explain(const std::string& sql, const std::string& db, EngineMode e explain_output->request_db_name = ctx.request_db_name; explain_output->limit_cnt = ctx.limit_cnt; - auto s = GetDependentTables(ctx.physical_plan, &explain_output->dependent_tables); + auto s = internal::GetDependentTables(ctx.physical_plan, &explain_output->dependent_tables); if (!s.isOK()) { LOG(ERROR) << s; status->code = common::kPhysicalPlanError; diff --git a/hybridse/src/vm/engine_compile_test.cc b/hybridse/src/vm/engine_compile_test.cc index d338a9176b0..b4a7c715f9b 100644 --- a/hybridse/src/vm/engine_compile_test.cc +++ b/hybridse/src/vm/engine_compile_test.cc @@ -251,13 +251,8 @@ TEST_F(EngineCompileTest, EngineCompileOnlyTest) { { std::vector sql_str_list = { - "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 full join t2 on " - "t1.col1 = t2.col2;", "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 left join t2 on " "t1.col1 = t2.col2;", - "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 right join t2 " - "on " - "t1.col1 = t2.col2;", "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 last join t2 " "order by t2.col5 on t1.col1 = t2.col2;"}; EngineOptions options; @@ -277,7 +272,7 @@ TEST_F(EngineCompileTest, EngineCompileOnlyTest) { std::vector sql_str_list = { "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 full join t2 on " "t1.col1 = t2.col2;", - "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 left join t2 on " + "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 inner join t2 on " "t1.col1 = t2.col2;", "SELECT t1.COL1, t1.COL2, t2.COL1, t2.COL2 FROM t1 right join t2 " "on " diff --git a/hybridse/src/vm/generator.cc b/hybridse/src/vm/generator.cc index 2b437ca2602..39bb4d34d2e 100644 --- a/hybridse/src/vm/generator.cc +++ b/hybridse/src/vm/generator.cc @@ -16,6 +16,11 @@ #include "vm/generator.h" +#include + +#include "node/sql_node.h" +#include "vm/catalog.h" +#include "vm/catalog_wrapper.h" #include "vm/runner.h" namespace hybridse { @@ -232,10 +237,41 @@ Row JoinGenerator::RowLastJoinDropLeftSlices( return right_row; } -std::shared_ptr JoinGenerator::LazyLastJoin(std::shared_ptr left, - std::shared_ptr right, - const Row& parameter) { - return std::make_shared(left, right, parameter, shared_from_this()); +std::shared_ptr JoinGenerator::LazyJoin(std::shared_ptr left, + std::shared_ptr right, const Row& parameter) { + if (left->GetHandlerType() == kPartitionHandler) { + return std::make_shared(std::dynamic_pointer_cast(left), right, + parameter, shared_from_this()); + } + + auto left_tb = std::dynamic_pointer_cast(left); + if (left->GetHandlerType() == kRowHandler) { + auto left_table = std::shared_ptr(new MemTableHandler()); + left_table->AddRow(std::dynamic_pointer_cast(left)->GetValue()); + left_tb = left_table; + } + return std::make_shared(left_tb, right, parameter, shared_from_this()); +} + +std::shared_ptr JoinGenerator::LazyJoinOptimized(std::shared_ptr left, + std::shared_ptr right, + const Row& parameter) { + return std::make_shared(left, right, parameter, shared_from_this()); +} + +std::unique_ptr JoinGenerator::InitRight(const Row& left_row, std::shared_ptr right, + const Row& param) { + auto partition_key = index_key_gen_.Gen(left_row, param); + auto right_seg = right->GetSegment(partition_key); + if (!right_seg) { + return {}; + } + auto it = right_seg->GetIterator(); + if (!it) { + return {}; + } + it->SeekToFirst(); + return it; } Row JoinGenerator::RowLastJoin(const Row& left_row, @@ -275,6 +311,7 @@ Row JoinGenerator::RowLastJoinPartition( auto right_table = partition->GetSegment(partition_key); return RowLastJoinTable(left_row, right_table, parameter); } + Row JoinGenerator::RowLastJoinTable(const Row& left_row, std::shared_ptr table, const Row& parameter) { @@ -325,6 +362,41 @@ Row JoinGenerator::RowLastJoinTable(const Row& left_row, return Row(left_slices_, left_row, right_slices_, Row()); } +std::pair JoinGenerator::RowJoinIterator(const Row& left_row, + std::unique_ptr& right_iter, + const Row& parameter) { + if (!right_iter || !right_iter ->Valid()) { + return {Row(left_slices_, left_row, right_slices_, Row()), false}; + } + + if (!left_key_gen_.Valid() && !condition_gen_.Valid()) { + auto right_value = right_iter->GetValue(); + return {Row(left_slices_, left_row, right_slices_, right_value), true}; + } + + std::string left_key_str = ""; + if (left_key_gen_.Valid()) { + left_key_str = left_key_gen_.Gen(left_row, parameter); + } + while (right_iter->Valid()) { + if (right_group_gen_.Valid()) { + auto right_key_str = right_group_gen_.GetKey(right_iter->GetValue(), parameter); + if (left_key_gen_.Valid() && left_key_str != right_key_str) { + right_iter->Next(); + continue; + } + } + + Row joined_row(left_slices_, left_row, right_slices_, right_iter->GetValue()); + if (!condition_gen_.Valid() || condition_gen_.Gen(joined_row, parameter)) { + return {joined_row, true}; + } + right_iter->Next(); + } + + return {Row(left_slices_, left_row, right_slices_, Row()), false}; +} + bool JoinGenerator::TableJoin(std::shared_ptr left, std::shared_ptr right, const Row& parameter, @@ -726,5 +798,106 @@ std::shared_ptr FilterGenerator::Filter(std::shared_ptr(table, limit.value()); } +bool FilterGenerator::ValidIndex() const { + return index_seek_gen_.Valid(); +} +std::vector> InputsGenerator::RunInputs( + RunnerContext& ctx) { + std::vector> union_inputs; + for (auto runner : input_runners_) { + union_inputs.push_back(runner->RunWithCache(ctx)); + } + return union_inputs; +} + +std::vector> WindowUnionGenerator::PartitionEach( + std::vector> union_inputs, const Row& parameter) { + std::vector> union_partitions; + if (!windows_gen_.empty()) { + union_partitions.reserve(windows_gen_.size()); + for (size_t i = 0; i < inputs_cnt_; i++) { + union_partitions.push_back( + windows_gen_[i].partition_gen_.Partition(union_inputs[i], parameter)); + } + } + return union_partitions; +} + +std::vector> WindowJoinGenerator::RunInputs( + RunnerContext& ctx) { + std::vector> union_inputs; + if (!input_runners_.empty()) { + for (auto runner : input_runners_) { + union_inputs.push_back(runner->RunWithCache(ctx)); + } + } + return union_inputs; +} +Row WindowJoinGenerator::Join( + const Row& left_row, + const std::vector>& join_right_tables, + const Row& parameter) { + Row row = left_row; + for (size_t i = 0; i < join_right_tables.size(); i++) { + row = joins_gen_[i]->RowLastJoin(row, join_right_tables[i], parameter); + } + return row; +} + +void WindowJoinGenerator::AddWindowJoin(const class Join& join, size_t left_slices, Runner* runner) { + size_t right_slices = runner->output_schemas()->GetSchemaSourceSize(); + joins_gen_.push_back(JoinGenerator::Create(join, left_slices, right_slices)); + AddInput(runner); +} + +std::vector> RequestWindowUnionGenerator::GetRequestWindows( + const Row& row, const Row& parameter, std::vector> union_inputs) { + std::vector> union_segments(union_inputs.size()); + for (size_t i = 0; i < union_inputs.size(); i++) { + union_segments[i] = windows_gen_[i].GetRequestWindow(row, parameter, union_inputs[i]); + } + return union_segments; +} +void RequestWindowUnionGenerator::AddWindowUnion(const RequestWindowOp& window_op, Runner* runner) { + windows_gen_.emplace_back(window_op); + AddInput(runner); +} +void WindowUnionGenerator::AddWindowUnion(const WindowOp& window_op, Runner* runner) { + windows_gen_.push_back(WindowGenerator(window_op)); + AddInput(runner); +} +std::shared_ptr RequestWindowGenertor::GetRequestWindow(const Row& row, const Row& parameter, + std::shared_ptr input) { + auto segment = index_seek_gen_.SegmentOfKey(row, parameter, input); + + if (filter_gen_.Valid()) { + auto filter_key = filter_gen_.GetKey(row, parameter); + segment = filter_gen_.Filter(parameter, segment, filter_key); + } + if (sort_gen_.Valid()) { + segment = sort_gen_.Sort(segment, true); + } + return segment; +} +std::shared_ptr FilterKeyGenerator::Filter(const Row& parameter, std::shared_ptr table, + const std::string& request_keys) { + if (!filter_key_.Valid()) { + return table; + } + auto mem_table = std::shared_ptr(new MemTimeTableHandler()); + mem_table->SetOrderType(table->GetOrderType()); + auto iter = table->GetIterator(); + if (iter) { + iter->SeekToFirst(); + while (iter->Valid()) { + std::string keys = filter_key_.Gen(iter->GetValue(), parameter); + if (request_keys == keys) { + mem_table->AddRow(iter->GetKey(), iter->GetValue()); + } + iter->Next(); + } + } + return mem_table; +} } // namespace vm } // namespace hybridse diff --git a/hybridse/src/vm/generator.h b/hybridse/src/vm/generator.h index 51bc6b1e674..c3f82c22256 100644 --- a/hybridse/src/vm/generator.h +++ b/hybridse/src/vm/generator.h @@ -29,6 +29,10 @@ namespace hybridse { namespace vm { +// forward +class Runner; +class RunnerContext; + class ProjectFun { public: virtual Row operator()(const Row& row, const Row& parameter) const = 0; @@ -79,11 +83,17 @@ class ConstProjectGenerator : public FnGenerator { const Row Gen(const Row& parameter); RowProjectFun fun_; }; -class AggGenerator : public FnGenerator { +class AggGenerator : public FnGenerator, public std::enable_shared_from_this { public: - explicit AggGenerator(const FnInfo& info) : FnGenerator(info) {} + [[nodiscard]] static std::shared_ptr Create(const FnInfo& info) { + return std::shared_ptr(new AggGenerator(info)); + } + virtual ~AggGenerator() {} const Row Gen(const codec::Row& parameter_row, std::shared_ptr table); + + private: + explicit AggGenerator(const FnInfo& info) : FnGenerator(info) {} }; class WindowProjectGenerator : public FnGenerator { public: @@ -112,8 +122,18 @@ class ConditionGenerator : public FnGenerator { const bool Gen(const Row& row, const Row& parameter) const; const bool Gen(std::shared_ptr table, const codec::Row& parameter_row); }; -class RangeGenerator { +class RangeGenerator : public std::enable_shared_from_this { public: + [[nodiscard]] static std::shared_ptr Create(const Range& range) { + return std::shared_ptr(new RangeGenerator(range)); + } + virtual ~RangeGenerator() {} + + const bool Valid() const { return ts_gen_.Valid(); } + OrderGenerator ts_gen_; + WindowRange window_range_; + + private: explicit RangeGenerator(const Range& range) : ts_gen_(range.fn_info()), window_range_() { if (range.frame_ != nullptr) { switch (range.frame()->frame_type()) { @@ -142,36 +162,15 @@ class RangeGenerator { } } } - virtual ~RangeGenerator() {} - const bool Valid() const { return ts_gen_.Valid(); } - OrderGenerator ts_gen_; - WindowRange window_range_; }; + class FilterKeyGenerator { public: explicit FilterKeyGenerator(const Key& filter_key) : filter_key_(filter_key.fn_info()) {} virtual ~FilterKeyGenerator() {} const bool Valid() const { return filter_key_.Valid(); } std::shared_ptr Filter(const Row& parameter, std::shared_ptr table, - const std::string& request_keys) { - if (!filter_key_.Valid()) { - return table; - } - auto mem_table = std::shared_ptr(new MemTimeTableHandler()); - mem_table->SetOrderType(table->GetOrderType()); - auto iter = table->GetIterator(); - if (iter) { - iter->SeekToFirst(); - while (iter->Valid()) { - std::string keys = filter_key_.Gen(iter->GetValue(), parameter); - if (request_keys == keys) { - mem_table->AddRow(iter->GetKey(), iter->GetValue()); - } - iter->Next(); - } - } - return mem_table; - } + const std::string& request_keys); const std::string GetKey(const Row& row, const Row& parameter) { return filter_key_.Valid() ? filter_key_.Gen(row, parameter) : ""; } @@ -230,6 +229,9 @@ class FilterGenerator : public PredicateFun { const bool Valid() const { return index_seek_gen_.Valid() || condition_gen_.Valid(); } + // return if index seek exists + bool ValidIndex() const; + std::shared_ptr Filter(std::shared_ptr table, const Row& parameter, std::optional limit); @@ -250,13 +252,15 @@ class FilterGenerator : public PredicateFun { class WindowGenerator { public: explicit WindowGenerator(const WindowOp& window) - : window_op_(window), partition_gen_(window.partition_), sort_gen_(window.sort_), range_gen_(window.range_) {} + : window_op_(window), partition_gen_(window.partition_), sort_gen_(window.sort_) { + range_gen_ = RangeGenerator::Create(window.range_); + } virtual ~WindowGenerator() {} - const int64_t OrderKey(const Row& row) { return range_gen_.ts_gen_.Gen(row); } + const int64_t OrderKey(const Row& row) { return range_gen_->ts_gen_.Gen(row); } const WindowOp window_op_; PartitionGenerator partition_gen_; SortGenerator sort_gen_; - RangeGenerator range_gen_; + std::shared_ptr range_gen_; }; class RequestWindowGenertor { @@ -269,18 +273,7 @@ class RequestWindowGenertor { index_seek_gen_(window.index_key_) {} virtual ~RequestWindowGenertor() {} std::shared_ptr GetRequestWindow(const Row& row, const Row& parameter, - std::shared_ptr input) { - auto segment = index_seek_gen_.SegmentOfKey(row, parameter, input); - - if (filter_gen_.Valid()) { - auto filter_key = filter_gen_.GetKey(row, parameter); - segment = filter_gen_.Filter(parameter, segment, filter_key); - } - if (sort_gen_.Valid()) { - segment = sort_gen_.Sort(segment, true); - } - return segment; - } + std::shared_ptr input); RequestWindowOp window_op_; FilterKeyGenerator filter_gen_; SortGenerator sort_gen_; @@ -296,6 +289,7 @@ class JoinGenerator : public std::enable_shared_from_this { } virtual ~JoinGenerator() {} + bool TableJoin(std::shared_ptr left, std::shared_ptr right, const Row& parameter, std::shared_ptr output); // NOLINT bool TableJoin(std::shared_ptr left, std::shared_ptr right, const Row& parameter, @@ -310,14 +304,29 @@ class JoinGenerator : public std::enable_shared_from_this { Row RowLastJoin(const Row& left_row, std::shared_ptr right, const Row& parameter); Row RowLastJoinDropLeftSlices(const Row& left_row, std::shared_ptr right, const Row& parameter); - std::shared_ptr LazyLastJoin(std::shared_ptr left, - std::shared_ptr right, const Row& parameter); + // lazy join, supports left join and last join + std::shared_ptr LazyJoin(std::shared_ptr left, std::shared_ptr right, + const Row& parameter); + std::shared_ptr LazyJoinOptimized(std::shared_ptr left, + std::shared_ptr right, const Row& parameter); + + // init right iterator from left row, returns right iterator, nullptr if no match + // apply to standard SQL joins like left join, not for last join & concat join + std::unique_ptr InitRight(const Row& left_row, std::shared_ptr right, + const Row& param); + + // row left join the iterator as right source, iterator is updated to the position of join, or + // last position if not found + // returns (joined_row, whether_any_right_row_matches) + std::pair RowJoinIterator(const Row& left_row, std::unique_ptr& right_it, // NOLINT + const Row& parameter); ConditionGenerator condition_gen_; KeyGenerator left_key_gen_; PartitionGenerator right_group_gen_; KeyGenerator index_key_gen_; SortGenerator right_sort_gen_; + node::JoinType join_type_; private: explicit JoinGenerator(const Join& join, size_t left_slices, size_t right_slices) @@ -326,6 +335,7 @@ class JoinGenerator : public std::enable_shared_from_this { right_group_gen_(join.right_key_), index_key_gen_(join.index_key_.fn_info()), right_sort_gen_(join.right_sort_), + join_type_(join.join_type()), left_slices_(left_slices), right_slices_(right_slices) {} @@ -336,6 +346,60 @@ class JoinGenerator : public std::enable_shared_from_this { size_t right_slices_; }; +class InputsGenerator { + public: + InputsGenerator() : inputs_cnt_(0), input_runners_() {} + virtual ~InputsGenerator() {} + + std::vector> RunInputs( + RunnerContext& ctx); // NOLINT + const bool Valid() const { return 0 != inputs_cnt_; } + void AddInput(Runner* runner) { + input_runners_.push_back(runner); + inputs_cnt_++; + } + size_t inputs_cnt_; + std::vector input_runners_; +}; +class WindowUnionGenerator : public InputsGenerator { + public: + WindowUnionGenerator() : InputsGenerator() {} + virtual ~WindowUnionGenerator() {} + std::vector> PartitionEach(std::vector> union_inputs, + const Row& parameter); + void AddWindowUnion(const WindowOp& window_op, Runner* runner); + std::vector windows_gen_; +}; + +class RequestWindowUnionGenerator : public InputsGenerator, + public std::enable_shared_from_this { + public: + [[nodiscard]] static std::shared_ptr Create() { + return std::shared_ptr(new RequestWindowUnionGenerator()); + } + virtual ~RequestWindowUnionGenerator() {} + + void AddWindowUnion(const RequestWindowOp& window_op, Runner* runner); + + std::vector> GetRequestWindows( + const Row& row, const Row& parameter, std::vector> union_inputs); + std::vector windows_gen_; + + private: + RequestWindowUnionGenerator() : InputsGenerator() {} +}; + +class WindowJoinGenerator : public InputsGenerator { + public: + WindowJoinGenerator() : InputsGenerator() {} + virtual ~WindowJoinGenerator() {} + void AddWindowJoin(const Join& join, size_t left_slices, Runner* runner); + std::vector> RunInputs(RunnerContext& ctx); // NOLINT + Row Join(const Row& left_row, const std::vector>& join_right_tables, + const Row& parameter); + std::vector> joins_gen_; +}; + } // namespace vm } // namespace hybridse diff --git a/hybridse/src/vm/internal/node_helper.cc b/hybridse/src/vm/internal/node_helper.cc new file mode 100644 index 00000000000..46b3e0dfa8f --- /dev/null +++ b/hybridse/src/vm/internal/node_helper.cc @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "vm/internal/node_helper.h" + +namespace hybridse { +namespace vm { +namespace internal { + +Status GetDependentTables(const PhysicalOpNode* root, std::set>* db_tbs) { + using OUT = std::set>; + *db_tbs = internal::ReduceNode( + root, OUT{}, + [](OUT init, const PhysicalOpNode* node) { + if (node->GetOpType() == kPhysicalOpDataProvider) { + auto* data_op = dynamic_cast(node); + if (data_op != nullptr) { + init.emplace(data_op->GetDb(), data_op->GetName()); + } + } + return init; + }, + [](const PhysicalOpNode* node) { return node->GetDependents(); }); + return Status::OK(); +} +absl::StatusOr ExtractRequestNode(PhysicalOpNode* in) { + if (in == nullptr) { + return absl::InvalidArgumentError("null input node"); + } + + switch (in->GetOpType()) { + case vm::kPhysicalOpDataProvider: { + auto tp = dynamic_cast(in)->provider_type_; + if (tp == kProviderTypeRequest) { + return in; + } + + // else data provider is fine inside node tree, + // generally it is of type Partition, but can be Table as well e.g window (t1 instance_not_in_window) + return nullptr; + } + case vm::kPhysicalOpJoin: + case vm::kPhysicalOpUnion: + case vm::kPhysicalOpPostRequestUnion: + case vm::kPhysicalOpRequestUnion: + case vm::kPhysicalOpRequestAggUnion: + case vm::kPhysicalOpRequestJoin: { + // Binary Node + // - left or right status not ok -> error + // - left and right both has non-null value + // - the two not equals -> error + // - otherwise -> left as request node + auto left = ExtractRequestNode(in->GetProducer(0)); + if (!left.ok()) { + return left; + } + auto right = ExtractRequestNode(in->GetProducer(1)); + if (!right.ok()) { + return right; + } + + if (left.value() != nullptr && right.value() != nullptr) { + if (!left.value()->Equals(right.value())) { + return absl::NotFoundError( + absl::StrCat("different request table from left and right path:\n", in->GetTreeString())); + } + } + + return left.value(); + } + default: { + break; + } + } + + if (in->GetProducerCnt() == 0) { + // leaf node excepting DataProdiverNode + // consider ok as right source from one of the supported binary op + return nullptr; + } + + if (in->GetProducerCnt() > 1) { + return absl::UnimplementedError( + absl::StrCat("Non-support op with more than one producer:\n", in->GetTreeString())); + } + + return ExtractRequestNode(in->GetProducer(0)); +} +} // namespace internal +} // namespace vm +} // namespace hybridse diff --git a/hybridse/src/vm/internal/node_helper.h b/hybridse/src/vm/internal/node_helper.h index 89b47ea4e0f..15514dda764 100644 --- a/hybridse/src/vm/internal/node_helper.h +++ b/hybridse/src/vm/internal/node_helper.h @@ -18,12 +18,15 @@ #ifndef HYBRIDSE_SRC_VM_INTERNAL_NODE_HELPER_H_ #define HYBRIDSE_SRC_VM_INTERNAL_NODE_HELPER_H_ +#include +#include #include #include #include "vm/physical_op.h" #include "vm/physical_plan_context.h" +/// PhysicalOpNode related utility functions namespace hybridse { namespace vm { namespace internal { @@ -63,6 +66,15 @@ State ReduceNode(const PhysicalOpNode* root, State state, BinOp&& op, GetKids&& return state; } +// Get all dependent (db, table) info from physical plan +Status GetDependentTables(const PhysicalOpNode*, std::set>*); + +// Extract request node of the node tree. +// Returns +// - Request node on success +// - NULL if tree do not has request table but sufficient as as input tree of the big one +// - Error status otherwise +absl::StatusOr ExtractRequestNode(PhysicalOpNode* in); } // namespace internal } // namespace vm } // namespace hybridse diff --git a/hybridse/src/vm/mem_catalog.cc b/hybridse/src/vm/mem_catalog.cc index dca41c9355b..f4f5897f10f 100644 --- a/hybridse/src/vm/mem_catalog.cc +++ b/hybridse/src/vm/mem_catalog.cc @@ -18,8 +18,6 @@ #include -#include "absl/strings/substitute.h" - namespace hybridse { namespace vm { MemTimeTableIterator::MemTimeTableIterator(const MemTimeTable* table, @@ -74,10 +72,6 @@ void MemWindowIterator::Seek(const std::string& key) { void MemWindowIterator::SeekToFirst() { iter_ = start_iter_; } void MemWindowIterator::Next() { iter_++; } bool MemWindowIterator::Valid() { return end_iter_ != iter_; } -std::unique_ptr MemWindowIterator::GetValue() { - return std::unique_ptr( - new MemTimeTableIterator(&(iter_->second), schema_)); -} RowIterator* MemWindowIterator::GetRawValue() { return new MemTimeTableIterator(&(iter_->second), schema_); @@ -116,12 +110,9 @@ MemTimeTableHandler::MemTimeTableHandler(const std::string& table_name, order_type_(kNoneOrder) {} MemTimeTableHandler::~MemTimeTableHandler() {} -std::unique_ptr MemTimeTableHandler::GetIterator() { - return std::make_unique(&table_, schema_); -} -std::unique_ptr MemTimeTableHandler::GetWindowIterator( - const std::string& idx_name) { - return std::unique_ptr(); + +RowIterator* MemTimeTableHandler::GetRawIterator() { + return new MemTimeTableIterator(&table_, schema_); } void MemTimeTableHandler::AddRow(const uint64_t key, const Row& row) { @@ -154,9 +145,6 @@ void MemTimeTableHandler::Reverse() { ? kDescOrder : kDescOrder == order_type_ ? kAscOrder : kNoneOrder; } -RowIterator* MemTimeTableHandler::GetRawIterator() { - return new MemTimeTableIterator(&table_, schema_); -} MemPartitionHandler::MemPartitionHandler() : PartitionHandler(), @@ -234,15 +222,6 @@ void MemPartitionHandler::Print() { } } -std::unique_ptr MemTableHandler::GetWindowIterator( - const std::string& idx_name) { - return std::unique_ptr(); -} -std::unique_ptr MemTableHandler::GetIterator() { - std::unique_ptr it( - new MemTableIterator(&table_, schema_)); - return std::move(it); -} RowIterator* MemTableHandler::GetRawIterator() { return new MemTableIterator(&table_, schema_); } diff --git a/hybridse/src/vm/runner.cc b/hybridse/src/vm/runner.cc index a15a2626bf3..eb284e6e945 100644 --- a/hybridse/src/vm/runner.cc +++ b/hybridse/src/vm/runner.cc @@ -18,19 +18,19 @@ #include #include -#include #include #include "absl/status/status.h" -#include "absl/strings/str_cat.h" #include "absl/strings/substitute.h" #include "base/texttable.h" -#include "udf/udf.h" +#include "node/node_enum.h" +#include "vm/catalog.h" #include "vm/catalog_wrapper.h" #include "vm/core_api.h" #include "vm/internal/eval.h" #include "vm/jit_runtime.h" #include "vm/mem_catalog.h" +#include "vm/runner_ctx.h" DECLARE_bool(enable_spark_unsaferow_format); @@ -40,848 +40,6 @@ namespace vm { #define MAX_DEBUG_LINES_CNT 20 #define MAX_DEBUG_COLUMN_MAX 20 -static bool IsPartitionProvider(vm::PhysicalOpNode* n) { - switch (n->GetOpType()) { - case kPhysicalOpSimpleProject: - case kPhysicalOpRename: - return IsPartitionProvider(n->GetProducer(0)); - case kPhysicalOpDataProvider: - return dynamic_cast(n)->provider_type_ == kProviderTypePartition; - default: - return false; - } -} - -// Build Runner for each physical node -// return cluster task of given runner -// -// DataRunner(kProviderTypePartition) --> cluster task -// RequestRunner --> local task -// DataRunner(kProviderTypeTable) --> LocalTask, Unsupport in distribute -// database -// -// SimpleProjectRunner --> inherit task -// TableProjectRunner --> inherit task -// WindowAggRunner --> LocalTask , Unsupport in distribute database -// GroupAggRunner --> LocalTask, Unsupport in distribute database -// -// RowProjectRunner --> inherit task -// ConstProjectRunner --> local task -// -// RequestUnionRunner -// --> complete route_info of right cluster task -// --> build proxy runner if need -// RequestJoinRunner -// --> complete route_info of right cluster task -// --> build proxy runner if need -// kPhysicalOpJoin -// --> kJoinTypeLast->RequestJoinRunner -// --> complete route_info of right cluster task -// --> build proxy runner if need -// --> kJoinTypeConcat -// --> build proxy runner if need -// kPhysicalOpPostRequestUnion -// --> build proxy runner if need -// GroupRunner --> LocalTask, Unsupport in distribute database -// kPhysicalOpFilter -// kPhysicalOpLimit -// kPhysicalOpRename -ClusterTask RunnerBuilder::Build(PhysicalOpNode* node, Status& status) { - auto fail = InvalidTask(); - if (nullptr == node) { - status.msg = "fail to build runner : physical node is null"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto iter = task_map_.find(node); - if (iter != task_map_.cend()) { - iter->second.GetRoot()->EnableCache(); - return iter->second; - } - switch (node->GetOpType()) { - case kPhysicalOpDataProvider: { - auto op = dynamic_cast(node); - switch (op->provider_type_) { - case kProviderTypeTable: { - auto provider = - dynamic_cast(node); - DataRunner* runner = CreateRunner(id_++, node->schemas_ctx(), provider->table_handler_); - return RegisterTask(node, CommonTask(runner)); - } - case kProviderTypePartition: { - auto provider = - dynamic_cast( - node); - DataRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), provider->table_handler_->GetPartition(provider->index_name_)); - if (support_cluster_optimized_) { - return RegisterTask( - node, UnCompletedClusterTask( - runner, provider->table_handler_, - provider->index_name_)); - } else { - return RegisterTask(node, CommonTask(runner)); - } - } - case kProviderTypeRequest: { - RequestRunner* runner = CreateRunner(id_++, node->schemas_ctx()); - return RegisterTask(node, BuildRequestTask(runner)); - } - default: { - status.msg = "fail to support data provider type " + - DataProviderTypeName(op->provider_type_); - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return RegisterTask(node, fail); - } - } - } - case kPhysicalOpSimpleProject: { - auto cluster_task = // NOLINT - Build(node->producers().at(0), status); - if (!cluster_task.IsValid()) { - status.msg = "fail to build input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - int select_slice = op->GetSelectSourceIndex(); - if (select_slice >= 0) { - SelectSliceRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), select_slice); - return RegisterTask(node, - UnaryInheritTask(cluster_task, runner)); - } else { - SimpleProjectRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), op->GetLimitCnt(), op->project().fn_info()); - return RegisterTask(node, - UnaryInheritTask(cluster_task, runner)); - } - } - case kPhysicalOpConstProject: { - auto op = dynamic_cast(node); - ConstProjectRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), - op->project().fn_info()); - return RegisterTask(node, CommonTask(runner)); - } - case kPhysicalOpProject: { - auto cluster_task = // NOLINT - Build(node->producers().at(0), status); - if (!cluster_task.IsValid()) { - status.msg = "fail to build runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto input = cluster_task.GetRoot(); - auto op = dynamic_cast(node); - switch (op->project_type_) { - case kTableProject: { - if (support_cluster_optimized_) { - // Non-support table join under distribution env - status.msg = "fail to build cluster with table project"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - TableProjectRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), op->GetLimitCnt(), op->project().fn_info()); - return RegisterTask(node, - UnaryInheritTask(cluster_task, runner)); - } - case kReduceAggregation: { - ReduceRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), op->GetLimitCnt(), - dynamic_cast(node)->having_condition_, - op->project().fn_info()); - return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); - } - case kAggregation: { - auto agg_node = dynamic_cast(node); - if (agg_node == nullptr) { - status.msg = "fail to build AggRunner: input node is not PhysicalAggregationNode"; - status.code = common::kExecutionPlanError; - return fail; - } - AggRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), - agg_node->having_condition_, op->project().fn_info()); - return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); - } - case kGroupAggregation: { - if (support_cluster_optimized_) { - // Non-support group aggregation under distribution env - status.msg = - "fail to build cluster with group agg project"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = - dynamic_cast(node); - GroupAggRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->group_, - op->having_condition_, op->project().fn_info()); - return RegisterTask(node, - UnaryInheritTask(cluster_task, runner)); - } - case kWindowAggregation: { - if (support_cluster_optimized_) { - // Non-support table window aggregation join under distribution env - status.msg = - "fail to build cluster with window agg project"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - WindowAggRunner* runner = CreateRunner( - id_++, op->schemas_ctx(), op->GetLimitCnt(), op->window_, op->project().fn_info(), - op->instance_not_in_window(), op->exclude_current_time(), - op->need_append_input() ? node->GetProducer(0)->schemas_ctx()->GetSchemaSourceSize() : 0); - size_t input_slices = input->output_schemas()->GetSchemaSourceSize(); - if (!op->window_unions_.Empty()) { - for (auto window_union : - op->window_unions_.window_unions_) { - auto union_task = Build(window_union.first, status); - auto union_table = union_task.GetRoot(); - if (nullptr == union_table) { - return RegisterTask(node, fail); - } - runner->AddWindowUnion(window_union.second, - union_table); - } - } - if (!op->window_joins_.Empty()) { - for (auto& window_join : - op->window_joins_.window_joins_) { - auto join_task = // NOLINT - Build(window_join.first, status); - auto join_right_runner = join_task.GetRoot(); - if (nullptr == join_right_runner) { - return RegisterTask(node, fail); - } - runner->AddWindowJoin(window_join.second, - input_slices, - join_right_runner); - } - } - return RegisterTask(node, - UnaryInheritTask(cluster_task, runner)); - } - case kRowProject: { - RowProjectRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), op->GetLimitCnt(), op->project().fn_info()); - return RegisterTask(node, - UnaryInheritTask(cluster_task, runner)); - } - default: { - status.msg = "fail to support project type " + - ProjectTypeName(op->project_type_); - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return RegisterTask(node, fail); - } - } - } - case kPhysicalOpRequestUnion: { - auto left_task = Build(node->producers().at(0), status); - if (!left_task.IsValid()) { - status.msg = "fail to build left input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto right_task = Build(node->producers().at(1), status); - auto right = right_task.GetRoot(); - if (!right_task.IsValid()) { - status.msg = "fail to build right input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - RequestUnionRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->window().range_, - op->exclude_current_time(), op->output_request_row()); - Key index_key; - if (!op->instance_not_in_window()) { - runner->AddWindowUnion(op->window_, right); - index_key = op->window_.index_key_; - } - if (!op->window_unions_.Empty()) { - for (auto window_union : op->window_unions_.window_unions_) { - auto union_task = Build(window_union.first, status); - if (!status.isOK()) { - LOG(WARNING) << status; - return fail; - } - auto union_table = union_task.GetRoot(); - if (nullptr == union_table) { - return RegisterTask(node, fail); - } - runner->AddWindowUnion(window_union.second, union_table); - if (!index_key.ValidKey()) { - index_key = window_union.second.index_key_; - right_task = union_task; - right_task.SetRoot(right); - } - } - } - return RegisterTask( - node, BinaryInherit(left_task, right_task, runner, index_key, - kRightBias)); - } - case kPhysicalOpRequestAggUnion: { - return BuildRequestAggUnionTask(node, status); - } - case kPhysicalOpRequestJoin: { - auto left_task = // NOLINT - Build(node->producers().at(0), status); - if (!left_task.IsValid()) { - status.msg = "fail to build left input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto left = left_task.GetRoot(); - auto right_task = // NOLINT - Build(node->producers().at(1), status); - if (!right_task.IsValid()) { - status.msg = "fail to build right input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto right = right_task.GetRoot(); - auto op = dynamic_cast(node); - switch (op->join().join_type()) { - case node::kJoinTypeLast: { - RequestLastJoinRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), op->GetLimitCnt(), op->join_, - left->output_schemas()->GetSchemaSourceSize(), right->output_schemas()->GetSchemaSourceSize(), - op->output_right_only()); - - if (support_cluster_optimized_ && IsPartitionProvider(node->GetProducer(0))) { - // Partion left join partition, route by index of the left source, and it should uncompleted - auto& route_info = left_task.GetRouteInfo(); - runner->AddProducer(left_task.GetRoot()); - runner->AddProducer(right_task.GetRoot()); - return UnCompletedClusterTask(runner, route_info.table_handler_, route_info.index_); - } else { - return RegisterTask( - node, BinaryInherit(left_task, right_task, runner, op->join().index_key(), kLeftBias)); - } - } - case node::kJoinTypeConcat: { - ConcatRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt()); - return RegisterTask(node, BinaryInherit(left_task, right_task, runner, Key(), kNoBias)); - } - default: { - status.code = common::kExecutionPlanError; - status.msg = "can't handle join type " + - node::JoinTypeName(op->join().join_type()); - LOG(WARNING) << status; - return RegisterTask(node, fail); - } - } - } - case kPhysicalOpJoin: { - auto left_task = Build(node->producers().at(0), status); - if (!left_task.IsValid()) { - status.msg = "fail to build left input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto left = left_task.GetRoot(); - auto right_task = Build(node->producers().at(1), status); - if (!right_task.IsValid()) { - status.msg = "fail to build right input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto right = right_task.GetRoot(); - auto op = dynamic_cast(node); - switch (op->join().join_type()) { - case node::kJoinTypeLast: { - // TableLastJoin convert to - // Batch Request RequestLastJoin - if (support_cluster_optimized_) { - RequestLastJoinRunner* runner = CreateRunner( - id_++, node->schemas_ctx(), op->GetLimitCnt(), op->join_, - left->output_schemas()->GetSchemaSourceSize(), - right->output_schemas()->GetSchemaSourceSize(), op->output_right_only_); - return RegisterTask( - node, - BinaryInherit(left_task, right_task, runner, - op->join().index_key(), kLeftBias)); - } else { - LastJoinRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->join_, - left->output_schemas()->GetSchemaSourceSize(), - right->output_schemas()->GetSchemaSourceSize()); - return RegisterTask( - node, BinaryInherit(left_task, right_task, runner, - Key(), kLeftBias)); - } - } - case node::kJoinTypeConcat: { - ConcatRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt()); - return RegisterTask( - node, BinaryInherit(left_task, right_task, runner, - op->join().index_key(), kNoBias)); - } - default: { - status.code = common::kExecutionPlanError; - status.msg = "can't handle join type " + - node::JoinTypeName(op->join().join_type()); - LOG(WARNING) << status; - return RegisterTask(node, fail); - } - } - } - case kPhysicalOpGroupBy: { - if (support_cluster_optimized_) { - // Non-support group by under distribution env - status.msg = "fail to build cluster with group by node"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto cluster_task = Build(node->producers().at(0), status); - if (!cluster_task.IsValid()) { - status.msg = "fail to build input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - GroupRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->group()); - return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); - } - case kPhysicalOpFilter: { - auto cluster_task = // NOLINT - Build(node->producers().at(0), status); - if (!cluster_task.IsValid()) { - status.msg = "fail to build input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - FilterRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->filter_); - return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); - } - case kPhysicalOpLimit: { - auto cluster_task = // NOLINT - Build(node->producers().at(0), status); - if (!cluster_task.IsValid()) { - status.msg = "fail to build input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - if (!op->GetLimitCnt().has_value() || op->GetLimitOptimized()) { - return RegisterTask(node, cluster_task); - } - // limit runner always expect limit not empty - LimitRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt().value()); - return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); - } - case kPhysicalOpRename: { - return Build(node->producers().at(0), status); - } - case kPhysicalOpPostRequestUnion: { - auto left_task = Build(node->producers().at(0), status); - if (!left_task.IsValid()) { - status.msg = "fail to build left input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto right_task = Build(node->producers().at(1), status); - if (!right_task.IsValid()) { - status.msg = "fail to build right input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto union_op = dynamic_cast(node); - PostRequestUnionRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), union_op->request_ts()); - return RegisterTask(node, BinaryInherit(left_task, right_task, - runner, Key(), kRightBias)); - } - default: { - status.code = common::kExecutionPlanError; - status.msg = absl::StrCat("Non-support node ", PhysicalOpTypeName(node->GetOpType()), - " for OpenMLDB Online execute mode"); - LOG(WARNING) << status; - return RegisterTask(node, fail); - } - } -} - -ClusterTask RunnerBuilder::BuildRequestAggUnionTask(PhysicalOpNode* node, Status& status) { - auto fail = InvalidTask(); - auto request_task = Build(node->producers().at(0), status); - if (!request_task.IsValid()) { - status.msg = "fail to build request input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto base_table_task = Build(node->producers().at(1), status); - auto base_table = base_table_task.GetRoot(); - if (!base_table_task.IsValid()) { - status.msg = "fail to build base_table input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto agg_table_task = Build(node->producers().at(2), status); - auto agg_table = agg_table_task.GetRoot(); - if (!agg_table_task.IsValid()) { - status.msg = "fail to build agg_table input runner"; - status.code = common::kExecutionPlanError; - LOG(WARNING) << status; - return fail; - } - auto op = dynamic_cast(node); - RequestAggUnionRunner* runner = - CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->window().range_, - op->exclude_current_time(), op->output_request_row(), op->project_); - Key index_key; - if (!op->instance_not_in_window()) { - index_key = op->window_.index_key(); - runner->AddWindowUnion(op->window_, base_table); - runner->AddWindowUnion(op->agg_window_, agg_table); - } - auto task = RegisterTask(node, MultipleInherit({&request_task, &base_table_task, &agg_table_task}, runner, - index_key, kRightBias)); - if (!runner->InitAggregator()) { - return fail; - } else { - return task; - } -} - -ClusterTask RunnerBuilder::BinaryInherit(const ClusterTask& left, - const ClusterTask& right, - Runner* runner, const Key& index_key, - const TaskBiasType bias) { - if (support_cluster_optimized_) { - return BuildClusterTaskForBinaryRunner(left, right, runner, index_key, - bias); - } else { - return BuildLocalTaskForBinaryRunner(left, right, runner); - } -} - -ClusterTask RunnerBuilder::MultipleInherit(const std::vector& children, - Runner* runner, const Key& index_key, - const TaskBiasType bias) { - // TODO(zhanghao): currently only kRunnerRequestAggUnion uses MultipleInherit - const ClusterTask* request = children[0]; - if (runner->type_ != kRunnerRequestAggUnion) { - LOG(WARNING) << "MultipleInherit only support RequestAggUnionRunner"; - return ClusterTask(); - } - - if (children.size() < 3) { - LOG(WARNING) << "MultipleInherit should be called for children size >= 3, but children.size() = " - << children.size(); - return ClusterTask(); - } - - for (const auto child : children) { - if (child->IsClusterTask()) { - if (index_key.ValidKey()) { - for (size_t i = 1; i < children.size(); i++) { - if (!children[i]->IsClusterTask()) { - LOG(WARNING) << "Fail to build cluster task for " - << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) - << ": can't handler local task with index key"; - return ClusterTask(); - } - if (children[i]->IsCompletedClusterTask()) { - LOG(WARNING) << "Fail to complete cluster task for " - << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) - << ": task is completed already"; - return ClusterTask(); - } - } - for (size_t i = 0; i < children.size(); i++) { - runner->AddProducer(children[i]->GetRoot()); - } - // build complete cluster task - // TODO(zhanghao): assume all children can be handled with one single tablet - const RouteInfo& route_info = children[1]->GetRouteInfo(); - ClusterTask cluster_task(runner, std::vector({runner}), - RouteInfo(route_info.index_, index_key, - std::make_shared(*request), route_info.table_handler_)); - return cluster_task; - } - } - } - - // if all are local tasks - for (const auto child : children) { - runner->AddProducer(child->GetRoot()); - } - return ClusterTask(runner); -} - -ClusterTask RunnerBuilder::BuildLocalTaskForBinaryRunner( - const ClusterTask& left, const ClusterTask& right, Runner* runner) { - if (left.IsClusterTask() || right.IsClusterTask()) { - LOG(WARNING) << "fail to build local task for binary runner"; - return ClusterTask(); - } - runner->AddProducer(left.GetRoot()); - runner->AddProducer(right.GetRoot()); - return ClusterTask(runner); -} -ClusterTask RunnerBuilder::BuildClusterTaskForBinaryRunner( - const ClusterTask& left, const ClusterTask& right, Runner* runner, - const Key& index_key, const TaskBiasType bias) { - if (nullptr == runner) { - LOG(WARNING) << "Fail to build cluster task for null runner"; - return ClusterTask(); - } - ClusterTask new_left = left; - ClusterTask new_right = right; - - // if index key is valid, try to complete route info of right cluster task - if (index_key.ValidKey()) { - if (!right.IsClusterTask()) { - LOG(WARNING) << "Fail to build cluster task for " - << "[" << runner->id_ << "]" - << RunnerTypeName(runner->type_) - << ": can't handler local task with index key"; - return ClusterTask(); - } - if (right.IsCompletedClusterTask()) { - // completed with same index key - std::stringstream ss; - right.Print(ss, " "); - LOG(WARNING) << "Fail to complete cluster task for " - << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) - << ": task is completed already:\n" - << ss.str(); - LOG(WARNING) << "index key is " << index_key.ToString(); - return ClusterTask(); - } - RequestRunner* request_runner = CreateRunner(id_++, new_left.GetRoot()->output_schemas()); - runner->AddProducer(request_runner); - runner->AddProducer(new_right.GetRoot()); - - const RouteInfo& right_route_info = new_right.GetRouteInfo(); - ClusterTask cluster_task(runner, std::vector({runner}), - RouteInfo(right_route_info.index_, index_key, std::make_shared(new_left), - right_route_info.table_handler_)); - - if (new_left.IsCompletedClusterTask()) { - return BuildProxyRunnerForClusterTask(cluster_task); - } else { - return cluster_task; - } - } - - // Concat - // Agg1(Proxy(RequestUnion(Request, DATA)) - // Agg2(Proxy(RequestUnion(Request, DATA)) - // --> - // Proxy(Concat - // Agg1(RequestUnion(Request,DATA) - // Agg2(RequestUnion(Request,DATA) - // ) - - // if left and right is completed cluster task - while (new_left.IsCompletedClusterTask() && - new_right.IsCompletedClusterTask()) { - // merge left and right task if tasks can be merged - if (ClusterTask::TaskCanBeMerge(new_left, new_right)) { - ClusterTask task = - ClusterTask::TaskMerge(runner, new_left, new_right); - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return task; - } - switch (bias) { - case kNoBias: { - // Add build left proxy task into cluster job, - // and update new_left - new_left = BuildProxyRunnerForClusterTask(new_left); - new_right = BuildProxyRunnerForClusterTask(new_right); - break; - } - case kLeftBias: { - // build proxy runner for right task - new_right = BuildProxyRunnerForClusterTask(new_right); - break; - } - case kRightBias: { - // build proxy runner for right task - new_left = BuildProxyRunnerForClusterTask(new_left); - break; - } - } - } - if (new_left.IsUnCompletedClusterTask() || - new_right.IsUnCompletedClusterTask()) { - LOG(WARNING) << "Fail to build cluster task, can't handler " - "uncompleted cluster task"; - return ClusterTask(); - } - - // prepare left and right for runner - - // left local task + right cluster task - if (new_right.IsCompletedClusterTask()) { - switch (bias) { - case kNoBias: - case kLeftBias: { - new_right = BuildProxyRunnerForClusterTask(new_right); - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToLeft(runner, new_left, - new_right); - } - case kRightBias: { - auto new_left_root_input = - ClusterTask::GetRequestInput(new_left); - auto new_right_root_input = - ClusterTask::GetRequestInput(new_right); - // task can be merge simply when their inputs are the same - if (new_right_root_input == new_left_root_input) { - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToRight(runner, new_left, - new_right); - } else if (new_left_root_input == nullptr) { - // reset replace inputs as request runner - new_right.ResetInputs(nullptr); - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToRight(runner, new_left, - new_right); - } else { - LOG(WARNING) << "fail to merge local left task and cluster " - "right task"; - return ClusterTask(); - } - } - default: - return ClusterTask(); - } - } else if (new_left.IsCompletedClusterTask()) { - switch (bias) { - case kNoBias: - case kRightBias: { - new_left = BuildProxyRunnerForClusterTask(new_left); - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToRight(runner, new_left, - new_right); - } - case kLeftBias: { - auto new_left_root_input = - ClusterTask::GetRequestInput(new_right); - auto new_right_root_input = - ClusterTask::GetRequestInput(new_right); - // task can be merge simply - if (new_right_root_input == new_left_root_input) { - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToLeft(runner, new_left, - new_right); - } else if (new_right_root_input == nullptr) { - // reset replace inputs as request runner - new_left.ResetInputs(nullptr); - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToLeft(runner, new_left, - new_right); - } else { - LOG(WARNING) << "fail to merge cluster left task and local " - "right task"; - return ClusterTask(); - } - } - default: - return ClusterTask(); - } - } else { - runner->AddProducer(new_left.GetRoot()); - runner->AddProducer(new_right.GetRoot()); - return ClusterTask::TaskMergeToLeft(runner, new_left, new_right); - } -} -ClusterTask RunnerBuilder::BuildProxyRunnerForClusterTask( - const ClusterTask& task) { - if (!task.IsCompletedClusterTask()) { - LOG(WARNING) - << "Fail to build proxy runner, cluster task is uncompleted"; - return ClusterTask(); - } - // return cached proxy runner - Runner* proxy_runner = nullptr; - auto find_iter = proxy_runner_map_.find(task.GetRoot()); - if (find_iter != proxy_runner_map_.cend()) { - proxy_runner = find_iter->second; - proxy_runner->EnableCache(); - } else { - uint32_t remote_task_id = cluster_job_.AddTask(task); - ProxyRequestRunner* new_proxy_runner = CreateRunner( - id_++, remote_task_id, task.GetIndexKeyInput(), task.GetRoot()->output_schemas()); - if (nullptr != task.GetIndexKeyInput()) { - task.GetIndexKeyInput()->EnableCache(); - } - if (task.GetRoot()->need_batch_cache()) { - new_proxy_runner->EnableBatchCache(); - } - proxy_runner_map_.insert( - std::make_pair(task.GetRoot(), new_proxy_runner)); - proxy_runner = new_proxy_runner; - } - - if (task.GetInput()) { - return UnaryInheritTask(*task.GetInput(), proxy_runner); - } else { - return UnaryInheritTask(*request_task_, proxy_runner); - } - LOG(WARNING) << "Fail to build proxy runner for cluster job"; - return ClusterTask(); -} -ClusterTask RunnerBuilder::UnCompletedClusterTask( - Runner* runner, const std::shared_ptr table_handler, - std::string index) { - return ClusterTask(runner, table_handler, index); -} -ClusterTask RunnerBuilder::BuildRequestTask(RequestRunner* runner) { - if (nullptr == runner) { - LOG(WARNING) << "fail to build request task with null runner"; - return ClusterTask(); - } - ClusterTask request_task(runner); - request_task_ = std::make_shared(request_task); - return request_task; -} -ClusterTask RunnerBuilder::UnaryInheritTask(const ClusterTask& input, - Runner* runner) { - ClusterTask task = input; - runner->AddProducer(task.GetRoot()); - task.SetRoot(runner); - return task; -} - bool Runner::GetColumnBool(const int8_t* buf, const RowView* row_view, int idx, type::Type type) { bool key = false; @@ -1490,7 +648,7 @@ void WindowAggRunner::RunWindowAggOnKey( int32_t min_union_pos = IteratorStatus::FindLastIteratorWithMininumKey(union_segment_status); int32_t cnt = output_table->GetCount(); - HistoryWindow window(instance_window_gen_.range_gen_.window_range_); + HistoryWindow window(instance_window_gen_.range_gen_->window_range_); window.set_instance_not_in_window(instance_not_in_window_); window.set_exclude_current_time(exclude_current_time_); @@ -1538,7 +696,7 @@ void WindowAggRunner::RunWindowAggOnKey( } } -std::shared_ptr RequestLastJoinRunner::Run( +std::shared_ptr RequestJoinRunner::Run( RunnerContext& ctx, const std::vector>& inputs) { // NOLINT auto fail_ptr = std::shared_ptr(); @@ -1555,22 +713,31 @@ std::shared_ptr RequestLastJoinRunner::Run( // row last join table, compute in place auto left_row = std::dynamic_pointer_cast(left)->GetValue(); auto& parameter = ctx.GetParameterRow(); - if (output_right_only_) { - return std::shared_ptr( - new MemRowHandler(join_gen_->RowLastJoinDropLeftSlices(left_row, right, parameter))); + if (join_gen_->join_type_ == node::kJoinTypeLast) { + if (output_right_only_) { + return std::shared_ptr( + new MemRowHandler(join_gen_->RowLastJoinDropLeftSlices(left_row, right, parameter))); + } else { + return std::shared_ptr( + new MemRowHandler(join_gen_->RowLastJoin(left_row, right, parameter))); + } + } else if (join_gen_->join_type_ == node::kJoinTypeLeft) { + return join_gen_->LazyJoin(left, right, ctx.GetParameterRow()); } else { - return std::shared_ptr(new MemRowHandler(join_gen_->RowLastJoin(left_row, right, parameter))); + LOG(WARNING) << "unsupport join type " << node::JoinTypeName(join_gen_->join_type_); + return {}; } } else if (kPartitionHandler == left->GetHandlerType() && right->GetHandlerType() == kPartitionHandler) { auto left_part = std::dynamic_pointer_cast(left); - return join_gen_->LazyLastJoin(left_part, std::dynamic_pointer_cast(right), - ctx.GetParameterRow()); + auto right_part = std::dynamic_pointer_cast(right); + return join_gen_->LazyJoinOptimized(left_part, right_part, ctx.GetParameterRow()); + } else { + return join_gen_->LazyJoin(left, right, ctx.GetParameterRow()); } - return std::shared_ptr(); } -std::shared_ptr LastJoinRunner::Run(RunnerContext& ctx, - const std::vector>& inputs) { +std::shared_ptr JoinRunner::Run(RunnerContext& ctx, + const std::vector>& inputs) { auto fail_ptr = std::shared_ptr(); if (inputs.size() < 2) { LOG(WARNING) << "inputs size < 2"; @@ -1588,6 +755,10 @@ std::shared_ptr LastJoinRunner::Run(RunnerContext& ctx, } auto ¶meter = ctx.GetParameterRow(); + if (join_gen_->join_type_ == node::kJoinTypeLeft) { + return join_gen_->LazyJoin(left, right, parameter); + } + switch (left->GetHandlerType()) { case kTableHandler: { if (join_gen_->right_group_gen_.Valid()) { @@ -2065,20 +1236,23 @@ std::shared_ptr ConcatRunner::Run( auto right = inputs[1]; auto left = inputs[0]; size_t left_slices = producers_[0]->output_schemas()->GetSchemaSourceSize(); - size_t right_slices = - producers_[1]->output_schemas()->GetSchemaSourceSize(); + size_t right_slices = producers_[1]->output_schemas()->GetSchemaSourceSize(); if (!left) { return std::shared_ptr(); } switch (left->GetHandlerType()) { case kRowHandler: - return std::shared_ptr(new RowCombineWrapper( - std::dynamic_pointer_cast(left), left_slices, - std::dynamic_pointer_cast(right), right_slices)); + return std::shared_ptr( + new RowCombineWrapper(std::dynamic_pointer_cast(left), left_slices, + std::dynamic_pointer_cast(right), right_slices)); case kTableHandler: - return std::shared_ptr(new ConcatTableHandler( - std::dynamic_pointer_cast(left), left_slices, - std::dynamic_pointer_cast(right), right_slices)); + return std::shared_ptr( + new ConcatTableHandler(std::dynamic_pointer_cast(left), left_slices, + std::dynamic_pointer_cast(right), right_slices)); + case kPartitionHandler: + return std::shared_ptr( + new ConcatPartitionHandler(std::dynamic_pointer_cast(left), left_slices, + std::dynamic_pointer_cast(right), right_slices)); default: { LOG(WARNING) << "fail to run conncat runner: handler type unsupported"; @@ -2113,6 +1287,8 @@ std::shared_ptr LimitRunner::Run( LOG(WARNING) << "fail limit when input type isn't row or table"; return fail_ptr; } + default: + break; } return fail_ptr; } @@ -2169,7 +1345,7 @@ std::shared_ptr GroupAggRunner::Run( return std::shared_ptr(); } if (!having_condition_.Valid() || having_condition_.Gen(table, parameter)) { - output_table->AddRow(agg_gen_.Gen(parameter, table)); + output_table->AddRow(agg_gen_->Gen(parameter, table)); } return output_table; } else if (kPartitionHandler == input->GetHandlerType()) { @@ -2192,7 +1368,7 @@ std::shared_ptr GroupAggRunner::Run( if (limit_cnt_.has_value() && cnt++ >= limit_cnt_) { break; } - output_table->AddRow(agg_gen_.Gen(parameter, segment)); + output_table->AddRow(agg_gen_->Gen(parameter, segment)); } iter->Next(); } @@ -2269,10 +1445,10 @@ std::shared_ptr RequestAggUnionRunner::Run( } auto request = std::dynamic_pointer_cast(request_handler)->GetValue(); - int64_t ts_gen = range_gen_.Valid() ? range_gen_.ts_gen_.Gen(request) : -1; + int64_t ts_gen = range_gen_->Valid() ? range_gen_->ts_gen_.Gen(request) : -1; // Prepare Union Window - auto union_inputs = windows_union_gen_.RunInputs(ctx); + auto union_inputs = windows_union_gen_->RunInputs(ctx); if (ctx.is_debug()) { for (size_t i = 0; i < union_inputs.size(); i++) { std::ostringstream sss; @@ -2281,13 +1457,13 @@ std::shared_ptr RequestAggUnionRunner::Run( } } - auto& key_gen = windows_union_gen_.windows_gen_[0].index_seek_gen_.index_key_gen_; + auto& key_gen = windows_union_gen_->windows_gen_[0].index_seek_gen_.index_key_gen_; std::string key = key_gen.Gen(request, ctx.GetParameterRow()); // do not use codegen to gen the union outputs for aggr segment union_inputs.pop_back(); auto union_segments = - windows_union_gen_.GetRequestWindows(request, ctx.GetParameterRow(), union_inputs); + windows_union_gen_->GetRequestWindows(request, ctx.GetParameterRow(), union_inputs); // code_gen result of agg_segment is not correct. we correct the result here auto agg_segment = std::dynamic_pointer_cast(union_inputs[1])->GetSegment(key); if (agg_segment) { @@ -2306,12 +1482,12 @@ std::shared_ptr RequestAggUnionRunner::Run( std::shared_ptr window; if (agg_segment) { - window = RequestUnionWindow(request, union_segments, ts_gen, range_gen_.window_range_, output_request_row_, + window = RequestUnionWindow(request, union_segments, ts_gen, range_gen_->window_range_, output_request_row_, exclude_current_time_); } else { LOG(WARNING) << "Aggr segment is empty. Fall back to normal RequestUnionRunner"; - window = RequestUnionRunner::RequestUnionWindow(request, union_segments, ts_gen, range_gen_.window_range_, true, - exclude_current_time_); + window = RequestUnionRunner::RequestUnionWindow(request, union_segments, ts_gen, range_gen_->window_range_, + true, exclude_current_time_); } return window; @@ -2730,9 +1906,8 @@ std::shared_ptr ReduceRunner::Run( return row_handler; } -std::shared_ptr RequestUnionRunner::Run( - RunnerContext& ctx, - const std::vector>& inputs) { +std::shared_ptr RequestUnionRunner::Run(RunnerContext& ctx, + const std::vector>& inputs) { auto fail_ptr = std::shared_ptr(); if (inputs.size() < 2u) { LOG(WARNING) << "inputs size < 2"; @@ -2743,50 +1918,62 @@ std::shared_ptr RequestUnionRunner::Run( if (!left || !right) { return std::shared_ptr(); } - if (kRowHandler != left->GetHandlerType()) { - return std::shared_ptr(); + if (kRowHandler == left->GetHandlerType()) { + auto request = std::dynamic_pointer_cast(left)->GetValue(); + return RunOneRequest(&ctx, request); + } else if (kPartitionHandler == left->GetHandlerType()) { + auto left_part = std::dynamic_pointer_cast(left); + auto func = std::bind(&RequestUnionRunner::RunOneRequest, this, &ctx, std::placeholders::_1); + return std::shared_ptr(new LazyRequestUnionPartitionHandler(left_part, func)); } - auto request = std::dynamic_pointer_cast(left)->GetValue(); - - int64_t ts_gen = range_gen_.Valid() ? range_gen_.ts_gen_.Gen(request) : -1; + LOG(WARNING) << "skip due to performance: left source of request union is table handler(unoptimized)"; + return std::shared_ptr(); +} +std::shared_ptr RequestUnionRunner::RunOneRequest(RunnerContext* ctx, const Row& request) { + // ts_gen < 0 if there is no ORDER BY clause for WINDOW + int64_t ts_gen = range_gen_->Valid() ? range_gen_->ts_gen_.Gen(request) : -1; // Prepare Union Window - auto union_inputs = windows_union_gen_.RunInputs(ctx); - auto union_segments = - windows_union_gen_.GetRequestWindows(request, ctx.GetParameterRow(), union_inputs); + auto union_inputs = windows_union_gen_->RunInputs(*ctx); + auto union_segments = windows_union_gen_->GetRequestWindows(request, ctx->GetParameterRow(), union_inputs); // build window with start and end offset - return RequestUnionWindow(request, union_segments, ts_gen, range_gen_.window_range_, output_request_row_, + return RequestUnionWindow(request, union_segments, ts_gen, range_gen_->window_range_, output_request_row_, exclude_current_time_); } + std::shared_ptr RequestUnionRunner::RequestUnionWindow( const Row& request, std::vector> union_segments, int64_t ts_gen, const WindowRange& window_range, bool output_request_row, bool exclude_current_time) { - uint64_t start = 0; - // end is empty means end value < 0, that there is no effective window range + // range_start, range_end default to [0, MAX], so for the case without ORDER BY, + // RANGE-type WINDOW includes all rows in partition + uint64_t range_start = 0; + // range_end is empty means end value < 0, that there is no effective window range // this happend when `ts_gen` is 0 and exclude current_time needed - std::optional end = UINT64_MAX; - uint64_t rows_start_preceding = 0; - uint64_t max_size = 0; + std::optional range_end = UINT64_MAX; + uint64_t rows_start_preceding = window_range.start_row_; + uint64_t max_size = window_range.max_size_; if (ts_gen >= 0) { - start = (ts_gen + window_range.start_offset_) < 0 + range_start = (ts_gen + window_range.start_offset_) < 0 ? 0 : (ts_gen + window_range.start_offset_); if (exclude_current_time && 0 == window_range.end_offset_) { if (ts_gen == 0) { - end = {}; + range_end = {}; } else { - end = ts_gen - 1; + range_end = ts_gen - 1; } } else { - end = (ts_gen + window_range.end_offset_) < 0 + range_end = (ts_gen + window_range.end_offset_) < 0 ? 0 : (ts_gen + window_range.end_offset_); } - rows_start_preceding = window_range.start_row_; - max_size = window_range.max_size_; } - uint64_t request_key = ts_gen > 0 ? static_cast(ts_gen) : 0; + // INT64_MAX is the magic number as row key of input row, + // when WINDOW without ORDER BY + // + // DONT BELIEVE THE UNSIGNED TYPE, codegen still use int64_t as data type + uint64_t request_key = ts_gen >= 0 ? static_cast(ts_gen) : INT64_MAX; auto window_table = std::make_shared(); @@ -2805,7 +1992,7 @@ std::shared_ptr RequestUnionRunner::RequestUnionWindow( union_segment_status[i] = IteratorStatus(); continue; } - union_segment_iters[i]->Seek(end.value_or(0)); + union_segment_iters[i]->Seek(range_end.value_or(0)); if (!union_segment_iters[i]->Valid()) { union_segment_status[i] = IteratorStatus(); continue; @@ -2818,12 +2005,12 @@ std::shared_ptr RequestUnionRunner::RequestUnionWindow( uint64_t cnt = 0; auto range_status = window_range.GetWindowPositionStatus( cnt > rows_start_preceding, window_range.end_offset_ < 0, - request_key < start); + request_key < range_start); if (output_request_row) { window_table->AddRow(request_key, request); - } - if (WindowRange::kInWindow == range_status) { - cnt++; + if (WindowRange::kInWindow == range_status) { + cnt++; + } } while (-1 != max_union_pos) { @@ -2832,8 +2019,8 @@ std::shared_ptr RequestUnionRunner::RequestUnionWindow( } auto range_status = window_range.GetWindowPositionStatus( cnt > rows_start_preceding, - union_segment_status[max_union_pos].key_ > end, - union_segment_status[max_union_pos].key_ < start); + union_segment_status[max_union_pos].key_ > range_end, + union_segment_status[max_union_pos].key_ < range_start); if (WindowRange::kExceedWindow == range_status) { break; } @@ -2900,16 +2087,26 @@ std::shared_ptr AggRunner::Run( LOG(WARNING) << "input is empty"; return std::shared_ptr(); } - if (kTableHandler != input->GetHandlerType()) { - return std::shared_ptr(); - } - auto table = std::dynamic_pointer_cast(input); - auto parameter = ctx.GetParameterRow(); - if (having_condition_.Valid() && !having_condition_.Gen(table, parameter)) { - return std::shared_ptr(); + + if (kTableHandler == input->GetHandlerType()) { + auto table = std::dynamic_pointer_cast(input); + auto parameter = ctx.GetParameterRow(); + if (having_condition_.Valid() && !having_condition_.Gen(table, parameter)) { + return std::shared_ptr(); + } + auto row_handler = std::shared_ptr(new MemRowHandler(agg_gen_->Gen(parameter, table))); + return row_handler; + } else if (kPartitionHandler == input->GetHandlerType()) { + // lazify + auto data_set = std::dynamic_pointer_cast(input); + if (data_set == nullptr) { + return std::shared_ptr(); + } + + return std::shared_ptr(new LazyAggPartitionHandler(data_set, agg_gen_, ctx.GetParameterRow())); } - auto row_handler = std::shared_ptr(new MemRowHandler(agg_gen_.Gen(parameter, table))); - return row_handler; + + return std::shared_ptr(); } std::shared_ptr ProxyRequestRunner::BatchRequestRun( RunnerContext& ctx) { @@ -3330,29 +2527,6 @@ Row Runner::GroupbyProject(const int8_t* fn, const codec::Row& parameter, TableH base::RefCountedSlice::CreateManaged(buf, RowView::GetSize(buf))); } -std::vector> InputsGenerator::RunInputs( - RunnerContext& ctx) { - std::vector> union_inputs; - for (auto runner : input_runners_) { - union_inputs.push_back(runner->RunWithCache(ctx)); - } - return union_inputs; -} -std::vector> -WindowUnionGenerator::PartitionEach( - std::vector> union_inputs, - const Row& parameter) { - std::vector> union_partitions; - if (!windows_gen_.empty()) { - union_partitions.reserve(windows_gen_.size()); - for (size_t i = 0; i < inputs_cnt_; i++) { - union_partitions.push_back( - windows_gen_[i].partition_gen_.Partition(union_inputs[i], parameter)); - } - } - return union_partitions; -} - int32_t IteratorStatus::FindLastIteratorWithMininumKey(const std::vector& status_list) { int32_t min_union_pos = -1; std::optional min_union_order; @@ -3383,62 +2557,5 @@ int32_t IteratorStatus::FindFirstIteratorWithMaximizeKey(const std::vector> WindowJoinGenerator::RunInputs( - RunnerContext& ctx) { - std::vector> union_inputs; - if (!input_runners_.empty()) { - for (auto runner : input_runners_) { - union_inputs.push_back(runner->RunWithCache(ctx)); - } - } - return union_inputs; -} -Row WindowJoinGenerator::Join( - const Row& left_row, - const std::vector>& join_right_tables, - const Row& parameter) { - Row row = left_row; - for (size_t i = 0; i < join_right_tables.size(); i++) { - row = joins_gen_[i]->RowLastJoin(row, join_right_tables[i], parameter); - } - return row; -} - -std::shared_ptr RunnerContext::GetBatchCache( - int64_t id) const { - auto iter = batch_cache_.find(id); - if (iter == batch_cache_.end()) { - return std::shared_ptr(); - } else { - return iter->second; - } -} - -void RunnerContext::SetBatchCache(int64_t id, - std::shared_ptr data) { - batch_cache_[id] = data; -} - -std::shared_ptr RunnerContext::GetCache(int64_t id) const { - auto iter = cache_.find(id); - if (iter == cache_.end()) { - return std::shared_ptr(); - } else { - return iter->second; - } -} - -void RunnerContext::SetCache(int64_t id, - const std::shared_ptr data) { - cache_[id] = data; -} - -void RunnerContext::SetRequest(const hybridse::codec::Row& request) { - request_ = request; -} -void RunnerContext::SetRequests( - const std::vector& requests) { - requests_ = requests; -} } // namespace vm } // namespace hybridse diff --git a/hybridse/src/vm/runner.h b/hybridse/src/vm/runner.h index 25857dbdd0f..b40130db812 100644 --- a/hybridse/src/vm/runner.h +++ b/hybridse/src/vm/runner.h @@ -17,22 +17,17 @@ #ifndef HYBRIDSE_SRC_VM_RUNNER_H_ #define HYBRIDSE_SRC_VM_RUNNER_H_ -#include #include #include #include -#include -#include #include #include "absl/container/flat_hash_map.h" #include "absl/status/statusor.h" #include "base/fe_status.h" #include "codec/fe_row_codec.h" -#include "node/node_manager.h" #include "vm/aggregator.h" #include "vm/catalog.h" -#include "vm/catalog_wrapper.h" #include "vm/core_api.h" #include "vm/generator.h" #include "vm/mem_catalog.h" @@ -73,10 +68,10 @@ enum RunnerType { kRunnerRequestAggUnion, kRunnerPostRequestUnion, kRunnerIndexSeek, - kRunnerLastJoin, + kRunnerJoin, kRunnerConcat, kRunnerRequestRunProxy, - kRunnerRequestLastJoin, + kRunnerRequestJoin, kRunnerBatchRequestRunProxy, kRunnerLimit, kRunnerUnknow, @@ -119,12 +114,12 @@ inline const std::string RunnerTypeName(const RunnerType& type) { return "POST_REQUEST_UNION"; case kRunnerIndexSeek: return "INDEX_SEEK"; - case kRunnerLastJoin: - return "LASTJOIN"; + case kRunnerJoin: + return "JOIN"; case kRunnerConcat: return "CONCAT"; - case kRunnerRequestLastJoin: - return "REQUEST_LASTJOIN"; + case kRunnerRequestJoin: + return "REQUEST_JOIN"; case kRunnerLimit: return "LIMIT"; case kRunnerRequestRunProxy: @@ -325,74 +320,6 @@ class IteratorStatus { uint64_t key_; }; // namespace vm -class InputsGenerator { - public: - InputsGenerator() : inputs_cnt_(0), input_runners_() {} - virtual ~InputsGenerator() {} - - std::vector> RunInputs( - RunnerContext& ctx); // NOLINT - const bool Valid() const { return 0 != inputs_cnt_; } - void AddInput(Runner* runner) { - input_runners_.push_back(runner); - inputs_cnt_++; - } - size_t inputs_cnt_; - std::vector input_runners_; -}; -class WindowUnionGenerator : public InputsGenerator { - public: - WindowUnionGenerator() : InputsGenerator() {} - virtual ~WindowUnionGenerator() {} - std::vector> PartitionEach( - std::vector> union_inputs, - const Row& parameter); - void AddWindowUnion(const WindowOp& window_op, Runner* runner) { - windows_gen_.push_back(WindowGenerator(window_op)); - AddInput(runner); - } - std::vector windows_gen_; -}; - -class RequestWindowUnionGenerator : public InputsGenerator { - public: - RequestWindowUnionGenerator() : InputsGenerator() {} - virtual ~RequestWindowUnionGenerator() {} - - void AddWindowUnion(const RequestWindowOp& window_op, Runner* runner) { - windows_gen_.push_back(RequestWindowGenertor(window_op)); - AddInput(runner); - } - - std::vector> GetRequestWindows( - const Row& row, const Row& parameter, std::vector> union_inputs) { - std::vector> union_segments(union_inputs.size()); - for (size_t i = 0; i < union_inputs.size(); i++) { - union_segments[i] = windows_gen_[i].GetRequestWindow(row, parameter, union_inputs[i]); - } - return union_segments; - } - std::vector windows_gen_; -}; - -class WindowJoinGenerator : public InputsGenerator { - public: - WindowJoinGenerator() : InputsGenerator() {} - virtual ~WindowJoinGenerator() {} - void AddWindowJoin(const Join& join, size_t left_slices, Runner* runner) { - size_t right_slices = runner->output_schemas()->GetSchemaSourceSize(); - joins_gen_.push_back(JoinGenerator::Create(join, left_slices, right_slices)); - AddInput(runner); - } - std::vector> RunInputs( - RunnerContext& ctx); // NOLINT - Row Join( - const Row& left_row, - const std::vector>& join_right_tables, - const Row& parameter); - std::vector> joins_gen_; -}; - class DataRunner : public Runner { public: DataRunner(const int32_t id, const SchemasContext* schema, @@ -549,7 +476,7 @@ class GroupAggRunner : public Runner { : Runner(id, kRunnerGroupAgg, schema, limit_cnt), group_(group.fn_info()), having_condition_(having_condition.fn_info()), - agg_gen_(project) {} + agg_gen_(AggGenerator::Create(project)) {} ~GroupAggRunner() {} std::shared_ptr Run( RunnerContext& ctx, // NOLINT @@ -557,24 +484,22 @@ class GroupAggRunner : public Runner { override; // NOLINT KeyGenerator group_; ConditionGenerator having_condition_; - AggGenerator agg_gen_; + std::shared_ptr agg_gen_; }; class AggRunner : public Runner { public: - AggRunner(const int32_t id, const SchemasContext* schema, - const std::optional limit_cnt, - const ConditionFilter& having_condition, - const FnInfo& fn_info) + AggRunner(const int32_t id, const SchemasContext* schema, const std::optional limit_cnt, + const ConditionFilter& having_condition, const FnInfo& fn_info) : Runner(id, kRunnerAgg, schema, limit_cnt), having_condition_(having_condition.fn_info()), - agg_gen_(fn_info) {} + agg_gen_(AggGenerator::Create(fn_info)) {} ~AggRunner() {} std::shared_ptr Run( RunnerContext& ctx, // NOLINT const std::vector>& inputs) override; // NOLINT ConditionGenerator having_condition_; - AggGenerator agg_gen_; + std::shared_ptr agg_gen_; }; class ReduceRunner : public Runner { @@ -583,12 +508,12 @@ class ReduceRunner : public Runner { const ConditionFilter& having_condition, const FnInfo& fn_info) : Runner(id, kRunnerReduce, schema, limit_cnt), having_condition_(having_condition.fn_info()), - agg_gen_(fn_info) {} + agg_gen_(AggGenerator::Create(fn_info)) {} ~ReduceRunner() {} std::shared_ptr Run(RunnerContext& ctx, const std::vector>& inputs) override; ConditionGenerator having_condition_; - AggGenerator agg_gen_; + std::shared_ptr agg_gen_; }; class WindowAggRunner : public Runner { @@ -638,37 +563,39 @@ class WindowAggRunner : public Runner { class RequestUnionRunner : public Runner { public: - RequestUnionRunner(const int32_t id, const SchemasContext* schema, - const std::optional limit_cnt, const Range& range, - bool exclude_current_time, bool output_request_row) + RequestUnionRunner(const int32_t id, const SchemasContext* schema, const std::optional limit_cnt, + const Range& range, bool exclude_current_time, bool output_request_row) : Runner(id, kRunnerRequestUnion, schema, limit_cnt), - range_gen_(range), + range_gen_(RangeGenerator::Create(range)), exclude_current_time_(exclude_current_time), - output_request_row_(output_request_row) {} + output_request_row_(output_request_row) { + windows_union_gen_ = RequestWindowUnionGenerator::Create(); + } + + std::shared_ptr Run(RunnerContext& ctx, // NOLINT + const std::vector>& inputs) override; + + std::shared_ptr RunOneRequest(RunnerContext* ctx, const Row& request); - std::shared_ptr Run( - RunnerContext& ctx, // NOLINT - const std::vector>& inputs) - override; // NOLINT static std::shared_ptr RequestUnionWindow(const Row& request, std::vector> union_segments, int64_t request_ts, const WindowRange& window_range, bool output_request_row, bool exclude_current_time); void AddWindowUnion(const RequestWindowOp& window, Runner* runner) { - windows_union_gen_.AddWindowUnion(window, runner); + windows_union_gen_->AddWindowUnion(window, runner); } void Print(std::ostream& output, const std::string& tab, std::set* visited_ids) const override { Runner::Print(output, tab, visited_ids); output << "\n" << tab << "window unions:\n"; - for (auto& r : windows_union_gen_.input_runners_) { + for (auto& r : windows_union_gen_->input_runners_) { r->Print(output, tab + " ", visited_ids); } } - RequestWindowUnionGenerator windows_union_gen_; - RangeGenerator range_gen_; + std::shared_ptr windows_union_gen_; + std::shared_ptr range_gen_; bool exclude_current_time_; bool output_request_row_; }; @@ -679,11 +606,12 @@ class RequestAggUnionRunner : public Runner { const Range& range, bool exclude_current_time, bool output_request_row, const node::CallExprNode* project) : Runner(id, kRunnerRequestAggUnion, schema, limit_cnt), - range_gen_(range), + range_gen_(RangeGenerator::Create(range)), exclude_current_time_(exclude_current_time), output_request_row_(output_request_row), func_(project->GetFnDef()), agg_col_(project->GetChild(0)) { + windows_union_gen_ = RequestWindowUnionGenerator::Create(); if (agg_col_->GetExprType() == node::kExprColumnRef) { agg_col_name_ = dynamic_cast(agg_col_)->GetColumnName(); } /* for kAllExpr like count(*), agg_col_name_ is empty */ @@ -704,7 +632,7 @@ class RequestAggUnionRunner : public Runner { const bool output_request_row, const bool exclude_current_time) const; void AddWindowUnion(const RequestWindowOp& window, Runner* runner) { - windows_union_gen_.AddWindowUnion(window, runner); + windows_union_gen_->AddWindowUnion(window, runner); } static std::string PrintEvalValue(const absl::StatusOr>& val); @@ -723,8 +651,8 @@ class RequestAggUnionRunner : public Runner { kMaxWhere, }; - RequestWindowUnionGenerator windows_union_gen_; - RangeGenerator range_gen_; + std::shared_ptr windows_union_gen_; + std::shared_ptr range_gen_; bool exclude_current_time_; // include request row from union. @@ -771,14 +699,14 @@ class PostRequestUnionRunner : public Runner { OrderGenerator request_ts_gen_; }; -class LastJoinRunner : public Runner { +class JoinRunner : public Runner { public: - LastJoinRunner(const int32_t id, const SchemasContext* schema, const std::optional limit_cnt, - const Join& join, size_t left_slices, size_t right_slices) - : Runner(id, kRunnerLastJoin, schema, limit_cnt) { + JoinRunner(const int32_t id, const SchemasContext* schema, const std::optional limit_cnt, const Join& join, + size_t left_slices, size_t right_slices) + : Runner(id, kRunnerJoin, schema, limit_cnt) { join_gen_ = JoinGenerator::Create(join, left_slices, right_slices); } - ~LastJoinRunner() {} + ~JoinRunner() {} std::shared_ptr Run( RunnerContext& ctx, // NOLINT const std::vector>& inputs) @@ -786,15 +714,15 @@ class LastJoinRunner : public Runner { std::shared_ptr join_gen_; }; -class RequestLastJoinRunner : public Runner { +class RequestJoinRunner : public Runner { public: - RequestLastJoinRunner(const int32_t id, const SchemasContext* schema, const std::optional limit_cnt, - const Join& join, const size_t left_slices, const size_t right_slices, - const bool output_right_only) - : Runner(id, kRunnerRequestLastJoin, schema, limit_cnt), output_right_only_(output_right_only) { + RequestJoinRunner(const int32_t id, const SchemasContext* schema, const std::optional limit_cnt, + const Join& join, const size_t left_slices, const size_t right_slices, + const bool output_right_only) + : Runner(id, kRunnerRequestJoin, schema, limit_cnt), output_right_only_(output_right_only) { join_gen_ = JoinGenerator::Create(join, left_slices, right_slices); } - ~RequestLastJoinRunner() {} + ~RequestJoinRunner() {} std::shared_ptr Run( RunnerContext& ctx, // NOLINT @@ -857,8 +785,7 @@ class ProxyRequestRunner : public Runner { const std::vector>& inputs) override; std::shared_ptr BatchRequestRun( RunnerContext& ctx) override; // NOLINT - virtual void PrintRunnerInfo(std::ostream& output, - const std::string& tab) const { + void PrintRunnerInfo(std::ostream& output, const std::string& tab) const override { output << tab << "[" << id_ << "]" << RunnerTypeName(type_) << "(TASK_ID=" << task_id_ << ")"; if (is_lazy_) { @@ -907,414 +834,6 @@ class ProxyRequestRunner : public Runner { uint32_t task_id_; Runner* index_input_; }; -class ClusterTask; -class RouteInfo { - public: - RouteInfo() - : index_(), - index_key_(), - index_key_input_runner_(nullptr), - input_(), - table_handler_() {} - RouteInfo(const std::string index, - std::shared_ptr table_handler) - : index_(index), - index_key_(), - index_key_input_runner_(nullptr), - input_(), - table_handler_(table_handler) {} - RouteInfo(const std::string index, const Key& index_key, - std::shared_ptr input, - std::shared_ptr table_handler) - : index_(index), - index_key_(index_key), - index_key_input_runner_(nullptr), - input_(input), - table_handler_(table_handler) {} - ~RouteInfo() {} - const bool IsCompleted() const { - return table_handler_ && !index_.empty() && index_key_.ValidKey(); - } - const bool IsCluster() const { return table_handler_ && !index_.empty(); } - static const bool EqualWith(const RouteInfo& info1, - const RouteInfo& info2) { - return info1.input_ == info2.input_ && - info1.table_handler_ == info2.table_handler_ && - info1.index_ == info2.index_ && - node::ExprEquals(info1.index_key_.keys_, info2.index_key_.keys_); - } - - const std::string ToString() const { - if (IsCompleted()) { - std::ostringstream oss; - oss << ", routing index = " << table_handler_->GetDatabase() << "." - << table_handler_->GetName() << "." << index_ << ", " - << index_key_.ToString(); - return oss.str(); - } else { - return ""; - } - } - std::string index_; - Key index_key_; - Runner* index_key_input_runner_; - std::shared_ptr input_; - std::shared_ptr table_handler_; -}; - -// task info of cluster job -// partitoin/index info -// index key generator -// request generator -class ClusterTask { - public: - ClusterTask() : root_(nullptr), input_runners_(), route_info_() {} - explicit ClusterTask(Runner* root) - : root_(root), input_runners_(), route_info_() {} - ClusterTask(Runner* root, const std::shared_ptr table_handler, - std::string index) - : root_(root), input_runners_(), route_info_(index, table_handler) {} - ClusterTask(Runner* root, const std::vector& input_runners, - const RouteInfo& route_info) - : root_(root), input_runners_(input_runners), route_info_(route_info) {} - ~ClusterTask() {} - void Print(std::ostream& output, const std::string& tab) const { - output << route_info_.ToString() << "\n"; - if (nullptr == root_) { - output << tab << "NULL RUNNER\n"; - } else { - std::set visited_ids; - root_->Print(output, tab, &visited_ids); - } - } - - void ResetInputs(std::shared_ptr input) { - for (auto input_runner : input_runners_) { - input_runner->SetProducer(0, route_info_.input_->GetRoot()); - } - route_info_.index_key_input_runner_ = route_info_.input_->GetRoot(); - route_info_.input_ = input; - } - Runner* GetRoot() const { return root_; } - void SetRoot(Runner* root) { root_ = root; } - Runner* GetInputRunner(size_t idx) const { - return idx >= input_runners_.size() ? nullptr : input_runners_[idx]; - } - Runner* GetIndexKeyInput() const { - return route_info_.index_key_input_runner_; - } - std::shared_ptr GetInput() const { return route_info_.input_; } - Key GetIndexKey() const { return route_info_.index_key_; } - void SetIndexKey(const Key& key) { route_info_.index_key_ = key; } - void SetInput(std::shared_ptr input) { - route_info_.input_ = input; - } - - const bool IsValid() const { return nullptr != root_; } - - const bool IsCompletedClusterTask() const { - return IsValid() && route_info_.IsCompleted(); - } - const bool IsUnCompletedClusterTask() const { - return IsClusterTask() && !route_info_.IsCompleted(); - } - const bool IsClusterTask() const { return route_info_.IsCluster(); } - const std::string& index() { return route_info_.index_; } - std::shared_ptr table_handler() { - return route_info_.table_handler_; - } - - // Cluster tasks with same input runners and index keys can be merged - static const bool TaskCanBeMerge(const ClusterTask& task1, - const ClusterTask& task2) { - return RouteInfo::EqualWith(task1.route_info_, task2.route_info_); - } - static const ClusterTask TaskMerge(Runner* root, const ClusterTask& task1, - const ClusterTask& task2) { - return TaskMergeToLeft(root, task1, task2); - } - static const ClusterTask TaskMergeToLeft(Runner* root, - const ClusterTask& task1, - const ClusterTask& task2) { - std::vector input_runners; - for (auto runner : task1.input_runners_) { - input_runners.push_back(runner); - } - for (auto runner : task2.input_runners_) { - input_runners.push_back(runner); - } - return ClusterTask(root, input_runners, task1.route_info_); - } - static const ClusterTask TaskMergeToRight(Runner* root, - const ClusterTask& task1, - const ClusterTask& task2) { - std::vector input_runners; - for (auto runner : task1.input_runners_) { - input_runners.push_back(runner); - } - for (auto runner : task2.input_runners_) { - input_runners.push_back(runner); - } - return ClusterTask(root, input_runners, task2.route_info_); - } - - static const Runner* GetRequestInput(const ClusterTask& task) { - if (!task.IsValid()) { - return nullptr; - } - auto input_task = task.GetInput(); - if (input_task) { - return input_task->GetRoot(); - } - return nullptr; - } - - const RouteInfo& GetRouteInfo() const { return route_info_; } - - protected: - Runner* root_; - std::vector input_runners_; - RouteInfo route_info_; -}; - -class ClusterJob { - public: - ClusterJob() - : tasks_(), main_task_id_(-1), sql_(""), common_column_indices_() {} - explicit ClusterJob(const std::string& sql, const std::string& db, - const std::set& common_column_indices) - : tasks_(), - main_task_id_(-1), - sql_(sql), - db_(db), - common_column_indices_(common_column_indices) {} - ClusterTask GetTask(int32_t id) { - if (id < 0 || id >= static_cast(tasks_.size())) { - LOG(WARNING) << "fail get task: task " << id << " not exist"; - return ClusterTask(); - } - return tasks_[id]; - } - - ClusterTask GetMainTask() { return GetTask(main_task_id_); } - int32_t AddTask(const ClusterTask& task) { - if (!task.IsValid()) { - LOG(WARNING) << "fail to add invalid task"; - return -1; - } - tasks_.push_back(task); - return tasks_.size() - 1; - } - bool AddRunnerToTask(Runner* runner, const int32_t id) { - if (id < 0 || id >= static_cast(tasks_.size())) { - LOG(WARNING) << "fail update task: task " << id << " not exist"; - return false; - } - runner->AddProducer(tasks_[id].GetRoot()); - tasks_[id].SetRoot(runner); - return true; - } - - void AddMainTask(const ClusterTask& task) { main_task_id_ = AddTask(task); } - void Reset() { tasks_.clear(); } - const size_t GetTaskSize() const { return tasks_.size(); } - const bool IsValid() const { return !tasks_.empty(); } - const int32_t main_task_id() const { return main_task_id_; } - const std::string& sql() const { return sql_; } - const std::string& db() const { return db_; } - void Print(std::ostream& output, const std::string& tab) const { - if (tasks_.empty()) { - output << "EMPTY CLUSTER JOB\n"; - return; - } - for (size_t i = 0; i < tasks_.size(); i++) { - if (main_task_id_ == static_cast(i)) { - output << "MAIN TASK ID " << i; - } else { - output << "TASK ID " << i; - } - tasks_[i].Print(output, tab); - output << "\n"; - } - } - const std::set& common_column_indices() const { - return common_column_indices_; - } - void Print() const { this->Print(std::cout, " "); } - - private: - std::vector tasks_; - int32_t main_task_id_; - std::string sql_; - std::string db_; - std::set common_column_indices_; -}; -class RunnerBuilder { - enum TaskBiasType { kLeftBias, kRightBias, kNoBias }; - - public: - explicit RunnerBuilder(node::NodeManager* nm, const std::string& sql, - const std::string& db, - bool support_cluster_optimized, - const std::set& common_column_indices, - const std::set& batch_common_node_set) - : nm_(nm), - support_cluster_optimized_(support_cluster_optimized), - id_(0), - cluster_job_(sql, db, common_column_indices), - task_map_(), - proxy_runner_map_(), - batch_common_node_set_(batch_common_node_set) {} - virtual ~RunnerBuilder() {} - ClusterTask RegisterTask(PhysicalOpNode* node, ClusterTask task) { - task_map_[node] = task; - if (batch_common_node_set_.find(node->node_id()) != - batch_common_node_set_.end()) { - task.GetRoot()->EnableBatchCache(); - } - return task; - } - ClusterTask Build(PhysicalOpNode* node, // NOLINT - Status& status); // NOLINT - ClusterJob BuildClusterJob(PhysicalOpNode* node, - Status& status) { // NOLINT - id_ = 0; - cluster_job_.Reset(); - auto task = // NOLINT whitespace/braces - Build(node, status); - if (!status.isOK()) { - return cluster_job_; - } - - if (task.IsCompletedClusterTask()) { - auto proxy_task = BuildProxyRunnerForClusterTask(task); - if (!proxy_task.IsValid()) { - status.code = common::kExecutionPlanError; - status.msg = "Fail to build proxy cluster task"; - LOG(WARNING) << status; - return cluster_job_; - } - cluster_job_.AddMainTask(proxy_task); - } else if (task.IsUnCompletedClusterTask()) { - status.code = common::kExecutionPlanError; - status.msg = - "Fail to build main task, can't handler " - "uncompleted cluster task"; - LOG(WARNING) << status; - return cluster_job_; - } else { - cluster_job_.AddMainTask(task); - } - return cluster_job_; - } - - template - Op* CreateRunner(Args&&... args) { - return nm_->MakeNode(std::forward(args)...); - } - - private: - node::NodeManager* nm_; - bool support_cluster_optimized_; - int32_t id_; - ClusterJob cluster_job_; - - std::unordered_map<::hybridse::vm::PhysicalOpNode*, - ::hybridse::vm::ClusterTask> - task_map_; - std::shared_ptr request_task_; - std::unordered_map - proxy_runner_map_; - std::set batch_common_node_set_; - ClusterTask MultipleInherit(const std::vector& children, Runner* runner, - const Key& index_key, const TaskBiasType bias); - ClusterTask BinaryInherit(const ClusterTask& left, const ClusterTask& right, - Runner* runner, const Key& index_key, - const TaskBiasType bias = kNoBias); - ClusterTask BuildLocalTaskForBinaryRunner(const ClusterTask& left, - const ClusterTask& right, - Runner* runner); - ClusterTask BuildClusterTaskForBinaryRunner(const ClusterTask& left, - const ClusterTask& right, - Runner* runner, - const Key& index_key, - const TaskBiasType bias); - ClusterTask BuildProxyRunnerForClusterTask(const ClusterTask& task); - ClusterTask InvalidTask() { return ClusterTask(); } - ClusterTask CommonTask(Runner* runner) { return ClusterTask(runner); } - ClusterTask UnCompletedClusterTask( - Runner* runner, const std::shared_ptr table_handler, - std::string index); - ClusterTask BuildRequestTask(RequestRunner* runner); - ClusterTask UnaryInheritTask(const ClusterTask& input, Runner* runner); - ClusterTask BuildRequestAggUnionTask(PhysicalOpNode* node, Status& status); // NOLINT -}; - -class RunnerContext { - public: - explicit RunnerContext(hybridse::vm::ClusterJob* cluster_job, - const hybridse::codec::Row& parameter, - const bool is_debug = false) - : cluster_job_(cluster_job), - sp_name_(""), - request_(), - requests_(), - parameter_(parameter), - is_debug_(is_debug), - batch_cache_() {} - explicit RunnerContext(hybridse::vm::ClusterJob* cluster_job, - const hybridse::codec::Row& request, - const std::string& sp_name = "", - const bool is_debug = false) - : cluster_job_(cluster_job), - sp_name_(sp_name), - request_(request), - requests_(), - parameter_(), - is_debug_(is_debug), - batch_cache_() {} - explicit RunnerContext(hybridse::vm::ClusterJob* cluster_job, - const std::vector& request_batch, - const std::string& sp_name = "", - const bool is_debug = false) - : cluster_job_(cluster_job), - sp_name_(sp_name), - request_(), - requests_(request_batch), - parameter_(), - is_debug_(is_debug), - batch_cache_() {} - - const size_t GetRequestSize() const { return requests_.size(); } - const hybridse::codec::Row& GetRequest() const { return request_; } - const hybridse::codec::Row& GetRequest(size_t idx) const { - return requests_[idx]; - } - const hybridse::codec::Row& GetParameterRow() const { return parameter_; } - hybridse::vm::ClusterJob* cluster_job() { return cluster_job_; } - void SetRequest(const hybridse::codec::Row& request); - void SetRequests(const std::vector& requests); - bool is_debug() const { return is_debug_; } - - const std::string& sp_name() { return sp_name_; } - std::shared_ptr GetCache(int64_t id) const; - void SetCache(int64_t id, std::shared_ptr data); - void ClearCache() { cache_.clear(); } - std::shared_ptr GetBatchCache(int64_t id) const; - void SetBatchCache(int64_t id, std::shared_ptr data); - - private: - hybridse::vm::ClusterJob* cluster_job_; - const std::string sp_name_; - hybridse::codec::Row request_; - std::vector requests_; - hybridse::codec::Row parameter_; - size_t idx_; - const bool is_debug_; - // TODO(chenjing): optimize - std::map> cache_; - std::map> batch_cache_; -}; } // namespace vm } // namespace hybridse diff --git a/hybridse/src/vm/runner_builder.cc b/hybridse/src/vm/runner_builder.cc new file mode 100644 index 00000000000..5d595ba9785 --- /dev/null +++ b/hybridse/src/vm/runner_builder.cc @@ -0,0 +1,909 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "vm/runner_builder.h" +#include "vm/physical_op.h" + +namespace hybridse { +namespace vm { + +static vm::PhysicalDataProviderNode* request_node(vm::PhysicalOpNode* n) { + switch (n->GetOpType()) { + case kPhysicalOpDataProvider: + return dynamic_cast(n); + default: + return request_node(n->GetProducer(0)); + } +} + +// Build Runner for each physical node +// return cluster task of given runner +// +// DataRunner(kProviderTypePartition) --> cluster task +// RequestRunner --> local task +// DataRunner(kProviderTypeTable) --> LocalTask, Unsupport in distribute +// database +// +// SimpleProjectRunner --> inherit task +// TableProjectRunner --> inherit task +// WindowAggRunner --> LocalTask , Unsupport in distribute database +// GroupAggRunner --> LocalTask, Unsupport in distribute database +// +// RowProjectRunner --> inherit task +// ConstProjectRunner --> local task +// +// RequestUnionRunner +// --> complete route_info of right cluster task +// --> build proxy runner if need +// RequestJoinRunner +// --> complete route_info of right cluster task +// --> build proxy runner if need +// kPhysicalOpJoin +// --> kJoinTypeLast->RequestJoinRunner +// --> complete route_info of right cluster task +// --> build proxy runner if need +// --> kJoinTypeConcat +// --> build proxy runner if need +// kPhysicalOpPostRequestUnion +// --> build proxy runner if need +// GroupRunner --> LocalTask, Unsupport in distribute database +// kPhysicalOpFilter +// kPhysicalOpLimit +// kPhysicalOpRename +ClusterTask RunnerBuilder::Build(PhysicalOpNode* node, Status& status) { + auto fail = InvalidTask(); + if (nullptr == node) { + status.msg = "fail to build runner : physical node is null"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto iter = task_map_.find(node); + if (iter != task_map_.cend()) { + iter->second.GetRoot()->EnableCache(); + return iter->second; + } + switch (node->GetOpType()) { + case kPhysicalOpDataProvider: { + auto op = dynamic_cast(node); + switch (op->provider_type_) { + case kProviderTypeTable: { + auto provider = dynamic_cast(node); + DataRunner* runner = CreateRunner(id_++, node->schemas_ctx(), provider->table_handler_); + return RegisterTask(node, CommonTask(runner)); + } + case kProviderTypePartition: { + auto provider = dynamic_cast(node); + DataRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), provider->table_handler_->GetPartition(provider->index_name_)); + if (support_cluster_optimized_) { + return RegisterTask( + node, UnCompletedClusterTask(runner, provider->table_handler_, provider->index_name_)); + } else { + return RegisterTask(node, CommonTask(runner)); + } + } + case kProviderTypeRequest: { + RequestRunner* runner = CreateRunner(id_++, node->schemas_ctx()); + return RegisterTask(node, BuildRequestTask(runner)); + } + default: { + status.msg = "fail to support data provider type " + DataProviderTypeName(op->provider_type_); + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return RegisterTask(node, fail); + } + } + } + case kPhysicalOpSimpleProject: { + auto cluster_task = Build(node->producers().at(0), status); + if (!cluster_task.IsValid()) { + status.msg = "fail to build input runner for simple project:\n" + node->GetTreeString(); + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + int select_slice = op->GetSelectSourceIndex(); + if (select_slice >= 0) { + SelectSliceRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), select_slice); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } else { + SimpleProjectRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), op->GetLimitCnt(), op->project().fn_info()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + } + case kPhysicalOpConstProject: { + auto op = dynamic_cast(node); + ConstProjectRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), + op->project().fn_info()); + return RegisterTask(node, CommonTask(runner)); + } + case kPhysicalOpProject: { + auto cluster_task = // NOLINT + Build(node->producers().at(0), status); + if (!cluster_task.IsValid()) { + status.msg = "fail to build runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto input = cluster_task.GetRoot(); + auto op = dynamic_cast(node); + switch (op->project_type_) { + case kTableProject: { + if (support_cluster_optimized_) { + // Non-support table join under distribution env + status.msg = "fail to build cluster with table project"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + TableProjectRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), op->GetLimitCnt(), op->project().fn_info()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kReduceAggregation: { + ReduceRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), op->GetLimitCnt(), + dynamic_cast(node)->having_condition_, + op->project().fn_info()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kAggregation: { + auto agg_node = dynamic_cast(node); + if (agg_node == nullptr) { + status.msg = "fail to build AggRunner: input node is not PhysicalAggregationNode"; + status.code = common::kExecutionPlanError; + return fail; + } + AggRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), + agg_node->having_condition_, op->project().fn_info()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kGroupAggregation: { + if (support_cluster_optimized_) { + // Non-support group aggregation under distribution env + status.msg = "fail to build cluster with group agg project"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + GroupAggRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->group_, + op->having_condition_, op->project().fn_info()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kWindowAggregation: { + if (support_cluster_optimized_) { + // Non-support table window aggregation join under distribution env + status.msg = "fail to build cluster with window agg project"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + WindowAggRunner* runner = CreateRunner( + id_++, op->schemas_ctx(), op->GetLimitCnt(), op->window_, op->project().fn_info(), + op->instance_not_in_window(), op->exclude_current_time(), + op->need_append_input() ? node->GetProducer(0)->schemas_ctx()->GetSchemaSourceSize() : 0); + size_t input_slices = input->output_schemas()->GetSchemaSourceSize(); + if (!op->window_unions_.Empty()) { + for (auto window_union : op->window_unions_.window_unions_) { + auto union_task = Build(window_union.first, status); + auto union_table = union_task.GetRoot(); + if (nullptr == union_table) { + return RegisterTask(node, fail); + } + runner->AddWindowUnion(window_union.second, union_table); + } + } + if (!op->window_joins_.Empty()) { + for (auto& window_join : op->window_joins_.window_joins_) { + auto join_task = // NOLINT + Build(window_join.first, status); + auto join_right_runner = join_task.GetRoot(); + if (nullptr == join_right_runner) { + return RegisterTask(node, fail); + } + runner->AddWindowJoin(window_join.second, input_slices, join_right_runner); + } + } + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kRowProject: { + RowProjectRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), op->GetLimitCnt(), op->project().fn_info()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + default: { + status.msg = "fail to support project type " + ProjectTypeName(op->project_type_); + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return RegisterTask(node, fail); + } + } + } + case kPhysicalOpRequestUnion: { + auto left_task = Build(node->producers().at(0), status); + if (!left_task.IsValid()) { + status.msg = "fail to build left input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto right_task = Build(node->producers().at(1), status); + auto right = right_task.GetRoot(); + if (!right_task.IsValid()) { + status.msg = "fail to build right input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + RequestUnionRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->window().range_, + op->exclude_current_time(), op->output_request_row()); + Key index_key; + if (!op->instance_not_in_window()) { + runner->AddWindowUnion(op->window_, right); + index_key = op->window_.index_key_; + } + if (!op->window_unions_.Empty()) { + for (auto window_union : op->window_unions_.window_unions_) { + auto union_task = Build(window_union.first, status); + if (!status.isOK()) { + LOG(WARNING) << status; + return fail; + } + auto union_table = union_task.GetRoot(); + if (nullptr == union_table) { + return RegisterTask(node, fail); + } + runner->AddWindowUnion(window_union.second, union_table); + if (!index_key.ValidKey()) { + index_key = window_union.second.index_key_; + right_task = union_task; + right_task.SetRoot(right); + } + } + } + if (support_cluster_optimized_) { + if (node->GetOutputType() == kSchemaTypeGroup) { + // route by index of the left source, and it should uncompleted + auto& route_info = left_task.GetRouteInfo(); + runner->AddProducer(left_task.GetRoot()); + runner->AddProducer(right_task.GetRoot()); + return RegisterTask(node, ClusterTask(runner, {}, route_info)); + } + } + return RegisterTask(node, BinaryInherit(left_task, right_task, runner, index_key, kRightBias)); + } + case kPhysicalOpRequestAggUnion: { + return BuildRequestAggUnionTask(node, status); + } + case kPhysicalOpRequestJoin: { + auto left_task = Build(node->GetProducer(0), status); + if (!left_task.IsValid()) { + status.msg = "fail to build left input runner for: " + node->GetProducer(0)->GetTreeString(); + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto left = left_task.GetRoot(); + auto right_task = Build(node->GetProducer(1), status); + if (!right_task.IsValid()) { + status.msg = "fail to build right input runner for: " + node->GetProducer(1)->GetTreeString(); + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto right = right_task.GetRoot(); + auto op = dynamic_cast(node); + switch (op->join().join_type()) { + case node::kJoinTypeLast: + case node::kJoinTypeLeft: { + RequestJoinRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), op->GetLimitCnt(), op->join_, + left->output_schemas()->GetSchemaSourceSize(), right->output_schemas()->GetSchemaSourceSize(), + op->output_right_only()); + + if (support_cluster_optimized_) { + if (node->GetOutputType() == kSchemaTypeRow) { + // complete cluster task from right + if (op->join().index_key().ValidKey()) { + // optimize key in this node + return RegisterTask(node, BinaryInherit(left_task, right_task, runner, + op->join().index_key(), kLeftBias)); + } else { + // optimize happens before, in left node + auto right_route_info = right_task.GetRouteInfo(); + runner->AddProducer(left_task.GetRoot()); + runner->AddProducer(right_task.GetRoot()); + return RegisterTask(node, ClusterTask(runner, {}, right_route_info)); + } + } else { + // uncomplete/lazify cluster task from left + auto left_route_info = left_task.GetRouteInfo(); + runner->AddProducer(left_task.GetRoot()); + runner->AddProducer(right_task.GetRoot()); + return RegisterTask(node, ClusterTask(runner, {}, left_route_info)); + } + } + + return RegisterTask( + node, BinaryInherit(left_task, right_task, runner, op->join().index_key(), kLeftBias)); + } + case node::kJoinTypeConcat: { + ConcatRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt()); + if (support_cluster_optimized_) { + if (right_task.IsCompletedClusterTask() && right_task.GetRouteInfo().lazy_route_ && + !op->join_.index_key_.ValidKey()) { + // concat join (.., filter) + runner->AddProducer(left_task.GetRoot()); + runner->AddProducer(right_task.GetRoot()); + return RegisterTask(node, ClusterTask(runner, {}, RouteInfo{})); + } + + // concat join (any(tx), any(tx)), tx is not request table + if (node->GetOutputType() != kSchemaTypeRow) { + runner->AddProducer(left_task.GetRoot()); + runner->AddProducer(right_task.GetRoot()); + return RegisterTask(node, ClusterTask(runner, {}, left_task.GetRouteInfo())); + } + } + return RegisterTask(node, BinaryInherit(left_task, right_task, runner, Key(), kNoBias)); + } + default: { + status.code = common::kExecutionPlanError; + status.msg = "can't handle join type " + node::JoinTypeName(op->join().join_type()); + LOG(WARNING) << status; + return RegisterTask(node, fail); + } + } + } + case kPhysicalOpJoin: { + auto left_task = Build(node->producers().at(0), status); + if (!left_task.IsValid()) { + status.msg = "fail to build left input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto left = left_task.GetRoot(); + auto right_task = Build(node->producers().at(1), status); + if (!right_task.IsValid()) { + status.msg = "fail to build right input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto right = right_task.GetRoot(); + auto op = dynamic_cast(node); + switch (op->join().join_type()) { + case node::kJoinTypeLeft: + case node::kJoinTypeLast: { + // TableLastJoin convert to Batch Request RequestLastJoin + if (support_cluster_optimized_) { + // looks strange, join op won't run for batch-cluster mode + RequestJoinRunner* runner = CreateRunner( + id_++, node->schemas_ctx(), op->GetLimitCnt(), op->join_, + left->output_schemas()->GetSchemaSourceSize(), + right->output_schemas()->GetSchemaSourceSize(), op->output_right_only_); + return RegisterTask( + node, BinaryInherit(left_task, right_task, runner, op->join().index_key(), kLeftBias)); + } else { + JoinRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->join_, + left->output_schemas()->GetSchemaSourceSize(), + right->output_schemas()->GetSchemaSourceSize()); + return RegisterTask(node, BinaryInherit(left_task, right_task, runner, Key(), kLeftBias)); + } + } + case node::kJoinTypeConcat: { + ConcatRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt()); + return RegisterTask(node, + BinaryInherit(left_task, right_task, runner, op->join().index_key(), kNoBias)); + } + default: { + status.code = common::kExecutionPlanError; + status.msg = "can't handle join type " + node::JoinTypeName(op->join().join_type()); + LOG(WARNING) << status; + return RegisterTask(node, fail); + } + } + } + case kPhysicalOpGroupBy: { + if (support_cluster_optimized_) { + // Non-support group by under distribution env + status.msg = "fail to build cluster with group by node"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto cluster_task = Build(node->producers().at(0), status); + if (!cluster_task.IsValid()) { + status.msg = "fail to build input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + GroupRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->group()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kPhysicalOpFilter: { + auto producer_task = Build(node->GetProducer(0), status); + if (!producer_task.IsValid()) { + status.msg = "fail to build input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + FilterRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->filter_); + // under cluster, filter task might be completed or uncompleted + // based on whether filter node has the index_key underlaying DataTask requires + ClusterTask out; + if (support_cluster_optimized_) { + auto& route_info_ref = producer_task.GetRouteInfo(); + if (runner->filter_gen_.ValidIndex()) { + // complete the route info + RouteInfo lazy_route_info(route_info_ref.index_, op->filter().index_key(), + std::make_shared(producer_task), + route_info_ref.table_handler_); + lazy_route_info.lazy_route_ = true; + runner->AddProducer(producer_task.GetRoot()); + out = ClusterTask(runner, {}, lazy_route_info); + } else { + runner->AddProducer(producer_task.GetRoot()); + out = UnCompletedClusterTask(runner, route_info_ref.table_handler_, route_info_ref.index_); + } + } else { + out = UnaryInheritTask(producer_task, runner); + } + return RegisterTask(node, out); + } + case kPhysicalOpLimit: { + auto cluster_task = // NOLINT + Build(node->producers().at(0), status); + if (!cluster_task.IsValid()) { + status.msg = "fail to build input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + if (!op->GetLimitCnt().has_value() || op->GetLimitOptimized()) { + return RegisterTask(node, cluster_task); + } + // limit runner always expect limit not empty + LimitRunner* runner = CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt().value()); + return RegisterTask(node, UnaryInheritTask(cluster_task, runner)); + } + case kPhysicalOpRename: { + return Build(node->producers().at(0), status); + } + case kPhysicalOpPostRequestUnion: { + auto left_task = Build(node->producers().at(0), status); + if (!left_task.IsValid()) { + status.msg = "fail to build left input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto right_task = Build(node->producers().at(1), status); + if (!right_task.IsValid()) { + status.msg = "fail to build right input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto union_op = dynamic_cast(node); + PostRequestUnionRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), union_op->request_ts()); + return RegisterTask(node, BinaryInherit(left_task, right_task, runner, Key(), kRightBias)); + } + default: { + status.code = common::kExecutionPlanError; + status.msg = absl::StrCat("Non-support node ", PhysicalOpTypeName(node->GetOpType()), + " for OpenMLDB Online execute mode"); + LOG(WARNING) << status; + return RegisterTask(node, fail); + } + } +} + +ClusterTask RunnerBuilder::BuildRequestAggUnionTask(PhysicalOpNode* node, Status& status) { + auto fail = InvalidTask(); + auto request_task = Build(node->producers().at(0), status); + if (!request_task.IsValid()) { + status.msg = "fail to build request input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto base_table_task = Build(node->producers().at(1), status); + auto base_table = base_table_task.GetRoot(); + if (!base_table_task.IsValid()) { + status.msg = "fail to build base_table input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto agg_table_task = Build(node->producers().at(2), status); + auto agg_table = agg_table_task.GetRoot(); + if (!agg_table_task.IsValid()) { + status.msg = "fail to build agg_table input runner"; + status.code = common::kExecutionPlanError; + LOG(WARNING) << status; + return fail; + } + auto op = dynamic_cast(node); + RequestAggUnionRunner* runner = + CreateRunner(id_++, node->schemas_ctx(), op->GetLimitCnt(), op->window().range_, + op->exclude_current_time(), op->output_request_row(), op->project_); + Key index_key; + if (!op->instance_not_in_window()) { + index_key = op->window_.index_key(); + runner->AddWindowUnion(op->window_, base_table); + runner->AddWindowUnion(op->agg_window_, agg_table); + } + auto task = RegisterTask( + node, MultipleInherit({&request_task, &base_table_task, &agg_table_task}, runner, index_key, kRightBias)); + if (!runner->InitAggregator()) { + return fail; + } else { + return task; + } +} + +ClusterTask RunnerBuilder::BinaryInherit(const ClusterTask& left, const ClusterTask& right, Runner* runner, + const Key& index_key, const TaskBiasType bias) { + if (support_cluster_optimized_) { + return BuildClusterTaskForBinaryRunner(left, right, runner, index_key, bias); + } else { + return BuildLocalTaskForBinaryRunner(left, right, runner); + } +} + +ClusterTask RunnerBuilder::MultipleInherit(const std::vector& children, Runner* runner, + const Key& index_key, const TaskBiasType bias) { + // TODO(zhanghao): currently only kRunnerRequestAggUnion uses MultipleInherit + const ClusterTask* request = children[0]; + if (runner->type_ != kRunnerRequestAggUnion) { + LOG(WARNING) << "MultipleInherit only support RequestAggUnionRunner"; + return ClusterTask(); + } + + if (children.size() < 3) { + LOG(WARNING) << "MultipleInherit should be called for children size >= 3, but children.size() = " + << children.size(); + return ClusterTask(); + } + + for (const auto child : children) { + if (child->IsClusterTask()) { + if (index_key.ValidKey()) { + for (size_t i = 1; i < children.size(); i++) { + if (!children[i]->IsClusterTask()) { + LOG(WARNING) << "Fail to build cluster task for " + << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) + << ": can't handler local task with index key"; + return ClusterTask(); + } + if (children[i]->IsCompletedClusterTask()) { + LOG(WARNING) << "Fail to complete cluster task for " + << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) + << ": task is completed already"; + return ClusterTask(); + } + } + for (size_t i = 0; i < children.size(); i++) { + runner->AddProducer(children[i]->GetRoot()); + } + // build complete cluster task + // TODO(zhanghao): assume all children can be handled with one single tablet + const RouteInfo& route_info = children[1]->GetRouteInfo(); + ClusterTask cluster_task(runner, std::vector({runner}), + RouteInfo(route_info.index_, index_key, + std::make_shared(*request), route_info.table_handler_)); + return cluster_task; + } + } + } + + // if all are local tasks + for (const auto child : children) { + runner->AddProducer(child->GetRoot()); + } + return ClusterTask(runner); +} + +ClusterTask RunnerBuilder::BuildLocalTaskForBinaryRunner(const ClusterTask& left, const ClusterTask& right, + Runner* runner) { + if (left.IsClusterTask() || right.IsClusterTask()) { + LOG(WARNING) << "fail to build local task for binary runner"; + return ClusterTask(); + } + runner->AddProducer(left.GetRoot()); + runner->AddProducer(right.GetRoot()); + return ClusterTask(runner); +} + +ClusterTask RunnerBuilder::BuildClusterTaskForBinaryRunner(const ClusterTask& left, const ClusterTask& right, + Runner* runner, const Key& index_key, + const TaskBiasType bias) { + if (nullptr == runner) { + LOG(WARNING) << "Fail to build cluster task for null runner"; + return ClusterTask(); + } + ClusterTask new_left = left; + ClusterTask new_right = right; + + // if index key is valid, try to complete route info of right cluster task + if (index_key.ValidKey()) { + if (!right.IsClusterTask()) { + LOG(WARNING) << "Fail to build cluster task for " + << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) + << ": can't handler local task with index key"; + return ClusterTask(); + } + if (right.IsCompletedClusterTask()) { + // completed with same index key + std::stringstream ss; + right.Print(ss, " "); + LOG(WARNING) << "Fail to complete cluster task for " + << "[" << runner->id_ << "]" << RunnerTypeName(runner->type_) + << ": task is completed already:\n" + << ss.str(); + LOG(WARNING) << "index key is " << index_key.ToString(); + return ClusterTask(); + } + RequestRunner* request_runner = CreateRunner(id_++, new_left.GetRoot()->output_schemas()); + runner->AddProducer(request_runner); + runner->AddProducer(new_right.GetRoot()); + + const RouteInfo& right_route_info = new_right.GetRouteInfo(); + ClusterTask cluster_task(runner, std::vector({runner}), + RouteInfo(right_route_info.index_, index_key, std::make_shared(new_left), + right_route_info.table_handler_)); + + if (new_left.IsCompletedClusterTask()) { + return BuildProxyRunnerForClusterTask(cluster_task); + } else { + return cluster_task; + } + } + + // Concat + // Agg1(Proxy(RequestUnion(Request, DATA)) + // Agg2(Proxy(RequestUnion(Request, DATA)) + // --> + // Proxy(Concat + // Agg1(RequestUnion(Request,DATA) + // Agg2(RequestUnion(Request,DATA) + // ) + + // if left and right is completed cluster task + while (new_left.IsCompletedClusterTask() && new_right.IsCompletedClusterTask()) { + // merge left and right task if tasks can be merged + if (ClusterTask::TaskCanBeMerge(new_left, new_right)) { + ClusterTask task = ClusterTask::TaskMerge(runner, new_left, new_right); + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return task; + } + switch (bias) { + case kNoBias: { + // Add build left proxy task into cluster job, + // and update new_left + new_left = BuildProxyRunnerForClusterTask(new_left); + new_right = BuildProxyRunnerForClusterTask(new_right); + break; + } + case kLeftBias: { + // build proxy runner for right task + new_right = BuildProxyRunnerForClusterTask(new_right); + break; + } + case kRightBias: { + // build proxy runner for right task + new_left = BuildProxyRunnerForClusterTask(new_left); + break; + } + } + } + if (new_left.IsUnCompletedClusterTask()) { + LOG(WARNING) << "can't handler uncompleted cluster task from left:" << new_left; + return ClusterTask(); + } + if (new_right.IsUnCompletedClusterTask()) { + LOG(WARNING) << "can't handler uncompleted cluster task from right:" << new_right; + return ClusterTask(); + } + + // prepare left and right for runner + + // left local task + right cluster task + if (new_right.IsCompletedClusterTask()) { + switch (bias) { + case kNoBias: + case kLeftBias: { + new_right = BuildProxyRunnerForClusterTask(new_right); + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToLeft(runner, new_left, new_right); + } + case kRightBias: { + auto new_left_root_input = ClusterTask::GetRequestInput(new_left); + auto new_right_root_input = ClusterTask::GetRequestInput(new_right); + // task can be merge simply when their inputs are the same + if (new_right_root_input == new_left_root_input) { + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToRight(runner, new_left, new_right); + } else if (new_left_root_input == nullptr) { + // reset replace inputs as request runner + new_right.ResetInputs(nullptr); + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToRight(runner, new_left, new_right); + } else { + LOG(WARNING) << "fail to merge local left task and cluster " + "right task"; + return ClusterTask(); + } + } + default: + return ClusterTask(); + } + } else if (new_left.IsCompletedClusterTask()) { + switch (bias) { + case kNoBias: + case kRightBias: { + new_left = BuildProxyRunnerForClusterTask(new_left); + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToRight(runner, new_left, new_right); + } + case kLeftBias: { + auto new_left_root_input = ClusterTask::GetRequestInput(new_right); + auto new_right_root_input = ClusterTask::GetRequestInput(new_right); + // task can be merge simply + if (new_right_root_input == new_left_root_input) { + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToLeft(runner, new_left, new_right); + } else if (new_right_root_input == nullptr) { + // reset replace inputs as request runner + new_left.ResetInputs(nullptr); + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToLeft(runner, new_left, new_right); + } else { + LOG(WARNING) << "fail to merge cluster left task and local " + "right task"; + return ClusterTask(); + } + } + default: + return ClusterTask(); + } + } else { + runner->AddProducer(new_left.GetRoot()); + runner->AddProducer(new_right.GetRoot()); + return ClusterTask::TaskMergeToLeft(runner, new_left, new_right); + } +} +ClusterTask RunnerBuilder::BuildProxyRunnerForClusterTask(const ClusterTask& task) { + if (!task.IsCompletedClusterTask()) { + LOG(WARNING) << "Fail to build proxy runner, cluster task is uncompleted"; + return ClusterTask(); + } + // return cached proxy runner + Runner* proxy_runner = nullptr; + auto find_iter = proxy_runner_map_.find(task.GetRoot()); + if (find_iter != proxy_runner_map_.cend()) { + proxy_runner = find_iter->second; + proxy_runner->EnableCache(); + } else { + uint32_t remote_task_id = cluster_job_.AddTask(task); + ProxyRequestRunner* new_proxy_runner = CreateRunner( + id_++, remote_task_id, task.GetIndexKeyInput(), task.GetRoot()->output_schemas()); + if (nullptr != task.GetIndexKeyInput()) { + task.GetIndexKeyInput()->EnableCache(); + } + if (task.GetRoot()->need_batch_cache()) { + new_proxy_runner->EnableBatchCache(); + } + proxy_runner_map_.insert(std::make_pair(task.GetRoot(), new_proxy_runner)); + proxy_runner = new_proxy_runner; + } + + if (task.GetInput()) { + return UnaryInheritTask(*task.GetInput(), proxy_runner); + } else { + return UnaryInheritTask(*request_task_, proxy_runner); + } + LOG(WARNING) << "Fail to build proxy runner for cluster job"; + return ClusterTask(); +} + +ClusterTask RunnerBuilder::UnCompletedClusterTask(Runner* runner, const std::shared_ptr table_handler, + std::string index) { + return ClusterTask(runner, table_handler, index); +} + +ClusterTask RunnerBuilder::BuildRequestTask(RequestRunner* runner) { + if (nullptr == runner) { + LOG(WARNING) << "fail to build request task with null runner"; + return ClusterTask(); + } + ClusterTask request_task(runner); + request_task_ = std::make_shared(request_task); + return request_task; +} +ClusterTask RunnerBuilder::UnaryInheritTask(const ClusterTask& input, Runner* runner) { + ClusterTask task = input; + runner->AddProducer(task.GetRoot()); + task.SetRoot(runner); + return task; +} + +ClusterTask RunnerBuilder::RegisterTask(PhysicalOpNode* node, ClusterTask task) { + task_map_[node] = task; + if (batch_common_node_set_.find(node->node_id()) != batch_common_node_set_.end()) { + task.GetRoot()->EnableBatchCache(); + } + return task; +} +ClusterJob RunnerBuilder::BuildClusterJob(PhysicalOpNode* node, Status& status) { + id_ = 0; + cluster_job_.Reset(); + auto task = Build(node, status); + if (!status.isOK()) { + return cluster_job_; + } + + if (task.IsCompletedClusterTask()) { + auto proxy_task = BuildProxyRunnerForClusterTask(task); + if (!proxy_task.IsValid()) { + status.code = common::kExecutionPlanError; + status.msg = "Fail to build proxy cluster task"; + LOG(WARNING) << status; + return cluster_job_; + } + cluster_job_.AddMainTask(proxy_task); + } else if (task.IsUnCompletedClusterTask()) { + status.code = common::kExecutionPlanError; + status.msg = + "Fail to build main task, can't handler " + "uncompleted cluster task"; + LOG(WARNING) << status; + return cluster_job_; + } else { + cluster_job_.AddMainTask(task); + } + return cluster_job_; +} + +} // namespace vm +} // namespace hybridse diff --git a/hybridse/src/vm/runner_builder.h b/hybridse/src/vm/runner_builder.h new file mode 100644 index 00000000000..fb403ef5639 --- /dev/null +++ b/hybridse/src/vm/runner_builder.h @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HYBRIDSE_SRC_VM_RUNNER_BUILDER_H_ +#define HYBRIDSE_SRC_VM_RUNNER_BUILDER_H_ + +#include +#include +#include +#include +#include +#include + +#include "node/node_manager.h" +#include "vm/cluster_task.h" +#include "vm/runner.h" + +namespace hybridse { +namespace vm { + +class RunnerBuilder { + enum TaskBiasType { kLeftBias, kRightBias, kNoBias }; + + public: + explicit RunnerBuilder(node::NodeManager* nm, const std::string& sql, const std::string& db, + bool support_cluster_optimized, const std::set& common_column_indices, + const std::set& batch_common_node_set) + : nm_(nm), + support_cluster_optimized_(support_cluster_optimized), + id_(0), + cluster_job_(sql, db, common_column_indices), + task_map_(), + proxy_runner_map_(), + batch_common_node_set_(batch_common_node_set) {} + virtual ~RunnerBuilder() {} + ClusterTask RegisterTask(PhysicalOpNode* node, ClusterTask task); + ClusterTask Build(PhysicalOpNode* node, // NOLINT + Status& status); // NOLINT + ClusterJob BuildClusterJob(PhysicalOpNode* node, Status& status); // NOLINT + + template + Op* CreateRunner(Args&&... args) { + return nm_->MakeNode(std::forward(args)...); + } + + private: + ClusterTask MultipleInherit(const std::vector& children, Runner* runner, const Key& index_key, + const TaskBiasType bias); + ClusterTask BinaryInherit(const ClusterTask& left, const ClusterTask& right, Runner* runner, const Key& index_key, + const TaskBiasType bias = kNoBias); + ClusterTask BuildLocalTaskForBinaryRunner(const ClusterTask& left, const ClusterTask& right, Runner* runner); + ClusterTask BuildClusterTaskForBinaryRunner(const ClusterTask& left, const ClusterTask& right, Runner* runner, + const Key& index_key, const TaskBiasType bias); + ClusterTask BuildProxyRunnerForClusterTask(const ClusterTask& task); + ClusterTask InvalidTask() { return ClusterTask(); } + ClusterTask CommonTask(Runner* runner) { return ClusterTask(runner); } + ClusterTask UnCompletedClusterTask(Runner* runner, const std::shared_ptr table_handler, + std::string index); + ClusterTask BuildRequestTask(RequestRunner* runner); + ClusterTask UnaryInheritTask(const ClusterTask& input, Runner* runner); + ClusterTask BuildRequestAggUnionTask(PhysicalOpNode* node, Status& status); // NOLINT + + private: + node::NodeManager* nm_; + // only set for request mode + bool support_cluster_optimized_; + int32_t id_; + ClusterJob cluster_job_; + + std::unordered_map<::hybridse::vm::PhysicalOpNode*, ::hybridse::vm::ClusterTask> task_map_; + std::shared_ptr request_task_; + std::unordered_map proxy_runner_map_; + std::set batch_common_node_set_; +}; + +} // namespace vm +} // namespace hybridse + +#endif // HYBRIDSE_SRC_VM_RUNNER_BUILDER_H_ diff --git a/hybridse/src/vm/runner_ctx.cc b/hybridse/src/vm/runner_ctx.cc new file mode 100644 index 00000000000..f18bef8065f --- /dev/null +++ b/hybridse/src/vm/runner_ctx.cc @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "vm/runner_ctx.h" + +namespace hybridse { +namespace vm { + +std::shared_ptr RunnerContext::GetBatchCache(int64_t id) const { + auto iter = batch_cache_.find(id); + if (iter == batch_cache_.end()) { + return std::shared_ptr(); + } else { + return iter->second; + } +} + +void RunnerContext::SetBatchCache(int64_t id, std::shared_ptr data) { batch_cache_[id] = data; } + +std::shared_ptr RunnerContext::GetCache(int64_t id) const { + auto iter = cache_.find(id); + if (iter == cache_.end()) { + return std::shared_ptr(); + } else { + return iter->second; + } +} + +void RunnerContext::SetCache(int64_t id, const std::shared_ptr data) { cache_[id] = data; } + +void RunnerContext::SetRequest(const hybridse::codec::Row& request) { request_ = request; } +void RunnerContext::SetRequests(const std::vector& requests) { requests_ = requests; } + +} // namespace vm +} // namespace hybridse diff --git a/hybridse/src/vm/runner_ctx.h b/hybridse/src/vm/runner_ctx.h new file mode 100644 index 00000000000..0924015450a --- /dev/null +++ b/hybridse/src/vm/runner_ctx.h @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2023 OpenMLDB authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HYBRIDSE_SRC_VM_RUNNER_CTX_H_ +#define HYBRIDSE_SRC_VM_RUNNER_CTX_H_ + +#include +#include +#include +#include + +#include "vm/cluster_task.h" + +namespace hybridse { +namespace vm { + +class RunnerContext { + public: + explicit RunnerContext(hybridse::vm::ClusterJob* cluster_job, + const hybridse::codec::Row& parameter, + const bool is_debug = false) + : cluster_job_(cluster_job), + sp_name_(""), + request_(), + requests_(), + parameter_(parameter), + is_debug_(is_debug), + batch_cache_() {} + explicit RunnerContext(hybridse::vm::ClusterJob* cluster_job, + const hybridse::codec::Row& request, + const std::string& sp_name = "", + const bool is_debug = false) + : cluster_job_(cluster_job), + sp_name_(sp_name), + request_(request), + requests_(), + parameter_(), + is_debug_(is_debug), + batch_cache_() {} + explicit RunnerContext(hybridse::vm::ClusterJob* cluster_job, + const std::vector& request_batch, + const std::string& sp_name = "", + const bool is_debug = false) + : cluster_job_(cluster_job), + sp_name_(sp_name), + request_(), + requests_(request_batch), + parameter_(), + is_debug_(is_debug), + batch_cache_() {} + + const size_t GetRequestSize() const { return requests_.size(); } + const hybridse::codec::Row& GetRequest() const { return request_; } + const hybridse::codec::Row& GetRequest(size_t idx) const { + return requests_[idx]; + } + const hybridse::codec::Row& GetParameterRow() const { return parameter_; } + hybridse::vm::ClusterJob* cluster_job() { return cluster_job_; } + void SetRequest(const hybridse::codec::Row& request); + void SetRequests(const std::vector& requests); + bool is_debug() const { return is_debug_; } + + const std::string& sp_name() { return sp_name_; } + std::shared_ptr GetCache(int64_t id) const; + void SetCache(int64_t id, std::shared_ptr data); + void ClearCache() { cache_.clear(); } + std::shared_ptr GetBatchCache(int64_t id) const; + void SetBatchCache(int64_t id, std::shared_ptr data); + + private: + hybridse::vm::ClusterJob* cluster_job_; + const std::string sp_name_; + hybridse::codec::Row request_; + std::vector requests_; + hybridse::codec::Row parameter_; + size_t idx_; + const bool is_debug_; + // TODO(chenjing): optimize + std::map> cache_; + std::map> batch_cache_; +}; + +} // namespace vm +} // namespace hybridse + +#endif // HYBRIDSE_SRC_VM_RUNNER_CTX_H_ diff --git a/hybridse/src/vm/runner_test.cc b/hybridse/src/vm/runner_test.cc index 177513a717f..ea8d9c9643e 100644 --- a/hybridse/src/vm/runner_test.cc +++ b/hybridse/src/vm/runner_test.cc @@ -15,26 +15,11 @@ */ #include -#include #include "absl/strings/match.h" -#include "boost/algorithm/string.hpp" #include "case/sql_case.h" #include "gtest/gtest.h" -#include "llvm/ExecutionEngine/Orc/LLJIT.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/InstrTypes.h" -#include "llvm/IR/LegacyPassManager.h" -#include "llvm/IR/Module.h" -#include "llvm/Support/InitLLVM.h" #include "llvm/Support/TargetSelect.h" -#include "llvm/Support/raw_ostream.h" -#include "llvm/Transforms/AggressiveInstCombine/AggressiveInstCombine.h" -#include "llvm/Transforms/InstCombine/InstCombine.h" -#include "llvm/Transforms/Scalar.h" -#include "llvm/Transforms/Scalar/GVN.h" -#include "plan/plan_api.h" #include "testing/test_base.h" #include "vm/sql_compiler.h" diff --git a/hybridse/src/vm/schemas_context.cc b/hybridse/src/vm/schemas_context.cc index 9bff83940bb..7d794bc8f92 100644 --- a/hybridse/src/vm/schemas_context.cc +++ b/hybridse/src/vm/schemas_context.cc @@ -15,7 +15,10 @@ */ #include "vm/schemas_context.h" + #include + +#include "absl/strings/str_join.h" #include "passes/physical/physical_pass.h" #include "vm/physical_op.h" @@ -121,14 +124,18 @@ size_t SchemaSource::size() const { return schema_ == nullptr ? 0 : schema_->size(); } -std::string SchemaSource::ToString() const { +// output: {db}.{table}[ {name}:{type}:{id}, ... ] +std::string SchemaSource::DebugString() const { std::stringstream ss; + ss << source_db_ << "." << source_name_ << "["; for (size_t i = 0; i < column_ids_.size(); ++i) { + ss << schema_->Get(i).name() << ":" << node::TypeName(schema_->Get(i).type()) << ":"; ss << "#" << std::to_string(column_ids_[i]); if (i < column_ids_.size() - 1) { ss << ", "; } } + ss << "]"; return ss.str(); } @@ -173,7 +180,7 @@ void SchemasContext::Merge(size_t child_idx, const SchemasContext* child) { db_name = source->GetSourceDB(); } std::string rel_name = child->GetName(); - if (rel_name.empty()&& !source->GetSourceName().empty()) { + if (rel_name.empty() && !source->GetSourceName().empty()) { rel_name = source->GetSourceName(); } new_source->SetSourceDBAndTableName(db_name, rel_name); @@ -375,40 +382,20 @@ Status DoSearchExprDependentColumns(const node::ExprNode* expr, std::vectorpush_back(expr); break; } - case node::kExprBetween: { - std::vector expr_list; - auto between_expr = dynamic_cast(expr); - CHECK_STATUS(DoSearchExprDependentColumns(between_expr->GetLow(), - columns)); - CHECK_STATUS(DoSearchExprDependentColumns(between_expr->GetHigh(), - columns)); - CHECK_STATUS(DoSearchExprDependentColumns(between_expr->GetLhs(), - columns)); - break; - } case node::kExprCall: { auto call_expr = dynamic_cast(expr); if (nullptr != call_expr->GetOver()) { auto orders = call_expr->GetOver()->GetOrders(); if (nullptr != orders) { - CHECK_STATUS( - DoSearchExprDependentColumns(orders, columns)); + CHECK_STATUS(DoSearchExprDependentColumns(orders, columns)); } auto partitions = call_expr->GetOver()->GetPartitions(); if (nullptr != partitions) { - CHECK_STATUS( - DoSearchExprDependentColumns(partitions, columns)); + CHECK_STATUS(DoSearchExprDependentColumns(partitions, columns)); } } break; } - case node::kExprOrderExpression: { - auto refx = dynamic_cast(expr); - CHECK_TRUE(refx != nullptr, common::kTypeError); - CHECK_STATUS(DoSearchExprDependentColumns(refx->expr(), columns)); - - break; - } default: break; } @@ -751,7 +738,34 @@ void SchemasContext::BuildTrivial( this->Build(); } -RowParser::RowParser(const SchemasContext* schema_ctx) : schema_ctx_(schema_ctx) { +std::string SchemasContext::DebugString() const { + std::stringstream ss; + ss << absl::StrCat("{", root_db_name_, ",", root_relation_name_, ",", default_db_name_, ", ", + absl::StrJoin(schema_sources_, ",", [](std::string* out, const SchemaSource* source) { + absl::StrAppend(out, source->DebugString()); + })); + ss << ", id_map={" + << absl::StrJoin(column_id_map_, ",", [](std::string* out, decltype(column_id_map_)::const_reference e) { + absl::StrAppend(out, e.first, "=(", e.second.first, ",", e.second.second, ")"); + }) << "}, "; + ss << "name_map={" + << absl::StrJoin(column_name_map_, ",", + [](std::string* out, decltype(column_name_map_)::const_reference e) { + absl::StrAppend( + out, e.first, "=[", + absl::StrJoin(e.second, ",", + [](std::string* out, decltype(e.second)::const_reference ref) { + absl::StrAppend(out, "(", ref.first, ",", ref.second, ")"); + }), + "]"); + }) + << "}"; + ss << "}"; + return ss.str(); +} + +RowParser::RowParser(const SchemasContext* schema_ctx) + : schema_ctx_(schema_ctx) { for (size_t i = 0; i < schema_ctx_->GetSchemaSourceSize(); ++i) { auto source = schema_ctx_->GetSchemaSource(i); row_view_list_.push_back(codec::RowView(*source->GetSchema())); diff --git a/hybridse/src/vm/sql_compiler.cc b/hybridse/src/vm/sql_compiler.cc index 7d77432d278..4c819238a6a 100644 --- a/hybridse/src/vm/sql_compiler.cc +++ b/hybridse/src/vm/sql_compiler.cc @@ -18,19 +18,14 @@ #include #include #include -#include "boost/filesystem.hpp" -#include "boost/filesystem/string_file.hpp" #include "codec/fe_schema_codec.h" -#include "codec/type_codec.h" -#include "codegen/block_ir_builder.h" -#include "codegen/fn_ir_builder.h" -#include "codegen/ir_base_builder.h" #include "glog/logging.h" #include "llvm/IR/Verifier.h" #include "llvm/Support/raw_ostream.h" #include "plan/plan_api.h" #include "udf/default_udf_library.h" #include "vm/runner.h" +#include "vm/runner_builder.h" #include "vm/transform.h" #include "vm/engine.h" diff --git a/hybridse/src/vm/sql_compiler.h b/hybridse/src/vm/sql_compiler.h index 861918d9c47..5d4b78e8ea2 100644 --- a/hybridse/src/vm/sql_compiler.h +++ b/hybridse/src/vm/sql_compiler.h @@ -18,15 +18,13 @@ #define HYBRIDSE_SRC_VM_SQL_COMPILER_H_ #include -#include #include -#include #include #include "base/fe_status.h" #include "llvm/IR/Module.h" -#include "proto/fe_common.pb.h" #include "udf/udf_library.h" #include "vm/catalog.h" +#include "vm/cluster_task.h" #include "vm/engine_context.h" #include "vm/jit_wrapper.h" #include "vm/physical_op.h" diff --git a/hybridse/src/vm/sql_compiler_test.cc b/hybridse/src/vm/sql_compiler_test.cc index c415cae3f4e..a7091ce4143 100644 --- a/hybridse/src/vm/sql_compiler_test.cc +++ b/hybridse/src/vm/sql_compiler_test.cc @@ -15,27 +15,16 @@ */ #include "vm/sql_compiler.h" + #include -#include -#include "boost/algorithm/string.hpp" +#include + #include "case/sql_case.h" #include "gtest/gtest.h" -#include "llvm/ExecutionEngine/Orc/LLJIT.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/InstrTypes.h" -#include "llvm/IR/LegacyPassManager.h" -#include "llvm/IR/Module.h" -#include "llvm/Support/InitLLVM.h" #include "llvm/Support/TargetSelect.h" -#include "llvm/Support/raw_ostream.h" -#include "llvm/Transforms/AggressiveInstCombine/AggressiveInstCombine.h" -#include "llvm/Transforms/InstCombine/InstCombine.h" -#include "llvm/Transforms/Scalar.h" -#include "llvm/Transforms/Scalar/GVN.h" -#include "vm/simple_catalog.h" -#include "testing/test_base.h" #include "testing/engine_test_base.h" +#include "testing/test_base.h" +#include "vm/simple_catalog.h" using namespace llvm; // NOLINT using namespace llvm::orc; // NOLINT diff --git a/hybridse/src/vm/transform.cc b/hybridse/src/vm/transform.cc index 60549447e42..dc67a30c9a8 100644 --- a/hybridse/src/vm/transform.cc +++ b/hybridse/src/vm/transform.cc @@ -19,13 +19,13 @@ #include #include #include +#include #include "absl/cleanup/cleanup.h" +#include "base/fe_status.h" #include "codegen/context.h" #include "codegen/fn_ir_builder.h" #include "codegen/fn_let_ir_builder.h" -#include "passes/expression/expr_pass.h" -#include "passes/lambdafy_projects.h" #include "passes/physical/batch_request_optimize.h" #include "passes/physical/cluster_optimized.h" #include "passes/physical/condition_optimized.h" @@ -38,6 +38,7 @@ #include "passes/physical/transform_up_physical_pass.h" #include "passes/physical/window_column_pruning.h" #include "plan/planner.h" +#include "proto/fe_common.pb.h" #include "vm/physical_op.h" #include "vm/schemas_context.h" #include "vm/internal/node_helper.h" @@ -639,16 +640,13 @@ Status RequestModeTransformer::TransformWindowOp(PhysicalOpNode* depend, } case kPhysicalOpDataProvider: { auto data_op = dynamic_cast(depend); - CHECK_TRUE(data_op->provider_type_ == kProviderTypeRequest, - kPlanError, - "Do not support window on non-request input"); + CHECK_TRUE(data_op->provider_type_ != kProviderTypePartition, kPlanError, "data node already a partition"); auto name = data_op->table_handler_->GetName(); auto db_name = data_op->table_handler_->GetDatabase(); auto table = catalog_->GetTable(db_name, name); - CHECK_TRUE(table != nullptr, kPlanError, - "Fail to transform data provider op: table " + name + - "not exists"); + CHECK_TRUE(table != nullptr, kPlanError, "Fail to transform data provider op: table ", name, "not exists"); + PhysicalTableProviderNode* right = nullptr; CHECK_STATUS(CreateOp(&right, table)); @@ -657,6 +655,12 @@ Status RequestModeTransformer::TransformWindowOp(PhysicalOpNode* depend, data_op, right, table->GetDatabase(), table->GetName(), table->GetSchema(), nullptr, w_ptr, &request_union_op)); + if (data_op->provider_type_ == kProviderTypeTable && !request_union_op->instance_not_in_window()) { + // REQUEST_UNION(t1, t1) do not has request table, dont output reqeust row, + // but should output if REQUEST_UNION(t1, t1, unions=xxx, instance_not_in_window) + request_union_op->set_output_request_row(false); + } + if (!w_ptr->union_tables().empty()) { for (auto iter = w_ptr->union_tables().cbegin(); iter != w_ptr->union_tables().cend(); iter++) { @@ -822,6 +826,7 @@ Status RequestModeTransformer::OptimizeSimpleProjectAsWindowProducer(PhysicalSim return Status::OK(); } +// From window (t1 last join t2 [ last join t3]...) // Optimize // RequestJoin(Request(left_table), DataSource(right_table)) // -> @@ -905,7 +910,8 @@ Status FixupWindowOverSimpleNLastJoin(const node::WindowPlanNode* w_ptr, const S size_t id = 0; CHECK_STATUS(window_depend_sc->ResolveColumnID(ref->GetDBName(), ref->GetRelationName(), - ref->GetColumnName(), &id)); + ref->GetColumnName(), &id), + "fail to resolve ", ref->GetExprString()); std::string name; CHECK_STATUS( left_join_sc->ResolveColumnNameByID(id, &name), @@ -930,7 +936,8 @@ Status FixupWindowOverSimpleNLastJoin(const node::WindowPlanNode* w_ptr, const S size_t id = 0; CHECK_STATUS(window_depend_sc->ResolveColumnID(ref->GetDBName(), ref->GetRelationName(), - ref->GetColumnName(), &id)); + ref->GetColumnName(), &id), + "fail to resolve ", ref->GetExprString()); std::string name; CHECK_STATUS(left_join_sc->ResolveColumnNameByID(id, &name), "Fail to handle window: window order expression should belong to left table of join"); @@ -1400,19 +1407,24 @@ Status BatchModeTransformer::CreatePhysicalProjectNode( } case kAggregation: { PhysicalAggregationNode* agg_op = nullptr; - CHECK_STATUS(CreateOp(&agg_op, depend, - column_projects, having_condition)); + CHECK_STATUS(CreateOp(&agg_op, depend, column_projects, having_condition)); *output = agg_op; break; } case kGroupAggregation: { - CHECK_TRUE(!node::ExprListNullOrEmpty(group_keys), kPlanError, - "Can not create group agg with non group keys"); + if (node::ExprListNullOrEmpty(group_keys)) { + PhysicalAggregationNode* agg_op = nullptr; + CHECK_STATUS(CreateOp(&agg_op, depend, column_projects, having_condition)); + *output = agg_op; + } else { + // CHECK_TRUE(!node::ExprListNullOrEmpty(group_keys), kPlanError, + // "Can not create group agg with non group keys"); - PhysicalGroupAggrerationNode* agg_op = nullptr; - CHECK_STATUS(CreateOp( - &agg_op, depend, column_projects, having_condition, group_keys)); - *output = agg_op; + PhysicalGroupAggrerationNode* agg_op = nullptr; + CHECK_STATUS(CreateOp(&agg_op, depend, column_projects, having_condition, + group_keys)); + *output = agg_op; + } break; } case kWindowAggregation: { @@ -1452,6 +1464,10 @@ base::Status BatchModeTransformer::ExtractGroupKeys(vm::PhysicalOpNode* depend, CHECK_STATUS(ExtractGroupKeys(depend->GetProducer(0), keys)) return base::Status::OK(); } + + if (depend->GetOpType() == kPhysicalOpRequestUnion) { + return base::Status::OK(); + } CHECK_TRUE(depend->GetOpType() == kPhysicalOpGroupBy, kPlanError, "Fail to extract group keys from op ", vm::PhysicalOpTypeName(depend->GetOpType())) *keys = dynamic_cast(depend)->group().keys_; @@ -1603,28 +1619,12 @@ bool BatchModeTransformer::isSourceFromTableOrPartition(PhysicalOpNode* in) { kPhysicalOpFilter == in->GetOpType()) { return isSourceFromTableOrPartition(in->GetProducer(0)); } else if (kPhysicalOpDataProvider == in->GetOpType()) { - return kProviderTypePartition == - dynamic_cast(in) - ->provider_type_ || - kProviderTypeTable == - dynamic_cast(in)->provider_type_; - } - return false; -} -bool BatchModeTransformer::isSourceFromTable(PhysicalOpNode* in) { - if (nullptr == in) { - DLOG(WARNING) << "Invalid physical node: null"; - return false; - } - if (kPhysicalOpSimpleProject == in->GetOpType() || - kPhysicalOpRename == in->GetOpType()) { - return isSourceFromTableOrPartition(in->GetProducer(0)); - } else if (kPhysicalOpDataProvider == in->GetOpType()) { - return kProviderTypeTable == - dynamic_cast(in)->provider_type_; + return kProviderTypePartition == dynamic_cast(in)->provider_type_ || + kProviderTypeTable == dynamic_cast(in)->provider_type_; } return false; } + Status BatchModeTransformer::ValidateTableProvider(PhysicalOpNode* in) { CHECK_TRUE(nullptr != in, kPlanError, "Invalid physical node: null"); CHECK_TRUE(isSourceFromTableOrPartition(in), kPlanError, @@ -1650,15 +1650,27 @@ Status BatchModeTransformer::ValidatePartitionDataProvider(PhysicalOpNode* in) { if (kPhysicalOpSimpleProject == in->GetOpType() || kPhysicalOpRename == in->GetOpType() || kPhysicalOpFilter == in->GetOpType()) { CHECK_STATUS(ValidatePartitionDataProvider(in->GetProducer(0))) + } else if (kPhysicalOpProject == in->GetOpType()) { + auto* prj = dynamic_cast(in); + CHECK_TRUE(prj->project_type_ == kAggregation, kPlanError, + "can't optimize project node: ", in->GetTreeString()); + CHECK_STATUS(ValidatePartitionDataProvider(in->GetProducer(0))); } else if (kPhysicalOpRequestJoin == in->GetOpType()) { CHECK_STATUS(ValidatePartitionDataProvider(in->GetProducer(0))); CHECK_STATUS(ValidatePartitionDataProvider(in->GetProducer(1))); + } else if (kPhysicalOpRequestUnion == in->GetOpType()) { + CHECK_STATUS(ValidatePartitionDataProvider(in->GetProducer(0))); + auto n = dynamic_cast(in); + if (!n->instance_not_in_window()) { + CHECK_STATUS(ValidatePartitionDataProvider(in->GetProducer(1))); + } + for (auto& window_union : n->window_unions().window_unions_) { + CHECK_STATUS(ValidateWindowIndexOptimization(window_union.second, window_union.first)); + } } else { - CHECK_TRUE( - kPhysicalOpDataProvider == in->GetOpType() && - kProviderTypePartition == - dynamic_cast(in)->provider_type_, - kPlanError, "Isn't partition provider:", in->GetTreeString()); + CHECK_TRUE(kPhysicalOpDataProvider == in->GetOpType() && + kProviderTypeTable != dynamic_cast(in)->provider_type_, + kPlanError, "Isn't partition provider:", in->GetTreeString()); } return Status::OK(); } @@ -1682,7 +1694,7 @@ Status BatchModeTransformer::ValidateJoinIndexOptimization( return Status::OK(); } else { CHECK_STATUS(ValidatePartitionDataProvider(right), - "Join node hasn't been optimized"); + "Join node hasn't been optimized: right=", right->GetTreeString()); } return Status::OK(); } @@ -1725,8 +1737,11 @@ Status BatchModeTransformer::ValidatePlanSupported(const PhysicalOpNode* in) { CHECK_STATUS(CheckPartitionColumn(join_op->join().right_key().keys(), join_op->schemas_ctx())); break; } - default: { + case node::kJoinTypeConcat: break; + default: { + FAIL_STATUS(common::kUnsupportSql, "unsupport join type ", + node::JoinTypeName(join_op->join_.join_type())) } } break; @@ -1739,8 +1754,11 @@ Status BatchModeTransformer::ValidatePlanSupported(const PhysicalOpNode* in) { CHECK_STATUS(CheckPartitionColumn(join_op->join().right_key().keys(), join_op->schemas_ctx())); break; } - default: { + case node::kJoinTypeConcat: break; + default: { + FAIL_STATUS(common::kUnsupportSql, "unsupport join type ", + node::JoinTypeName(join_op->join_.join_type())) } } break; @@ -1795,10 +1813,13 @@ Status BatchModeTransformer::ValidatePlanSupported(const PhysicalOpNode* in) { // - ValidateNotAggregationOverTable Status RequestModeTransformer::ValidatePlan(PhysicalOpNode* node) { CHECK_STATUS(BatchModeTransformer::ValidatePlan(node)) - PhysicalOpNode* primary_source = nullptr; + + // output is reqeust + CHECK_TRUE(node->GetOutputType() == kSchemaTypeRow, kPlanError, + "unsupport non-row output type for online-request mode"); // OnlineServing restriction: Expect to infer one and only one request table from given SQL - CHECK_STATUS(ValidateRequestTable(node, &primary_source), "Fail to validate physical plan") + CHECK_STATUS(ValidateRequestTable(node), "Fail to validate physical plan") // For Online serving, SQL queries should be designed extremely performance-sensitive to satisfy the real-time // requirements. Thus, we need to Validate if the SQL has been optimized well enough @@ -2244,13 +2265,29 @@ Status BatchModeTransformer::CheckWindow( const node::WindowPlanNode* w_ptr, const vm::SchemasContext* schemas_ctx) { CHECK_TRUE(w_ptr != nullptr, common::kPlanError, "NULL Window"); CHECK_TRUE(!node::ExprListNullOrEmpty(w_ptr->GetKeys()), common::kPlanError, - "Invalid Window: Do not support window on non-partition"); - CHECK_TRUE(nullptr != w_ptr->GetOrders() && - !node::ExprListNullOrEmpty(w_ptr->GetOrders()->order_expressions_), - common::kPlanError, - "Invalid Window: Do not support window on non-order"); + "un-implemented: WINDOW without PARTITION BY clause"); CHECK_STATUS(CheckHistoryWindowFrame(w_ptr)); + // without ORDER BY clause: + if (w_ptr->GetOrders() == nullptr || node::ExprListNullOrEmpty(w_ptr->GetOrders()->order_expressions())) { + // 1. forbidden: RANGE/ROWS_RANGE WINDOW WITH offset PRECEDING/FOLLOWING + if (w_ptr->frame_node()->frame_type() != node::FrameType::kFrameRows) { + auto* range = w_ptr->frame_node()->frame_range(); + if ((range->start() && range->start()->is_offset_bound()) || + (range->end() && range->end()->is_offset_bound())) { + CHECK_TRUE( + false, common::kPlanError, + "RANGE/ROWS_RANGE-type FRAME with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column") + } + } + + // 2. forbidden: WINDOW without ORDER BY + EXCLUDE CURRENT_TIME + if (w_ptr->exclude_current_time()) { + CHECK_TRUE(false, common::kPlanError, + "WINDOW with EXCLUDE CURRENT_TIME requires exactly one ORDER BY column"); + } + } + CHECK_STATUS(CheckTimeOrIntegerOrderColumn(w_ptr->GetOrders(), schemas_ctx)); return Status::OK(); @@ -2422,66 +2459,18 @@ Status RequestModeTransformer::TransformScanOp(const node::TablePlanNode* node, return BatchModeTransformer::TransformScanOp(node, output); } } -Status RequestModeTransformer::ValidateRequestTable( - PhysicalOpNode* in, PhysicalOpNode** request_table) { - CHECK_TRUE(in != NULL, kPlanError, "NULL Physical Node"); +Status RequestModeTransformer::ValidateRequestTable(PhysicalOpNode* in) { + auto req = internal::ExtractRequestNode(in); + CHECK_TRUE(req.ok(), kPlanError, req.status()); - switch (in->GetOpType()) { - case vm::kPhysicalOpDataProvider: { - CHECK_TRUE(kProviderTypeRequest == dynamic_cast(in)->provider_type_, kPlanError, - "Expect a request table but a ", - vm::DataProviderTypeName(dynamic_cast(in)->provider_type_), " ", - in->GetTreeString()) - *request_table = in; - return Status::OK(); - } - case vm::kPhysicalOpJoin: - case vm::kPhysicalOpUnion: - case vm::kPhysicalOpPostRequestUnion: - case vm::kPhysicalOpRequestUnion: - case vm::kPhysicalOpRequestAggUnion: - case vm::kPhysicalOpRequestJoin: { - vm::PhysicalOpNode* left_primary_source = nullptr; - CHECK_STATUS(ValidateRequestTable(in->GetProducer(0), &left_primary_source)) - CHECK_TRUE( - nullptr != left_primary_source, kPlanError, - "Fail to infer a request table") - // Case 1: - // binary_node - // + Left - PrimarySource - // + Right - TableProvider - if (isSourceFromTableOrPartition(in->GetProducer(1))) { - *request_table = left_primary_source; - return Status::OK(); - } + std::set> db_tables; + CHECK_STATUS(internal::GetDependentTables(in, &db_tables)); + CHECK_TRUE(req.value() != nullptr || db_tables.empty(), kPlanError, "no request node found"); - vm::PhysicalOpNode* right_primary_source = nullptr; - CHECK_STATUS(ValidateRequestTable(in->GetProducer(1), &right_primary_source)) - CHECK_TRUE( - nullptr != right_primary_source, kPlanError, - "Fail to infer a request table") - // Case 2: - // binary_node - // + Left <-- SamePrimarySource1 - // + Right <-- SamePrimarySource1 - CHECK_TRUE(left_primary_source->Equals(right_primary_source), kPlanError, - "left path and right path has different request table") - *request_table = left_primary_source; - return Status::OK(); - } - case vm::kPhysicalOpConstProject: { - break; - } - default: { - CHECK_TRUE(in->GetProducerCnt() == 1, kPlanError, - "Non-support Op ", PhysicalOpTypeName(in->GetOpType())) - CHECK_STATUS(ValidateRequestTable(in->GetProducer(0), request_table)); - return Status::OK(); - } - } return Status::OK(); } + // transform a single `ProjectListNode` of `ProjectPlanNode` Status RequestModeTransformer::TransformProjectOp( node::ProjectListNode* project_list, PhysicalOpNode* depend, diff --git a/hybridse/src/vm/transform.h b/hybridse/src/vm/transform.h index 22a7b74c5b9..45c4d9660e7 100644 --- a/hybridse/src/vm/transform.h +++ b/hybridse/src/vm/transform.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "absl/base/attributes.h" @@ -29,7 +28,6 @@ #include "base/fe_status.h" #include "base/graph.h" #include "llvm/Bitcode/BitcodeWriter.h" -#include "llvm/Support/raw_ostream.h" #include "node/node_manager.h" #include "node/plan_node.h" #include "node/sql_node.h" @@ -141,7 +139,6 @@ class BatchModeTransformer { Status GenRange(Range* sort, const SchemasContext* schemas_ctx); static bool isSourceFromTableOrPartition(PhysicalOpNode* in); - bool isSourceFromTable(PhysicalOpNode* in); std::string ExtractSchemaName(PhysicalOpNode* in); static Status ValidateIndexOptimization(PhysicalOpNode* physical_plan); @@ -319,7 +316,10 @@ class RequestModeTransformer : public BatchModeTransformer { absl::StatusOr ResolveCTERef(absl::string_view tb_name) override; - Status ValidateRequestTable(PhysicalOpNode* in, PhysicalOpNode** request_table); + // Valid the final optimized PhysicalOpNode is either: + // - has one and only one request table + // - do not has any physical table refered + Status ValidateRequestTable(PhysicalOpNode* in); private: // Optimize simple project node which is the producer of window project diff --git a/hybridse/tools/documentation/udf_doxygen/Makefile b/hybridse/tools/documentation/udf_doxygen/Makefile index b58a1c70aeb..d3e8a344ba2 100644 --- a/hybridse/tools/documentation/udf_doxygen/Makefile +++ b/hybridse/tools/documentation/udf_doxygen/Makefile @@ -27,8 +27,8 @@ doxygen2md: doxygen sync: doxygen2md @if [ -n "$(SYNC_DIR)" ]; then \ - cp -v "$(UDF_GEN_DIR)/Files/udfs_8h.md" "$(SYNC_DIR)/docs/en/reference/sql/functions_and_operators/Files/udfs_8h.md"; \ - cp -v "$(UDF_GEN_DIR)/Files/udfs_8h.md" "$(SYNC_DIR)/docs/zh/openmldb_sql/functions_and_operators/Files/udfs_8h.md"; \ + cp -v "$(UDF_GEN_DIR)/Files/udfs_8h.md" "$(SYNC_DIR)/docs/en/reference/sql/udfs_8h.md"; \ + cp -v "$(UDF_GEN_DIR)/Files/udfs_8h.md" "$(SYNC_DIR)/docs/zh/openmldb_sql/udfs_8h.md"; \ else \ echo "SKIP SYNC: DEFAULT Sync DIR not found"; \ fi diff --git a/hybridse/tools/documentation/udf_doxygen/README.md b/hybridse/tools/documentation/udf_doxygen/README.md index b911d067d84..33a74c5c84d 100644 --- a/hybridse/tools/documentation/udf_doxygen/README.md +++ b/hybridse/tools/documentation/udf_doxygen/README.md @@ -67,7 +67,7 @@ will output `udf_doxygen/udfgen`. ### 3. Put the document into proper position ```bash -cp udfgen/Files/udfs_8h.md ${project_root}/docs/zh/openmldb_sql/functions_and_operators/Files/udfs_8h.md +cp udfgen/Files/udfs_8h.md ${project_root}/docs/zh/openmldb_sql/udfs_8h.md ``` You may checkout changes manully and discard anything unnecessary like header. diff --git a/hybridse/tools/documentation/udf_doxygen/config.json b/hybridse/tools/documentation/udf_doxygen/config.json index 20d5297ab19..11e2451d1cf 100644 --- a/hybridse/tools/documentation/udf_doxygen/config.json +++ b/hybridse/tools/documentation/udf_doxygen/config.json @@ -1,5 +1,5 @@ { - "baseUrl": "/openmldb_sql/functions_and_operators/", + "baseUrl": "/openmldb_sql/", "indexInFolders": true, "linkSuffix": ".md", "linkLowercase": false, diff --git a/java/hybridse-native/pom.xml b/java/hybridse-native/pom.xml index e347b945b6c..629da3b0282 100644 --- a/java/hybridse-native/pom.xml +++ b/java/hybridse-native/pom.xml @@ -5,7 +5,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/java/hybridse-proto/pom.xml b/java/hybridse-proto/pom.xml index 83b28bdd95f..a78a0da61cf 100644 --- a/java/hybridse-proto/pom.xml +++ b/java/hybridse-proto/pom.xml @@ -4,7 +4,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/java/hybridse-sdk/pom.xml b/java/hybridse-sdk/pom.xml index e103c064280..ee37ccdf60f 100644 --- a/java/hybridse-sdk/pom.xml +++ b/java/hybridse-sdk/pom.xml @@ -6,7 +6,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/java/openmldb-batch/pom.xml b/java/openmldb-batch/pom.xml index a2a93ad4d3a..f2da581860d 100644 --- a/java/openmldb-batch/pom.xml +++ b/java/openmldb-batch/pom.xml @@ -7,7 +7,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT openmldb-batch diff --git a/java/openmldb-batchjob/pom.xml b/java/openmldb-batchjob/pom.xml index fddac8ffb82..408cb79e16d 100644 --- a/java/openmldb-batchjob/pom.xml +++ b/java/openmldb-batchjob/pom.xml @@ -7,7 +7,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT openmldb-batchjob diff --git a/java/openmldb-common/pom.xml b/java/openmldb-common/pom.xml index 8bd3e054cce..d42db9b7d54 100644 --- a/java/openmldb-common/pom.xml +++ b/java/openmldb-common/pom.xml @@ -5,7 +5,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT 4.0.0 openmldb-common diff --git a/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/codec/FlexibleRowBuilder.java b/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/codec/FlexibleRowBuilder.java index 5497237ce20..e9029fb7663 100644 --- a/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/codec/FlexibleRowBuilder.java +++ b/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/codec/FlexibleRowBuilder.java @@ -213,6 +213,9 @@ public boolean setNULL(int idx) { } Type.DataType type = metaData.getSchema().get(idx).getDataType(); if (type == Type.DataType.kVarchar || type == Type.DataType.kString) { + if (settedValue.at(idx)) { + return false; + } if (idx != metaData.getStrIdxList().get(curStrIdx)) { if (stringValueCache == null) { stringValueCache = new TreeMap<>(); diff --git a/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKClient.java b/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKClient.java index 256174c6573..85a1cf0422d 100644 --- a/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKClient.java +++ b/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKClient.java @@ -20,8 +20,11 @@ import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.api.ACLProvider; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; import java.util.concurrent.TimeUnit; import java.util.List; @@ -46,12 +49,26 @@ public CuratorFramework getClient() { public boolean connect() throws InterruptedException { log.info("ZKClient connect with config: {}", config); RetryPolicy retryPolicy = new ExponentialBackoffRetry(config.getBaseSleepTime(), config.getMaxRetries()); - CuratorFramework client = CuratorFrameworkFactory.builder() + CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder() .connectString(config.getCluster()) .sessionTimeoutMs(config.getSessionTimeout()) .connectionTimeoutMs(config.getConnectionTimeout()) - .retryPolicy(retryPolicy) - .build(); + .retryPolicy(retryPolicy); + if (!config.getCert().isEmpty()) { + builder.authorization("digest", config.getCert().getBytes()) + .aclProvider(new ACLProvider() { + @Override + public List getDefaultAcl() { + return ZooDefs.Ids.CREATOR_ALL_ACL; + } + + @Override + public List getAclForPath(String s) { + return ZooDefs.Ids.CREATOR_ALL_ACL; + } + }); + } + CuratorFramework client = builder.build(); client.start(); if (!client.blockUntilConnected(config.getMaxConnectWaitTime(), TimeUnit.MILLISECONDS)) { return false; diff --git a/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKConfig.java b/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKConfig.java index e215533a483..f0721a2f256 100644 --- a/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKConfig.java +++ b/java/openmldb-common/src/main/java/com/_4paradigm/openmldb/common/zk/ZKConfig.java @@ -32,5 +32,7 @@ public class ZKConfig { private int baseSleepTime = 1000; @Builder.Default private int maxConnectWaitTime = 30000; + @Builder.Default + private String cert = ""; } diff --git a/java/openmldb-jdbc/pom.xml b/java/openmldb-jdbc/pom.xml index 3978579b373..5cb7936b908 100644 --- a/java/openmldb-jdbc/pom.xml +++ b/java/openmldb-jdbc/pom.xml @@ -5,7 +5,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT ../pom.xml 4.0.0 @@ -61,6 +61,11 @@ snappy-java 1.1.7.2 + + com.github.ben-manes.caffeine + caffeine + 2.9.3 + diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/DirectResultSet.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/DirectResultSet.java index a3d9497f78d..772b0898a8e 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/DirectResultSet.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/DirectResultSet.java @@ -18,6 +18,7 @@ import com._4paradigm.openmldb.common.codec.RowView; import com._4paradigm.openmldb.sdk.Schema; +import com._4paradigm.openmldb.sdk.Common; import java.nio.ByteBuffer; import java.sql.*; @@ -151,7 +152,10 @@ public Timestamp getTimestamp(int i) throws SQLException { public String getNString(int i) throws SQLException { int realIdx = i - 1; try { - return rowView.getString(realIdx); + if (rowView.isNull(realIdx)) { + return null; + } + return rowView.getValue(realIdx, Common.sqlType2ProtoType(schema.getColumnType(realIdx))).toString(); } catch (Exception e) { throw new SQLException(e.getMessage()); } diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/SQLInsertMetaData.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/SQLInsertMetaData.java index e4ccd903146..144c889c5b4 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/SQLInsertMetaData.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/jdbc/SQLInsertMetaData.java @@ -18,10 +18,7 @@ import static com._4paradigm.openmldb.sdk.impl.Util.sqlTypeToString; -import com._4paradigm.openmldb.DataType; -import com._4paradigm.openmldb.Schema; -import com._4paradigm.openmldb.common.Pair; -import com._4paradigm.openmldb.sdk.Common; +import com._4paradigm.openmldb.sdk.Schema; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -29,42 +26,26 @@ public class SQLInsertMetaData implements ResultSetMetaData { - private final List schema; - private final Schema realSchema; - private final List> idx; + private final Schema schema; + private final List holeIdx; - public SQLInsertMetaData(List schema, - Schema realSchema, - List> idx) { + public SQLInsertMetaData(Schema schema, List holeIdx) { this.schema = schema; - this.realSchema = realSchema; - this.idx = idx; + this.holeIdx = holeIdx; } - private void checkSchemaNull() throws SQLException { - if (schema == null) { - throw new SQLException("schema is null"); - } - } - - private void checkIdx(int i) throws SQLException { - if (i <= 0) { + private void check(int i) throws SQLException { + if (i < 0) { throw new SQLException("index underflow"); } - if (i > schema.size()) { + if (i >= holeIdx.size()) { throw new SQLException("index overflow"); } } - public void check(int i) throws SQLException { - checkIdx(i); - checkSchemaNull(); - } - @Override public int getColumnCount() throws SQLException { - checkSchemaNull(); - return schema.size(); + return holeIdx.size(); } @Override @@ -93,9 +74,10 @@ public boolean isCurrency(int i) throws SQLException { @Override public int isNullable(int i) throws SQLException { - check(i); - Long index = idx.get(i - 1).getKey(); - if (realSchema.IsColumnNotNull(index)) { + int realIdx = i - 1; + check(realIdx); + boolean nullable = schema.isNullable(holeIdx.get(realIdx)); + if (!nullable) { return columnNoNulls; } else { return columnNullable; @@ -122,9 +104,9 @@ public String getColumnLabel(int i) throws SQLException { @Override public String getColumnName(int i) throws SQLException { - check(i); - Long index = idx.get(i - 1).getKey(); - return realSchema.GetColumnName(index); + int realIdx = i - 1; + check(realIdx); + return schema.getColumnName(holeIdx.get(realIdx)); } @Override @@ -159,9 +141,9 @@ public String getCatalogName(int i) throws SQLException { @Override public int getColumnType(int i) throws SQLException { - check(i); - Long index = idx.get(i - 1).getKey(); - return Common.type2SqlType(realSchema.GetColumnType(index)); + int realIdx = i - 1; + check(realIdx); + return schema.getColumnType(holeIdx.get(realIdx)); } @Override diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/Common.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/Common.java index 0c57cf26a5a..81f85482750 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/Common.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/Common.java @@ -171,8 +171,12 @@ public static ProcedureInfo convertProcedureInfo(com._4paradigm.openmldb.Procedu spInfo.setDbName(procedureInfo.GetDbName()); spInfo.setProName(procedureInfo.GetSpName()); spInfo.setSql(procedureInfo.GetSql()); - spInfo.setInputSchema(convertSchema(procedureInfo.GetInputSchema())); - spInfo.setOutputSchema(convertSchema(procedureInfo.GetOutputSchema())); + com._4paradigm.openmldb.Schema inputSchema = procedureInfo.GetInputSchema(); + spInfo.setInputSchema(convertSchema(inputSchema)); + inputSchema.delete(); + com._4paradigm.openmldb.Schema outputSchema = procedureInfo.GetOutputSchema(); + spInfo.setOutputSchema(convertSchema(outputSchema)); + outputSchema.delete(); spInfo.setMainTable(procedureInfo.GetMainTable()); spInfo.setInputTables(procedureInfo.GetTables()); spInfo.setInputDbs(procedureInfo.GetDbs()); diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/QueryFuture.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/QueryFuture.java index 12bbd1ab8d9..94a75df69d4 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/QueryFuture.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/QueryFuture.java @@ -74,6 +74,8 @@ public java.sql.ResultSet get() throws InterruptedException, ExecutionException if (resultSet != null) { resultSet.delete(); } + queryFuture.delete(); + queryFuture = null; logger.error("call procedure failed: {}", msg); throw new ExecutionException(new SqlException("call procedure failed: " + msg)); } diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SdkOption.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SdkOption.java index 830f6d1f097..83dd73cf657 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SdkOption.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SdkOption.java @@ -33,6 +33,7 @@ public class SdkOption { private String sparkConfPath = ""; private int zkLogLevel = 3; private String zkLogFile = ""; + private String zkCert = ""; // options for standalone mode private String host = ""; @@ -70,6 +71,7 @@ public SQLRouterOptions buildSQLRouterOptions() throws SqlException { copt.setSpark_conf_path(getSparkConfPath()); copt.setZk_log_level(getZkLogLevel()); copt.setZk_log_file(getZkLogFile()); + copt.setZk_cert(getZkCert()); // base buildBaseOptions(copt); diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SqlExecutor.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SqlExecutor.java index c89e53379bd..b55da67a430 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SqlExecutor.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/SqlExecutor.java @@ -48,10 +48,13 @@ public interface SqlExecutor { @Deprecated java.sql.ResultSet executeSQL(String db, String sql); + @Deprecated SQLInsertRow getInsertRow(String db, String sql); + @Deprecated SQLInsertRows getInsertRows(String db, String sql); + @Deprecated ResultSet executeSQLRequest(String db, String sql, SQLRequestRow row); Statement getStatement(); diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementCache.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementCache.java new file mode 100644 index 00000000000..9139217cc45 --- /dev/null +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementCache.java @@ -0,0 +1,75 @@ +package com._4paradigm.openmldb.sdk.impl; + +import com._4paradigm.openmldb.common.zk.ZKClient; +import com._4paradigm.openmldb.proto.NS; +import com._4paradigm.openmldb.sdk.SqlException; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.curator.framework.recipes.cache.NodeCache; +import org.apache.curator.framework.recipes.cache.NodeCacheListener; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class InsertPreparedStatementCache { + + private Cache, InsertPreparedStatementMeta> cache; + + private ZKClient zkClient; + private NodeCache nodeCache; + private String tablePath; + + public InsertPreparedStatementCache(int cacheSize, ZKClient zkClient) throws SqlException { + cache = Caffeine.newBuilder().maximumSize(cacheSize).build(); + this.zkClient = zkClient; + if (zkClient != null) { + tablePath = zkClient.getConfig().getNamespace() + "/table/db_table_data"; + nodeCache = new NodeCache(zkClient.getClient(), zkClient.getConfig().getNamespace() + "/table/notify"); + try { + nodeCache.start(); + nodeCache.getListenable().addListener(new NodeCacheListener() { + @Override + public void nodeChanged() throws Exception { + checkAndInvalid(); + } + }); + } catch (Exception e) { + throw new SqlException("NodeCache exception: " + e.getMessage()); + } + } + } + + public InsertPreparedStatementMeta get(String db, String sql) { + return cache.getIfPresent(new AbstractMap.SimpleImmutableEntry<>(db, sql)); + } + + public void put(String db, String sql, InsertPreparedStatementMeta meta) { + cache.put(new AbstractMap.SimpleImmutableEntry<>(db, sql), meta); + } + + public void checkAndInvalid() throws Exception { + if (!zkClient.checkExists(tablePath)) { + return; + } + List children = zkClient.getChildren(tablePath); + Map, InsertPreparedStatementMeta> view = cache.asMap(); + Map, Integer> tableMap = new HashMap<>(); + for (String path : children) { + byte[] bytes = zkClient.getClient().getData().forPath(tablePath + "/" + path); + NS.TableInfo tableInfo = NS.TableInfo.parseFrom(bytes); + tableMap.put(new AbstractMap.SimpleImmutableEntry<>(tableInfo.getDb(), tableInfo.getName()), tableInfo.getTid()); + } + Iterator, InsertPreparedStatementMeta>> iterator + = view.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry, InsertPreparedStatementMeta> entry = iterator.next(); + String db = entry.getKey().getKey(); + InsertPreparedStatementMeta meta = entry.getValue(); + String name = meta.getName(); + Integer tid = tableMap.get(new AbstractMap.SimpleImmutableEntry<>(db, name)); + if (tid != null && tid != meta.getTid()) { + cache.invalidate(entry.getKey()); + } + } + } +} diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementImpl.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementImpl.java index 1eeb10865b5..6acefe8acff 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementImpl.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementImpl.java @@ -18,99 +18,46 @@ import com._4paradigm.openmldb.*; -import com._4paradigm.openmldb.common.Pair; +import com._4paradigm.openmldb.common.codec.CodecUtil; +import com._4paradigm.openmldb.common.codec.FlexibleRowBuilder; +import com._4paradigm.openmldb.jdbc.PreparedStatement; import com._4paradigm.openmldb.jdbc.SQLInsertMetaData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.sql.*; import java.sql.Date; import java.sql.ResultSet; import java.util.*; -import java.util.stream.Collectors; -public class InsertPreparedStatementImpl implements PreparedStatement { - public static final Charset CHARSET = StandardCharsets.UTF_8; +public class InsertPreparedStatementImpl extends PreparedStatement { private static final Logger logger = LoggerFactory.getLogger(InsertPreparedStatementImpl.class); - private final String db; - private final String sql; - private final SQLRouter router; - - // need manual deletion - private final List currentRows = new ArrayList<>(); - private Schema currentSchema; - - private final List currentDatas; - private final List currentDatasType; - private final List hasSet; - // stmt insert idx -> real table schema idx - private final List> schemaIdxes; - // used by building row - private final List> sortedIdxes; - - private boolean closed = false; - private boolean closeOnComplete = false; - private Integer stringsLen = 0; - - public InsertPreparedStatementImpl(String db, String sql, SQLRouter router) throws SQLException { - this.db = db; - this.sql = sql; - this.router = router; + private SQLRouter router; + private FlexibleRowBuilder rowBuilder; + private InsertPreparedStatementMeta cache; - SQLInsertRow tempRow = getSQLInsertRow(); - this.currentSchema = tempRow.GetSchema(); - VectorUint32 idxes = tempRow.GetHoleIdx(); - - // In stmt order, if no columns in stmt, in schema order - // We'll sort it to schema order later, so needs the map - schemaIdxes = new ArrayList<>(idxes.size()); - // CurrentData and Type order is consistent with insert stmt. We'll do appending in schema order when build - // row. - currentDatas = new ArrayList<>(idxes.size()); - currentDatasType = new ArrayList<>(idxes.size()); - hasSet = new ArrayList<>(idxes.size()); - - for (int i = 0; i < idxes.size(); i++) { - Long realIdx = idxes.get(i); - schemaIdxes.add(new Pair<>(realIdx, i)); - DataType type = currentSchema.GetColumnType(realIdx); - currentDatasType.add(type); - currentDatas.add(null); - hasSet.add(false); - logger.debug("add col {}, {}", currentSchema.GetColumnName(realIdx), type); - } - // SQLInsertRow::AppendXXX order is the schema order(skip the no-hole columns) - sortedIdxes = schemaIdxes.stream().sorted(Comparator.comparing(Pair::getKey)) - .collect(Collectors.toList()); - } + private Set indexCol; + private Map> indexMap; + private Map indexValue; + private Map defaultIndexValue; + private List> batchValues; - private SQLInsertRow getSQLInsertRow() throws SQLException { - Status status = new Status(); - SQLInsertRow row = router.GetInsertRow(db, sql, status); - if (status.getCode() != 0) { - String msg = status.ToString(); - status.delete(); - if (row != null) { - row.delete(); - } - throw new SQLException("getSQLInsertRow failed, " + msg); - } - status.delete(); - return row; + public InsertPreparedStatementImpl(InsertPreparedStatementMeta cache, SQLRouter router) throws SQLException { + this.router = router; + rowBuilder = new FlexibleRowBuilder(cache.getCodecMeta()); + this.cache = cache; + indexCol = cache.getIndexPos(); + indexMap = cache.getIndexMap(); + indexValue = new HashMap<>(); + defaultIndexValue = cache.getDefaultIndexValue(); + batchValues = new ArrayList<>(); } - private void clearSQLInsertRowList() { - for (SQLInsertRow row : currentRows) { - row.delete(); - } - currentRows.clear(); + private int getSchemaIdx(int idx) throws SQLException { + return cache.getSchemaIdx(idx - 1); } @Override @@ -125,246 +72,237 @@ public int executeUpdate() throws SQLException { throw new SQLException("current do not support this method"); } - private void checkIdx(int i) throws SQLException { - if (closed) { - throw new SQLException("prepared statement closed"); - } - if (i <= 0) { - throw new SQLException("error sqe number"); - } - if (i > schemaIdxes.size()) { - throw new SQLException("out of data range"); - } - } - - private void checkType(int i, DataType type) throws SQLException { - if (currentDatasType.get(i - 1) != type) { - throw new SQLException("data type not match, expect " + currentDatasType.get(i - 1) + ", actual " + type); - } - } - - private void setNull(int i) throws SQLException { - checkIdx(i); - boolean notAllowNull = checkNotAllowNull(i); - if (notAllowNull) { + private boolean setNull(int i) throws SQLException { + if (!cache.getSchema().isNullable(i)) { throw new SQLException("this column not allow null"); } - hasSet.set(i - 1, true); - currentDatas.set(i - 1, null); + return rowBuilder.setNULL(i); } @Override public void setNull(int i, int i1) throws SQLException { - setNull(i); + int realIdx = getSchemaIdx(i); + if (!setNull(realIdx)) { + throw new SQLException("set null failed. pos is " + i); + } + if (indexCol.contains(realIdx)) { + indexValue.put(realIdx, InsertPreparedStatementMeta.NONETOKEN); + } } @Override public void setBoolean(int i, boolean b) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeBool); - hasSet.set(i - 1, true); - currentDatas.set(i - 1, b); - } - - @Override - @Deprecated - public void setByte(int i, byte b) throws SQLException { - throw new SQLException("current do not support this method"); + int realIdx = getSchemaIdx(i); + if (!rowBuilder.setBool(realIdx, b)) { + throw new SQLException("set bool failed. pos is " + i); + } + if (indexCol.contains(realIdx)) { + indexValue.put(realIdx, String.valueOf(b)); + } } @Override public void setShort(int i, short i1) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeInt16); - hasSet.set(i - 1, true); - currentDatas.set(i - 1, i1); + int realIdx = getSchemaIdx(i); + if (!rowBuilder.setSmallInt(realIdx, i1)) { + throw new SQLException("set short failed. pos is " + i); + } + if (indexCol.contains(realIdx)) { + indexValue.put(realIdx, String.valueOf(i1)); + } } @Override public void setInt(int i, int i1) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeInt32); - hasSet.set(i - 1, true); - currentDatas.set(i - 1, i1); - + int realIdx = getSchemaIdx(i); + if (!rowBuilder.setInt(realIdx, i1)) { + throw new SQLException("set int failed. pos is " + i); + } + if (indexCol.contains(realIdx)) { + indexValue.put(realIdx, String.valueOf(i1)); + } } @Override public void setLong(int i, long l) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeInt64); - hasSet.set(i - 1, true); - currentDatas.set(i - 1, l); + int realIdx = getSchemaIdx(i); + if (!rowBuilder.setBigInt(realIdx, l)) { + throw new SQLException("set long failed. pos is " + i); + } + if (indexCol.contains(realIdx)) { + indexValue.put(realIdx, String.valueOf(l)); + } } @Override public void setFloat(int i, float v) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeFloat); - hasSet.set(i - 1, true); - currentDatas.set(i - 1, v); + if (!rowBuilder.setFloat(getSchemaIdx(i), v)) { + throw new SQLException("set float failed. pos is " + i); + } } @Override public void setDouble(int i, double v) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeDouble); - hasSet.set(i - 1, true); - currentDatas.set(i - 1, v); - } - - @Override - @Deprecated - public void setBigDecimal(int i, BigDecimal bigDecimal) throws SQLException { - throw new SQLException("current do not support this type"); - } - - private boolean checkNotAllowNull(int i) { - Long idx = this.schemaIdxes.get(i - 1).getKey(); - return this.currentSchema.IsColumnNotNull(idx); + if (!rowBuilder.setDouble(getSchemaIdx(i), v)) { + throw new SQLException("set double failed. pos is " + i); + } } @Override public void setString(int i, String s) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeString); + int realIdx = getSchemaIdx(i); if (s == null) { - setNull(i); + setNull(realIdx); + if (indexCol.contains(realIdx)) { + indexValue.put(realIdx, InsertPreparedStatementMeta.NONETOKEN); + } return; } - byte[] bytes = s.getBytes(CHARSET); - // if this index already set, should first reduce length of bytes last time - if (hasSet.get(i - 1)) { - stringsLen -= ((byte[]) currentDatas.get(i - 1)).length; + if (!rowBuilder.setString(getSchemaIdx(i), s)) { + throw new SQLException("set string failed. pos is " + i); + } + if (indexCol.contains(realIdx)) { + if (s.isEmpty()) { + indexValue.put(realIdx, InsertPreparedStatementMeta.EMPTY_STRING); + } else { + indexValue.put(realIdx, s); + } } - stringsLen += bytes.length; - hasSet.set(i - 1, true); - currentDatas.set(i - 1, bytes); - } - - @Override - @Deprecated - public void setBytes(int i, byte[] bytes) throws SQLException { - throw new SQLException("current do not support this type"); } @Override public void setDate(int i, Date date) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeDate); + int realIdx = getSchemaIdx(i); + if (indexCol.contains(realIdx)) { + if (date != null) { + indexValue.put(realIdx, String.valueOf(CodecUtil.dateToDateInt(date))); + } else { + indexValue.put(realIdx, InsertPreparedStatementMeta.NONETOKEN); + } + } if (date == null) { - setNull(i); + if (!setNull(realIdx)) { + throw new SQLException("set date failed. pos is " + i); + } return; } - hasSet.set(i - 1, true); - currentDatas.set(i - 1, date); + if (!rowBuilder.setDate(realIdx, date)) { + throw new SQLException("set date failed. pos is " + i); + } } - @Override - @Deprecated - public void setTime(int i, Time time) throws SQLException { - throw new SQLException("current do not support this type"); - } @Override public void setTimestamp(int i, Timestamp timestamp) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeTimestamp); + int realIdx = getSchemaIdx(i); + if (indexCol.contains(realIdx)) { + if (timestamp != null) { + indexValue.put(realIdx, String.valueOf(timestamp.getTime())); + } else { + indexValue.put(realIdx, InsertPreparedStatementMeta.NONETOKEN); + } + } if (timestamp == null) { - setNull(i); + if (!setNull(realIdx)) { + throw new SQLException("set timestamp failed. pos is " + i); + } return; } - hasSet.set(i - 1, true); - long ts = timestamp.getTime(); - currentDatas.set(i - 1, ts); - } - - @Override - @Deprecated - public void setAsciiStream(int i, InputStream inputStream, int i1) throws SQLException { - throw new SQLException("current do not support this type"); - } - - @Override - @Deprecated - public void setUnicodeStream(int i, InputStream inputStream, int i1) throws SQLException { - throw new SQLException("current do not support this type"); - } - - @Override - @Deprecated - public void setBinaryStream(int i, InputStream inputStream, int i1) throws SQLException { - throw new SQLException("current do not support this type"); - } - - @Override - public void clearParameters() throws SQLException { - for (int i = 0; i < hasSet.size(); i++) { - hasSet.set(i, false); - currentDatas.set(i, null); + if (!rowBuilder.setTimestamp(realIdx, timestamp)) { + throw new SQLException("set timestamp failed. pos is " + i); } - stringsLen = 0; } @Override - @Deprecated - public void setObject(int i, Object o, int i1) throws SQLException { - throw new SQLException("current do not support this method"); - } - - private void buildRow() throws SQLException { - SQLInsertRow currentRow = getSQLInsertRow(); - boolean ok = currentRow.Init(stringsLen); - if (!ok) { - throw new SQLException("init row failed"); + public void clearParameters() throws SQLException { + rowBuilder.clear(); + indexValue.clear(); + } + + private ByteBuffer buildDimension() throws SQLException { + int totalLen = 0; + Map lenMap = new HashMap<>(); + for (Map.Entry> entry : indexMap.entrySet()) { + totalLen += 4; // encode the size of idx(int) + totalLen += 4; // encode the value size + int curLen = entry.getValue().size() - 1; + for (Integer pos : entry.getValue()) { + if (indexValue.containsKey(pos)) { + curLen += indexValue.get(pos).getBytes(CodecUtil.CHARSET).length; + } else if (defaultIndexValue.containsKey(pos)) { + curLen += defaultIndexValue.get(pos).getBytes(CodecUtil.CHARSET).length; + } else { + throw new SQLException("cannot get index value. pos is " + pos); + } + } + totalLen += curLen; + lenMap.put(entry.getKey(), curLen); } - - for (Pair sortedIdx : sortedIdxes) { - Integer currentDataIdx = sortedIdx.getValue(); - Object data = currentDatas.get(currentDataIdx); - if (data == null) { - ok = currentRow.AppendNULL(); - } else { - DataType curType = currentDatasType.get(currentDataIdx); - if (DataType.kTypeBool.equals(curType)) { - ok = currentRow.AppendBool((boolean) data); - } else if (DataType.kTypeDate.equals(curType)) { - Date date = (Date) data; - ok = currentRow.AppendDate(date.getYear() + 1900, date.getMonth() + 1, date.getDate()); - } else if (DataType.kTypeDouble.equals(curType)) { - ok = currentRow.AppendDouble((double) data); - } else if (DataType.kTypeFloat.equals(curType)) { - ok = currentRow.AppendFloat((float) data); - } else if (DataType.kTypeInt16.equals(curType)) { - ok = currentRow.AppendInt16((short) data); - } else if (DataType.kTypeInt32.equals(curType)) { - ok = currentRow.AppendInt32((int) data); - } else if (DataType.kTypeInt64.equals(curType)) { - ok = currentRow.AppendInt64((long) data); - } else if (DataType.kTypeString.equals(curType)) { - byte[] bdata = (byte[]) data; - ok = currentRow.AppendString(bdata, bdata.length); - } else if (DataType.kTypeTimestamp.equals(curType)) { - ok = currentRow.AppendTimestamp((long) data); + ByteBuffer dimensionValue = ByteBuffer.allocate(totalLen).order(ByteOrder.LITTLE_ENDIAN); + for (Map.Entry> entry : indexMap.entrySet()) { + Integer indexPos = entry.getKey(); + dimensionValue.putInt(indexPos); + dimensionValue.putInt(lenMap.get(indexPos)); + for (int i = 0; i < entry.getValue().size(); i++) { + int pos = entry.getValue().get(i); + if (i > 0) { + dimensionValue.put((byte)'|'); + } + if (indexValue.containsKey(pos)) { + dimensionValue.put(indexValue.get(pos).getBytes(CodecUtil.CHARSET)); } else { - throw new SQLException("unknown data type"); + dimensionValue.put(defaultIndexValue.get(pos).getBytes(CodecUtil.CHARSET)); } } - if (!ok) { - throw new SQLException("append failed on currentDataIdx: " + currentDataIdx + ", curType: " + currentDatasType.get(currentDataIdx) + ", current data: " + data); + } + return dimensionValue; + } + + private ByteBuffer buildRow() throws SQLException { + Map defaultValue = cache.getDefaultValue(); + if (!defaultValue.isEmpty()) { + for (Map.Entry entry : defaultValue.entrySet()) { + int idx = entry.getKey(); + Object val = entry.getValue(); + if (val == null) { + rowBuilder.setNULL(idx); + continue; + } + switch (cache.getSchema().getColumnType(idx)) { + case Types.BOOLEAN: + rowBuilder.setBool(idx, (boolean)val); + break; + case Types.SMALLINT: + rowBuilder.setSmallInt(idx, (short)val); + break; + case Types.INTEGER: + rowBuilder.setInt(idx, (int)val); + break; + case Types.BIGINT: + rowBuilder.setBigInt(idx, (long)val); + break; + case Types.FLOAT: + rowBuilder.setFloat(idx, (float)val); + break; + case Types.DOUBLE: + rowBuilder.setDouble(idx, (double)val); + break; + case Types.DATE: + rowBuilder.setDate(idx, (Date)val); + break; + case Types.TIMESTAMP: + rowBuilder.setTimestamp(idx, (Timestamp)val); + break; + case Types.VARCHAR: + rowBuilder.setString(idx, (String)val); + break; + } } } - if (!currentRow.Build()) { - throw new SQLException("build insert row failed(str size init != actual)"); + if (!rowBuilder.build()) { + throw new SQLException("encode row failed"); } - currentRows.add(currentRow); - clearParameters(); - } - - @Override - @Deprecated - public void setObject(int i, Object o) throws SQLException { - throw new SQLException("current do not support this method"); + return rowBuilder.getValue(); } @Override @@ -372,17 +310,19 @@ public boolean execute() throws SQLException { if (closed) { throw new SQLException("InsertPreparedStatement closed"); } - // buildRow will add a new row to currentRows - if (!currentRows.isEmpty()) { + if (!batchValues.isEmpty()) { throw new SQLException("please use executeBatch"); } - buildRow(); + ByteBuffer dimensions = buildDimension(); + ByteBuffer value = buildRow(); Status status = new Status(); // actually only one row - boolean ok = router.ExecuteInsert(db, sql, currentRows.get(0), status); + boolean ok = router.ExecuteInsert(cache.getDatabase(), cache.getName(), + cache.getTid(), cache.getPartitionNum(), + dimensions.array(), dimensions.capacity(), value.array(), value.capacity(), status); // cleanup rows even if insert failed // we can't execute() again without set new row, so we must clean up here - clearSQLInsertRowList(); + clearParameters(); if (!ok) { logger.error("execute insert failed: {}", status.ToString()); status.delete(); @@ -401,220 +341,24 @@ public void addBatch() throws SQLException { if (closed) { throw new SQLException("InsertPreparedStatement closed"); } - // build the current row and cleanup the cache of current row - // so that the cache is ready for new row - buildRow(); - } - - @Override - @Deprecated - public void setCharacterStream(int i, Reader reader, int i1) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setRef(int i, Ref ref) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setBlob(int i, Blob blob) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setClob(int i, Clob clob) throws SQLException { - throw new SQLException("current do not support this method"); + batchValues.add(new AbstractMap.SimpleImmutableEntry<>(buildDimension(), buildRow())); + clearParameters(); } - @Override - @Deprecated - public void setArray(int i, Array array) throws SQLException { - throw new SQLException("current do not support this method"); - } @Override public ResultSetMetaData getMetaData() throws SQLException { - return new SQLInsertMetaData(this.currentDatasType, this.currentSchema, this.schemaIdxes); + return new SQLInsertMetaData(cache.getSchema(), cache.getHoleIdx()); } @Override public void setDate(int i, Date date, Calendar calendar) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeDate); - if (date == null) { - setNull(i); - return; - } - hasSet.set(i - 1, true); - currentDatas.set(i - 1, date); - } - - @Override - @Deprecated - public void setTime(int i, Time time, Calendar calendar) throws SQLException { - throw new SQLException("current do not support this method"); + setDate(i, date); } @Override public void setTimestamp(int i, Timestamp timestamp, Calendar calendar) throws SQLException { - checkIdx(i); - checkType(i, DataType.kTypeTimestamp); - if (timestamp == null) { - setNull(i); - return; - } - hasSet.set(i - 1, true); - long ts = timestamp.getTime(); - currentDatas.set(i - 1, ts); - } - - @Override - @Deprecated - public void setNull(int i, int i1, String s) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setURL(int i, URL url) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public ParameterMetaData getParameterMetaData() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setRowId(int i, RowId rowId) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setNString(int i, String s) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setNCharacterStream(int i, Reader reader, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setNClob(int i, NClob nClob) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setClob(int i, Reader reader, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setBlob(int i, InputStream inputStream, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setNClob(int i, Reader reader, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setSQLXML(int i, SQLXML sqlxml) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setObject(int i, Object o, int i1, int i2) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setAsciiStream(int i, InputStream inputStream, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setBinaryStream(int i, InputStream inputStream, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setCharacterStream(int i, Reader reader, long l) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setAsciiStream(int i, InputStream inputStream) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setBinaryStream(int i, InputStream inputStream) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setCharacterStream(int i, Reader reader) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setNCharacterStream(int i, Reader reader) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setClob(int i, Reader reader) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setBlob(int i, InputStream inputStream) throws SQLException { - - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setNClob(int i, Reader reader) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public ResultSet executeQuery(String s) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int executeUpdate(String s) throws SQLException { - throw new SQLException("current do not support this method"); + setTimestamp(i, timestamp); } @Override @@ -622,158 +366,22 @@ public void close() throws SQLException { if (closed) { return; } - clearSQLInsertRowList(); - if (currentSchema != null) { - currentSchema.delete(); - currentSchema = null; - } closed = true; } - @Override - @Deprecated - public int getMaxFieldSize() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setMaxFieldSize(int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int getMaxRows() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setMaxRows(int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setEscapeProcessing(boolean b) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int getQueryTimeout() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setQueryTimeout(int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void cancel() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public SQLWarning getWarnings() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void clearWarnings() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setCursorName(String s) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean execute(String s) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public ResultSet getResultSet() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int getUpdateCount() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean getMoreResults() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public void setFetchDirection(int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Deprecated - @Override - public int getFetchDirection() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - public void setFetchSize(int i) throws SQLException { - } - - @Override - @Deprecated - public int getFetchSize() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int getResultSetConcurrency() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int getResultSetType() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - public void addBatch(String s) throws SQLException { - throw new SQLException("cannot take arguments in PreparedStatement"); - } - - @Override - @Deprecated - public void clearBatch() throws SQLException { - throw new SQLException("current do not support this method"); - } - @Override public int[] executeBatch() throws SQLException { if (closed) { throw new SQLException("InsertPreparedStatement closed"); } - int[] result = new int[currentRows.size()]; + int[] result = new int[batchValues.size()]; Status status = new Status(); - for (int i = 0; i < currentRows.size(); i++) { - boolean ok = router.ExecuteInsert(db, sql, currentRows.get(i), status); + for (int i = 0; i < batchValues.size(); i++) { + AbstractMap.SimpleImmutableEntry pair = batchValues.get(i); + boolean ok = router.ExecuteInsert(cache.getDatabase(), cache.getName(), + cache.getTid(), cache.getPartitionNum(), + pair.getKey().array(), pair.getKey().capacity(), + pair.getValue().array(), pair.getValue().capacity(), status); if (!ok) { // TODO(hw): may lost log, e.g. openmldb-batch online import in yarn mode? logger.warn(status.ToString()); @@ -781,106 +389,8 @@ public int[] executeBatch() throws SQLException { result[i] = ok ? 0 : -1; } status.delete(); - clearSQLInsertRowList(); + clearParameters(); + batchValues.clear(); return result; } - - @Override - @Deprecated - public Connection getConnection() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean getMoreResults(int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public ResultSet getGeneratedKeys() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int executeUpdate(String s, int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int executeUpdate(String s, int[] ints) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int executeUpdate(String s, String[] strings) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean execute(String s, int i) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean execute(String s, int[] ints) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean execute(String s, String[] strings) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public int getResultSetHoldability() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - public boolean isClosed() throws SQLException { - return closed; - } - - @Override - @Deprecated - public void setPoolable(boolean b) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean isPoolable() throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - public void closeOnCompletion() throws SQLException { - this.closeOnComplete = true; - } - - @Override - public boolean isCloseOnCompletion() throws SQLException { - return this.closeOnComplete; - } - - @Override - @Deprecated - public T unwrap(Class aClass) throws SQLException { - throw new SQLException("current do not support this method"); - } - - @Override - @Deprecated - public boolean isWrapperFor(Class aClass) throws SQLException { - throw new SQLException("current do not support this method"); - } } diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementMeta.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementMeta.java new file mode 100644 index 00000000000..448438e9d31 --- /dev/null +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/InsertPreparedStatementMeta.java @@ -0,0 +1,218 @@ +package com._4paradigm.openmldb.sdk.impl; + +import com._4paradigm.openmldb.SQLInsertRow; +import com._4paradigm.openmldb.DefaultValueContainer; +import com._4paradigm.openmldb.VectorUint32; +import com._4paradigm.openmldb.common.codec.CodecMetaData; +import com._4paradigm.openmldb.common.codec.CodecUtil; +import com._4paradigm.openmldb.proto.NS; +import com._4paradigm.openmldb.sdk.Common; +import com._4paradigm.openmldb.sdk.Schema; + +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.*; + +public class InsertPreparedStatementMeta { + + public static String NONETOKEN = "!N@U#L$L%"; + public static String EMPTY_STRING = "!@#$%"; + + private String sql; + private String db; + private String name; + private int tid; + private int partitionNum; + private Schema schema; + private CodecMetaData codecMetaData; + private Map defaultValue = new HashMap<>(); + private List holeIdx = new ArrayList<>(); + private Set indexPos = new HashSet<>(); + private Map> indexMap = new HashMap<>(); + private Map defaultIndexValue = new HashMap<>(); + + public InsertPreparedStatementMeta(String sql, NS.TableInfo tableInfo, SQLInsertRow insertRow) { + this.sql = sql; + try { + schema = Common.convertSchema(tableInfo.getColumnDescList()); + codecMetaData = new CodecMetaData(tableInfo.getColumnDescList(), false); + } catch (Exception e) { + e.printStackTrace(); + } + db = tableInfo.getDb(); + name = tableInfo.getName(); + tid = tableInfo.getTid(); + partitionNum = tableInfo.getTablePartitionCount(); + buildIndex(tableInfo); + DefaultValueContainer value = insertRow.GetDefaultValue(); + buildDefaultValue(value); + value.delete(); + VectorUint32 idxArray = insertRow.GetHoleIdx(); + buildHoleIdx(idxArray); + idxArray.delete(); + } + + private void buildIndex(NS.TableInfo tableInfo) { + Map nameIdxMap = new HashMap<>(); + for (int i = 0; i < schema.size(); i++) { + nameIdxMap.put(schema.getColumnName(i), i); + } + for (int i = 0; i < tableInfo.getColumnKeyList().size(); i++) { + com._4paradigm.openmldb.proto.Common.ColumnKey columnKey = tableInfo.getColumnKeyList().get(i); + List colList = new ArrayList<>(columnKey.getColNameCount()); + for (String name : columnKey.getColNameList()) { + colList.add(nameIdxMap.get(name)); + indexPos.add(nameIdxMap.get(name)); + } + indexMap.put(i, colList); + } + } + + private void buildHoleIdx(VectorUint32 idxArray) { + int size = idxArray.size(); + for (int i = 0; i < size; i++) { + holeIdx.add(idxArray.get(i).intValue()); + } + } + + private void buildDefaultValue(DefaultValueContainer valueContainer) { + VectorUint32 defaultPos = valueContainer.GetAllPosition(); + int size = defaultPos.size(); + for (int i = 0; i < size; i++) { + int schemaIdx = defaultPos.get(i).intValue(); + boolean isIndexVal = indexPos.contains(schemaIdx); + if (valueContainer.IsNull(schemaIdx)) { + defaultValue.put(schemaIdx, null); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, NONETOKEN); + } + } else { + switch (schema.getColumnType(schemaIdx)) { + case Types.BOOLEAN: { + boolean val = valueContainer.GetBool(schemaIdx); + defaultValue.put(schemaIdx, val); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, String.valueOf(val)); + } + break; + } + case Types.SMALLINT: { + short val = valueContainer.GetSmallInt(schemaIdx); + defaultValue.put(schemaIdx, val); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, String.valueOf(val)); + } + break; + } + case Types.INTEGER: { + int val = valueContainer.GetInt(schemaIdx); + defaultValue.put(schemaIdx, val); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, String.valueOf(val)); + } + break; + } + case Types.BIGINT: { + long val = valueContainer.GetBigInt(schemaIdx); + defaultValue.put(schemaIdx, val); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, String.valueOf(val)); + } + break; + } + case Types.FLOAT: + defaultValue.put(schemaIdx, valueContainer.GetFloat(schemaIdx)); + break; + case Types.DOUBLE: + defaultValue.put(schemaIdx, valueContainer.GetDouble(schemaIdx)); + break; + case Types.DATE: { + int val = valueContainer.GetDate(schemaIdx); + defaultValue.put(schemaIdx, CodecUtil.dateIntToDate(val)); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, String.valueOf(val)); + } + break; + } + case Types.TIMESTAMP: { + long val = valueContainer.GetTimeStamp(schemaIdx); + defaultValue.put(schemaIdx, new Timestamp(val)); + if (isIndexVal) { + defaultIndexValue.put(schemaIdx, String.valueOf(val)); + } + break; + } + case Types.VARCHAR: { + String val = valueContainer.GetString(schemaIdx); + defaultValue.put(schemaIdx, val); + if (isIndexVal) { + if (val.isEmpty()) { + defaultIndexValue.put(schemaIdx, EMPTY_STRING); + } else { + defaultIndexValue.put(schemaIdx, val); + } + } + break; + } + } + } + } + defaultPos.delete(); + } + + public Schema getSchema() { + return schema; + } + + public String getDatabase() { + return db; + } + + public String getName() { + return name; + } + + public int getTid() { + return tid; + } + + public int getPartitionNum() { + return partitionNum; + } + + public CodecMetaData getCodecMeta() { + return codecMetaData; + } + + public Map getDefaultValue() { + return defaultValue; + } + + public String getSql() { + return sql; + } + + public int getSchemaIdx(int idx) throws SQLException { + if (idx >= holeIdx.size()) { + throw new SQLException("out of data range"); + } + return holeIdx.get(idx); + } + + List getHoleIdx() { + return holeIdx; + } + + Set getIndexPos() { + return indexPos; + } + + Map> getIndexMap() { + return indexMap; + } + + Map getDefaultIndexValue() { + return defaultIndexValue; + } +} diff --git a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/SqlClusterExecutor.java b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/SqlClusterExecutor.java index 7d32ac092af..9505cd6aba9 100644 --- a/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/SqlClusterExecutor.java +++ b/java/openmldb-jdbc/src/main/java/com/_4paradigm/openmldb/sdk/impl/SqlClusterExecutor.java @@ -52,6 +52,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -62,6 +63,7 @@ public class SqlClusterExecutor implements SqlExecutor { private SQLRouter sqlRouter; private DeploymentManager deploymentManager; private ZKClient zkClient; + private InsertPreparedStatementCache insertCache; public SqlClusterExecutor(SdkOption option, String libraryPath) throws SqlException { initJavaSdkLibrary(libraryPath); @@ -91,6 +93,7 @@ public SqlClusterExecutor(SdkOption option, String libraryPath) throws SqlExcept throw new SqlException("fail to create sql executor"); } deploymentManager = new DeploymentManager(zkClient); + insertCache = new InsertPreparedStatementCache(option.getMaxSqlCacheSize(), zkClient); } public SqlClusterExecutor(SdkOption option) throws SqlException { @@ -183,7 +186,26 @@ public Statement getStatement() { @Override public PreparedStatement getInsertPreparedStmt(String db, String sql) throws SQLException { - return new InsertPreparedStatementImpl(db, sql, this.sqlRouter); + InsertPreparedStatementMeta meta = insertCache.get(db, sql); + if (meta == null) { + Status status = new Status(); + SQLInsertRow row = sqlRouter.GetInsertRow(db, sql, status); + if (!status.IsOK()) { + String msg = status.ToString(); + status.delete(); + if (row != null) { + row.delete(); + } + throw new SQLException("getSQLInsertRow failed, " + msg); + } + status.delete(); + String name = row.GetTableInfo().getName(); + NS.TableInfo tableInfo = getTableInfo(db, name); + meta = new InsertPreparedStatementMeta(sql, tableInfo, row); + row.delete(); + insertCache.put(db, sql, meta); + } + return new InsertPreparedStatementImpl(meta, this.sqlRouter); } @Override diff --git a/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/JDBCDriverTest.java b/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/JDBCDriverTest.java index 5c62bca51dc..6d449928b44 100644 --- a/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/JDBCDriverTest.java +++ b/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/JDBCDriverTest.java @@ -212,7 +212,6 @@ public void testForKafkaConnector() throws SQLException { // don't work, but do not throw exception pstmt.setFetchSize(100); - pstmt.addBatch(); insertSql = "INSERT INTO " + tableName + "(`c3`,`c2`) VALUES(?,?)"; diff --git a/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/RequestPreparedStatementTest.java b/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/RequestPreparedStatementTest.java index dc520b74221..aa3fe6fbe4f 100644 --- a/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/RequestPreparedStatementTest.java +++ b/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/RequestPreparedStatementTest.java @@ -23,6 +23,7 @@ import java.sql.*; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.PreparedStatement; @@ -49,20 +50,30 @@ public class RequestPreparedStatementTest { } } - @Test - public void testRequest() { + @DataProvider(name = "createOption") + Object[][] getCreateParm() { + return new Object[][] { {"NoCompress", "Memory"}, + {"NoCompress", "HDD"}, + {"Snappy", "Memory"}, + {"Snappy", "HDD"} }; + } + + @Test(dataProvider = "createOption") + public void testRequest(String compressType, String storageMode) { String dbname = "db" + random.nextInt(100000); executor.dropDB(dbname); boolean ok = executor.createDB(dbname); Assert.assertTrue(ok); - String createTableSql = "create table trans(c1 string,\n" + + String baseSql = "create table trans(c1 string,\n" + " c3 int,\n" + " c4 bigint,\n" + " c5 float,\n" + " c6 double,\n" + " c7 timestamp,\n" + " c8 date,\n" + - " index(key=c1, ts=c7));"; + " index(key=c1, ts=c7))\n "; + String createTableSql = String.format("%s OPTIONS (compress_type='%s', storage_mode='%s');", + baseSql, compressType, storageMode); executor.executeDDL(dbname, createTableSql); String insertSql = "insert into trans values(\"aa\",23,33,1.4,2.4,1590738993000,\"2020-05-04\");"; PreparedStatement pstmt = null; @@ -127,8 +138,8 @@ public void testRequest() { } } - @Test - public void testDeploymentRequest() { + @Test(dataProvider = "createOption") + public void testDeploymentRequest(String compressType, String storageMode) { java.sql.Statement state = executor.getStatement(); String dbname = "db" + random.nextInt(100000); String deploymentName = "dp_test1"; @@ -136,14 +147,16 @@ public void testDeploymentRequest() { state.execute("drop database if exists " + dbname + ";"); state.execute("create database " + dbname + ";"); state.execute("use " + dbname + ";"); - String createTableSql = "create table trans(c1 string,\n" + + String baseSql = "create table trans(c1 string,\n" + " c3 int,\n" + " c4 bigint,\n" + " c5 float,\n" + " c6 double,\n" + " c7 timestamp,\n" + " c8 date,\n" + - " index(key=c1, ts=c7));"; + " index(key=c1, ts=c7))"; + String createTableSql = String.format(" %s OPTIONS (compress_type='%s', storage_mode='%s');", + baseSql, compressType, storageMode); state.execute(createTableSql); String selectSql = "SELECT c1, c3, sum(c4) OVER w1 as w1_c4_sum FROM trans WINDOW w1 AS " + "(PARTITION BY trans.c1 ORDER BY trans.c7 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW);"; @@ -217,20 +230,22 @@ public void testDeploymentRequest() { } } - @Test - public void testBatchRequest() { + @Test(dataProvider = "createOption") + public void testBatchRequest(String compressType, String storageMode) { String dbname = "db" + random.nextInt(100000); executor.dropDB(dbname); boolean ok = executor.createDB(dbname); Assert.assertTrue(ok); - String createTableSql = "create table trans(c1 string,\n" + + String baseSql = "create table trans(c1 string,\n" + " c3 int,\n" + " c4 bigint,\n" + " c5 float,\n" + " c6 double,\n" + " c7 timestamp,\n" + " c8 date,\n" + - " index(key=c1, ts=c7));"; + " index(key=c1, ts=c7))"; + String createTableSql = String.format(" %s OPTIONS (compress_type='%s', storage_mode='%s');", + baseSql, compressType, storageMode); executor.executeDDL(dbname, createTableSql); String insertSql = "insert into trans values(\"aa\",23,33,1.4,2.4,1590738993000,\"2020-05-04\");"; PreparedStatement pstmt = null; @@ -302,8 +317,8 @@ public void testBatchRequest() { } } - @Test - public void testDeploymentBatchRequest() { + @Test(dataProvider = "createOption") + public void testDeploymentBatchRequest(String compressType, String storageMode) { java.sql.Statement state = executor.getStatement(); String dbname = "db" + random.nextInt(100000); String deploymentName = "dp_test1"; @@ -311,14 +326,16 @@ public void testDeploymentBatchRequest() { state.execute("drop database if exists " + dbname + ";"); state.execute("create database " + dbname + ";"); state.execute("use " + dbname + ";"); - String createTableSql = "create table trans(c1 string,\n" + + String baseSql = "create table trans(c1 string,\n" + " c3 int,\n" + " c4 bigint,\n" + " c5 float,\n" + " c6 double,\n" + " c7 timestamp,\n" + " c8 date,\n" + - " index(key=c1, ts=c7));"; + " index(key=c1, ts=c7))"; + String createTableSql = String.format(" %s OPTIONS (compress_type='%s', storage_mode='%s');", + baseSql, compressType, storageMode); state.execute(createTableSql); String selectSql = "SELECT c1, c3, sum(c4) OVER w1 as w1_c4_sum FROM trans WINDOW w1 AS " + "(PARTITION BY trans.c1 ORDER BY trans.c7 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW);"; @@ -398,4 +415,80 @@ public void testDeploymentBatchRequest() { } } } + + @Test + public void testResultSetNull() { + java.sql.Statement state = executor.getStatement(); + String dbname = "db" + random.nextInt(100000); + String deploymentName = "dp_test1"; + try { + state.execute("drop database if exists " + dbname + ";"); + state.execute("create database " + dbname + ";"); + state.execute("use " + dbname + ";"); + String baseSql = "create table trans(c1 string,\n" + + " c3 int,\n" + + " c4 bigint,\n" + + " c5 float,\n" + + " c6 double,\n" + + " c7 timestamp,\n" + + " c8 date,\n" + + " index(key=c1, ts=c7));"; + state.execute(baseSql); + String selectSql = "SELECT c1, c3, sum(c4) OVER w1 as w1_c4_sum FROM trans WINDOW w1 AS " + + "(PARTITION BY trans.c1 ORDER BY trans.c7 ROWS_RANGE BETWEEN 2s PRECEDING AND 0s OPEN PRECEDING EXCLUDE CURRENT_TIME);"; + String deploySql = "DEPLOY " + deploymentName + " " + selectSql; + state.execute(deploySql); + } catch (SQLException e) { + e.printStackTrace(); + Assert.fail(); + } + PreparedStatement pstmt = null; + ResultSet resultSet = null; + try { + Thread.sleep(100); + pstmt = executor.getCallablePreparedStmt(dbname, deploymentName); + + pstmt.setString(1, "aa"); + pstmt.setInt(2, 20); + pstmt.setNull(3, Types.BIGINT); + pstmt.setFloat(4, 1.1f); + pstmt.setDouble(5, 2.1); + pstmt.setTimestamp(6, new Timestamp(0)); + pstmt.setDate(7, Date.valueOf("2020-05-01")); + + resultSet = pstmt.executeQuery(); + + Assert.assertEquals(resultSet.getMetaData().getColumnCount(), 3); + while (resultSet.next()) { + Assert.assertEquals(resultSet.getString(1), "aa"); + Assert.assertEquals(resultSet.getNString(1), "aa"); + Assert.assertEquals(resultSet.getInt(2), 20); + Assert.assertEquals(resultSet.getNString(2), "20"); + Assert.assertTrue(resultSet.getNString(3) == null); + } + + state.execute("drop deployment " + deploymentName + ";"); + String drop = "drop table trans;"; + boolean ok = executor.executeDDL(dbname, drop); + Assert.assertTrue(ok); + ok = executor.dropDB(dbname); + Assert.assertTrue(ok); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } finally { + try { + state.close(); + if (resultSet != null) { + resultSet.close(); + } + if (pstmt != null) { + pstmt.close(); + } + } catch (Exception throwables) { + throwables.printStackTrace(); + } + } + } + } diff --git a/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/SQLRouterSmokeTest.java b/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/SQLRouterSmokeTest.java index b8f54bfa5ca..68dc237d1cf 100644 --- a/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/SQLRouterSmokeTest.java +++ b/java/openmldb-jdbc/src/test/java/com/_4paradigm/openmldb/jdbc/SQLRouterSmokeTest.java @@ -380,7 +380,7 @@ public void testInsertPreparedState(SqlExecutor router) { try { impl2.setString(2, "c"); } catch (Exception e) { - Assert.assertTrue(e.getMessage().contains("data type not match")); + Assert.assertTrue(e.getMessage().contains("set string failed")); } impl2.setString(1, "sandong"); impl2.setDate(2, d3); @@ -390,11 +390,16 @@ public void testInsertPreparedState(SqlExecutor router) { insert = "insert into tsql1010 values(?, ?, ?, ?, ?);"; PreparedStatement impl3 = router.getInsertPreparedStmt(dbname, insert); impl3.setLong(1, 1003); - impl3.setString(3, "zhejiangxx"); impl3.setString(3, "zhejiang"); - impl3.setString(4, "xxhangzhou"); + try { + impl3.setString(3, "zhejiangxx"); + Assert.fail(); + } catch (Exception e) { + Assert.assertTrue(true); + } impl3.setString(4, "hangzhou"); impl3.setDate(2, d4); + impl3.setInt(5, 3); impl3.setInt(5, 4); impl3.closeOnCompletion(); Assert.assertTrue(impl3.isCloseOnCompletion()); @@ -500,7 +505,7 @@ public void testInsertPreparedStateBatch(SqlExecutor router) { try { impl.setInt(2, 1002); } catch (Exception e) { - Assert.assertTrue(e.getMessage().contains("data type not match")); + Assert.assertTrue(e.getMessage().contains("set int failed")); } try { // set failed, so the row is uncompleted, appending row will be failed @@ -510,7 +515,7 @@ public void testInsertPreparedStateBatch(SqlExecutor router) { // j > 0, addBatch has been called Assert.assertEquals(e.getMessage(), "please use executeBatch"); } else { - Assert.assertTrue(e.getMessage().contains("append failed")); + Assert.assertTrue(e.getMessage().contains("cannot get index value")); } } impl.setLong(1, (Long) datas1[j][0]); @@ -536,7 +541,7 @@ public void testInsertPreparedStateBatch(SqlExecutor router) { try { impl2.setInt(2, 1002); } catch (Exception e) { - Assert.assertTrue(e.getMessage().contains("data type not match")); + Assert.assertTrue(e.getMessage().contains("set int failed")); } try { impl2.execute(); @@ -544,7 +549,7 @@ public void testInsertPreparedStateBatch(SqlExecutor router) { if (j > 0) { Assert.assertEquals(e.getMessage(), "please use executeBatch"); } else { - Assert.assertTrue(e.getMessage().contains("append failed")); + Assert.assertTrue(e.getMessage().contains("cannot get index value")); } } impl2.setLong(1, (Long) datas1[j][0]); @@ -562,8 +567,9 @@ public void testInsertPreparedStateBatch(SqlExecutor router) { Object[] datas2 = batchData[i]; try { impl2.addBatch((String) datas2[0]); + Assert.fail(); } catch (Exception e) { - Assert.assertEquals(e.getMessage(), "cannot take arguments in PreparedStatement"); + Assert.assertTrue(true); } int[] result = impl.executeBatch(); diff --git a/java/openmldb-native/pom.xml b/java/openmldb-native/pom.xml index 8bd8a8399f3..b9ad048d640 100644 --- a/java/openmldb-native/pom.xml +++ b/java/openmldb-native/pom.xml @@ -5,7 +5,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/java/openmldb-spark-connector/pom.xml b/java/openmldb-spark-connector/pom.xml index 3f79972c60a..7431aae760f 100644 --- a/java/openmldb-spark-connector/pom.xml +++ b/java/openmldb-spark-connector/pom.xml @@ -6,7 +6,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT openmldb-spark-connector diff --git a/java/openmldb-synctool/pom.xml b/java/openmldb-synctool/pom.xml index a877d921eac..360f26e26be 100644 --- a/java/openmldb-synctool/pom.xml +++ b/java/openmldb-synctool/pom.xml @@ -6,7 +6,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT openmldb-synctool openmldb-synctool diff --git a/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolConfig.java b/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolConfig.java index 26680f85c17..4fdb22834db 100644 --- a/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolConfig.java +++ b/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolConfig.java @@ -37,6 +37,7 @@ public class SyncToolConfig { // public static int CHANNEL_KEEP_ALIVE_TIME; public static String ZK_CLUSTER; public static String ZK_ROOT_PATH; + public static String ZK_CERT; public static String SYNC_TASK_PROGRESS_PATH; public static String HADOOP_CONF_DIR; @@ -86,6 +87,7 @@ private static void parseFromProperties(Properties prop) { if (ZK_ROOT_PATH.isEmpty()) { throw new RuntimeException("zookeeper.root_path should not be empty"); } + ZK_CERT = prop.getProperty("zookeeper.cert", ""); HADOOP_CONF_DIR = prop.getProperty("hadoop.conf.dir", ""); if (HADOOP_CONF_DIR.isEmpty()) { diff --git a/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolImpl.java b/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolImpl.java index f63ff2ae406..0e98cffa6f3 100644 --- a/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolImpl.java +++ b/java/openmldb-synctool/src/main/java/com/_4paradigm/openmldb/synctool/SyncToolImpl.java @@ -85,11 +85,13 @@ public SyncToolImpl(String endpoint) throws SqlException, InterruptedException { this.zkClient = new ZKClient(ZKConfig.builder() .cluster(SyncToolConfig.ZK_CLUSTER) .namespace(SyncToolConfig.ZK_ROOT_PATH) + .cert(SyncToolConfig.ZK_CERT) .build()); Preconditions.checkState(zkClient.connect(), "zk connect failed"); SdkOption option = new SdkOption(); option.setZkCluster(SyncToolConfig.ZK_CLUSTER); option.setZkPath(SyncToolConfig.ZK_ROOT_PATH); + option.setZkCert(SyncToolConfig.ZK_CERT); this.router = new SqlClusterExecutor(option); this.zkCollectorPath = SyncToolConfig.ZK_ROOT_PATH + "/sync_tool/collector"; diff --git a/java/openmldb-taskmanager/pom.xml b/java/openmldb-taskmanager/pom.xml index 56aeef8b7c3..e983c4027e2 100644 --- a/java/openmldb-taskmanager/pom.xml +++ b/java/openmldb-taskmanager/pom.xml @@ -6,7 +6,7 @@ openmldb-parent com.4paradigm.openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT openmldb-taskmanager openmldb-taskmanager diff --git a/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/client/TaskManagerClient.java b/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/client/TaskManagerClient.java index ad4bc157b6e..309154233f8 100644 --- a/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/client/TaskManagerClient.java +++ b/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/client/TaskManagerClient.java @@ -30,9 +30,12 @@ import org.apache.commons.logging.LogFactory; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.api.ACLProvider; import org.apache.curator.framework.recipes.cache.NodeCache; import org.apache.curator.framework.recipes.cache.NodeCacheListener; import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import java.util.ArrayList; import java.util.HashMap; @@ -59,16 +62,34 @@ public TaskManagerClient(String endpoint) { } public TaskManagerClient(String zkCluster, String zkPath) throws Exception { + this(zkCluster, zkPath, ""); + } + + public TaskManagerClient(String zkCluster, String zkPath, String zkCert) throws Exception { if (zkCluster == null || zkPath == null) { logger.info("Zookeeper address is wrong, please check the configuration"); } String masterZnode = zkPath + "/taskmanager/leader"; - zkClient = CuratorFrameworkFactory.builder() + CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder() .connectString(zkCluster) .sessionTimeoutMs(10000) - .retryPolicy(new ExponentialBackoffRetry(1000, 10)) - .build(); + .retryPolicy(new ExponentialBackoffRetry(1000, 10)); + if (!zkCert.isEmpty()) { + builder.authorization("digest", zkCert.getBytes()) + .aclProvider(new ACLProvider() { + @Override + public List getDefaultAcl() { + return ZooDefs.Ids.CREATOR_ALL_ACL; + } + + @Override + public List getAclForPath(String s) { + return ZooDefs.Ids.CREATOR_ALL_ACL; + } + }); + } + zkClient = builder.build(); zkClient.start(); Stat stat = zkClient.checkExists().forPath(masterZnode); if (stat != null) { // The original master exists and is directly connected to it. diff --git a/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/config/TaskManagerConfig.java b/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/config/TaskManagerConfig.java index 76642ff17d6..bba740a2ffa 100644 --- a/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/config/TaskManagerConfig.java +++ b/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/config/TaskManagerConfig.java @@ -101,6 +101,10 @@ public static String getZkRootPath() { return getString("zookeeper.root_path"); } + public static String getZkCert() { + return props.getProperty("zookeeper.cert", ""); + } + public static int getZkConnectionTimeout() { return getInt("zookeeper.connection_timeout"); } @@ -287,7 +291,7 @@ private void init() throws ConfigException { props.setProperty("", ""); } - if (getZkCluster().isEmpty()) { + if (isEmpty(getZkCluster())) { throw new ConfigException("zookeeper.cluster", "should not be empty"); } @@ -339,12 +343,12 @@ private void init() throws ConfigException { props.setProperty("spark.yarn.jars", ""); } - if (isYarn() && !getSparkYarnJars().isEmpty() && getSparkYarnJars().startsWith("file://")) { + if (isYarn() && !isEmpty(getSparkYarnJars()) && getSparkYarnJars().startsWith("file://")) { throw new ConfigException("spark.yarn.jars", "should not use local filesystem for yarn mode"); } - if (props.getProperty("spark.home", "").isEmpty()) { + if (isEmpty(props.getProperty("spark.home", ""))) { if (System.getenv("SPARK_HOME") == null) { throw new ConfigException("spark.home", "should set config 'spark.home' or environment variable 'SPARK_HOME'"); } else { @@ -419,10 +423,10 @@ private void init() throws ConfigException { props.setProperty("spark.default.conf", ""); } - if (!getSparkDefaultConf().isEmpty()) { + if (!isEmpty(getSparkDefaultConf())) { String[] defaultSparkConfs = getSparkDefaultConf().split(";"); for (String sparkConfMap : defaultSparkConfs) { - if (!sparkConfMap.isEmpty()) { + if (!isEmpty(sparkConfMap)) { String[] kv = sparkConfMap.split("="); if (kv.length < 2) { throw new ConfigException("spark.default.conf", String.format("error format of %s", sparkConfMap)); @@ -437,7 +441,7 @@ private void init() throws ConfigException { props.setProperty("spark.eventLog.dir", ""); } - if (!getSparkEventlogDir().isEmpty() && isYarn()) { + if (!isEmpty(getSparkEventlogDir()) && isYarn()) { if (getSparkEventlogDir().startsWith("file://")) { throw new ConfigException("spark.eventLog.dir", "should not use local filesystem for yarn mode"); } @@ -456,7 +460,7 @@ private void init() throws ConfigException { props.setProperty("offline.data.prefix", "file:///tmp/openmldb_offline_storage/"); } - if (getOfflineDataPrefix().isEmpty()) { + if (isEmpty(getOfflineDataPrefix())) { throw new ConfigException("offline.data.prefix", "should not be null"); } else { if (isYarn() || isK8s()) { @@ -466,11 +470,11 @@ private void init() throws ConfigException { } } - if (props.getProperty("batchjob.jar.path", "").isEmpty()) { + if (isEmpty(props.getProperty("batchjob.jar.path", ""))) { props.setProperty("batchjob.jar.path", BatchJobUtil.findLocalBatchJobJar()); } - if (isYarn() && getHadoopConfDir().isEmpty()) { + if (isYarn() && isEmpty(getHadoopConfDir())) { if (System.getenv("HADOOP_CONF_DIR") == null) { throw new ConfigException("hadoop.conf.dir", "should set config 'hadoop.conf.dir' or environment variable 'HADOOP_CONF_DIR'"); } else { @@ -528,7 +532,5 @@ public static boolean isEmpty(String s) { return s == null || s.isEmpty(); } - - } diff --git a/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/server/impl/TaskManagerImpl.java b/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/server/impl/TaskManagerImpl.java index 6fd43d4200c..695338925d8 100644 --- a/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/server/impl/TaskManagerImpl.java +++ b/java/openmldb-taskmanager/src/main/java/com/_4paradigm/openmldb/taskmanager/server/impl/TaskManagerImpl.java @@ -80,6 +80,7 @@ private void initExternalFunction() throws InterruptedException { .connectionTimeout(TaskManagerConfig.getZkConnectionTimeout()) .maxConnectWaitTime(TaskManagerConfig.getZkMaxConnectWaitTime()) .maxRetries(TaskManagerConfig.getZkMaxRetries()) + .cert(TaskManagerConfig.getZkCert()) .build()); zkClient.connect(); diff --git a/java/pom.xml b/java/pom.xml index 3a46ab4d5c1..42ba84c34b8 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -7,7 +7,7 @@ openmldb-parent pom openmldb - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT hybridse-sdk hybridse-native @@ -65,7 +65,7 @@ - 0.8.2-SNAPSHOT + 0.8.3-SNAPSHOT error 2.9.0 @@ -355,7 +355,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.3.0 attach-sources @@ -364,6 +364,9 @@ + + true + org.apache.maven.plugins diff --git a/onebox/start_onebox.sh b/onebox/start_onebox.sh index 639e409b37c..1d92dc7cb62 100755 --- a/onebox/start_onebox.sh +++ b/onebox/start_onebox.sh @@ -75,6 +75,8 @@ cluster_start_component() { --zk_keep_alive_check_interval=60000 --db_root_path="$binlog_dir" --recycle_bin_root_path="$recycle_bin_dir" + --hdd_root_path="$binlog_dir" + --recycle_bin_hdd_root_path="$recycle_bin_dir" ) elif [[ $role = 'nameserver' ]]; then extra_opts+=( diff --git a/onebox/stop_all.sh b/onebox/stop_all.sh index 747adcdf929..7ba340228f3 100755 --- a/onebox/stop_all.sh +++ b/onebox/stop_all.sh @@ -17,7 +17,7 @@ set -x -e if [[ "$OSTYPE" = "darwin"* ]]; then - pkill -9 -x -l openmldb + pkill -9 -x -l openmldb || exit 0 else pgrep -a -f "openmldb.*onebox.*" | awk '{print $1}' | xargs -I {} kill -9 {} fi diff --git a/python/openmldb_sdk/openmldb/sdk/sdk.py b/python/openmldb_sdk/openmldb/sdk/sdk.py index bc8454039b4..e079f77c5d3 100644 --- a/python/openmldb_sdk/openmldb/sdk/sdk.py +++ b/python/openmldb_sdk/openmldb/sdk/sdk.py @@ -52,6 +52,8 @@ def init(self): options.zk_log_level = int(self.options_map['zkLogLevel']) if 'zkLogFile' in self.options_map: options.zk_log_file = self.options_map['zkLogFile'] + if 'zkCert' in self.options_map: + options.zk_cert = self.options_map['zkCert'] else: options = sql_router_sdk.StandaloneOptions() # use host diff --git a/python/openmldb_sdk/setup.py b/python/openmldb_sdk/setup.py index 528250e1822..28493d1a8d4 100644 --- a/python/openmldb_sdk/setup.py +++ b/python/openmldb_sdk/setup.py @@ -18,7 +18,7 @@ setup( name='openmldb', - version='0.8.2a0', + version='0.8.3a0', author='OpenMLDB Team', author_email=' ', url='https://github.com/4paradigm/OpenMLDB', @@ -28,9 +28,10 @@ 'Programming Language :: Python :: 3', ], install_requires=[ - "sqlalchemy <= 1.4.9", - "IPython", - "prettytable", + "importlib-metadata < 5.0", + "sqlalchemy <= 1.4.50", + "IPython <= 7.30.1", + "prettytable <= 3.1.0", ], extras_require={'test': [ "pytest", diff --git a/python/openmldb_tool/README.md b/python/openmldb_tool/README.md index 3381751edf9..d5168a4bf25 100644 --- a/python/openmldb_tool/README.md +++ b/python/openmldb_tool/README.md @@ -48,21 +48,27 @@ status [-h] [--helpfull] [--diff DIFF] optional arguments: -h, --help show this help message and exit --helpfull show full help message and exit - --diff check if all endpoints in conf are in cluster. If set, need to set `--conf_file` + --diff check if all endpoints in conf are in cluster. If set, need to set `-f,--conf_file` ``` Use `show components` to show servers(no apiserver now). +--conn: +- ping all servers, brpc /health to check ok,and +- online servers version and cost time, we can get from brpc http:///version. (ns,tablet, apiserver set_version in brpc server) + TODO: -- ping all servers, brpc /health to check ok -- online servers version, we can get from brpc http:///version. (ns,tablet, apiserver set_version in brpc server) - brpc /flags to get all gflags(including openmldb), `--enable_flags_service=true` required ## Inspect -Use `show table status like '%';` in all dbs, even the hidden db(system db). +`inspect` for full report, no offline diag now. + +inspect online: Use `show table status like '%';` in all dbs, even the hidden db(system db). + +inspect offline: failed jobs, no more info. TODO: check register table? -If you found some online tables are not behaving properly, do inspect online. +inspect job: full support of offline job, select jobs, parse job log ## Test diff --git a/python/openmldb_tool/diagnostic_tool/diagnose.py b/python/openmldb_tool/diagnostic_tool/diagnose.py index 8bd67719489..21ee2961421 100644 --- a/python/openmldb_tool/diagnostic_tool/diagnose.py +++ b/python/openmldb_tool/diagnostic_tool/diagnose.py @@ -31,13 +31,15 @@ import diagnostic_tool.server_checker as checker from diagnostic_tool.table_checker import TableChecker from diagnostic_tool.parser import LogParser +from .inspect import server_ins, table_ins, partition_ins, ops_ins, ops_hint, inspect_hint +from .rpc import RPC from absl import app from absl import flags from absl.flags import argparse_flags from absl import logging # --verbosity --log_dir -# only some sub cmd needs dist file +# only some sub cmd needs dist file TODO(hw): better to move then to other py file, to avoid -h show them flags.DEFINE_string( "conf_file", "", @@ -81,7 +83,7 @@ def status(args): # --diff with dist conf file, conf_file is required if args.diff: - assert flags.FLAGS.conf_file, "need --conf_file" + assert flags.FLAGS.conf_file, "need -f,--conf_file" print( "only check components in conf file, if cluster has more components, ignore them" ) @@ -96,39 +98,56 @@ def status(args): def inspect(args): - insepct_online(args) - inspect_offline(args) + # report all + # 1. server level + connect = Connector() + status_checker = checker.StatusChecker(connect) + server_map = status_checker._get_components() + offlines = server_ins(server_map) + + # 3. ns ops level, but show only if has unhealthy tables, so hint later + last_one, should_warn, related_ops = ops_ins(connect) + + # 2. partition level: show unhealthy tables and get some hints about table + hints = partition_ins(server_map, related_ops) + if hints: + # show 3 here + ops_hint(last_one, should_warn) + # 4. hint + # let user know what to do + # 1) start offline servers + # 2) let user know the warning table is fatal or not, related ops, warn if offset is too large + # 3) if table not healthy and no related ops, use recoverdata + inspect_hint(offlines, hints) def insepct_online(args): - """show table status""" - conn = Connector() + """inspect online""" + connect = Connector() # scan all db include system db - fails = [] - rs = conn.execfetch("show table status like '%';") - rs.sort(key=lambda x: x[0]) - print(f"inspect {len(rs)} online tables(including system tables)") - for t in rs: + fails = table_ins(connect) + for t in fails: if t[13]: print(f"unhealthy table {t[2]}.{t[1]}:\n {t[:13]}") # sqlalchemy truncated ref https://github.com/sqlalchemy/sqlalchemy/commit/591e0cf08a798fb16e0ee9b56df5c3141aa48959 # so we print warnings alone print(f"full warnings:\n{t[13]}") - fails.append(f"{t[2]}.{t[1]}") - - assert not fails, f"unhealthy tables: {fails}" - print(f"all tables are healthy") + # if has fails, summary will print in table_ins + if not fails: + print(f"all tables are healthy") if getattr(args, "dist", False): - table_checker = TableChecker(conn) - table_checker.check_distribution(dbs=flags.FLAGS.db.split(",")) + table_checker = TableChecker(connect) + dbs = flags.FLAGS.db + db_list = dbs.split(",") if dbs else None + table_checker.check_distribution(dbs=db_list) def inspect_offline(args): """scan jobs status, show job log if failed""" final_failed = ["failed", "killed", "lost"] total, num, jobs = _get_jobs(final_failed) - # TODO some failed jobs are known, what if we want skip them? + # TODO some failed jobs are known or too old, what if we want skip them? print(f"inspect {total} offline jobs") if num: failed_jobs_str = "\n".join(jobs) @@ -241,7 +260,6 @@ def rpc(args): tm: taskmanager""" ) return - from diagnostic_tool.rpc import RPC # use status connction to get version conns_with_version = { @@ -301,7 +319,7 @@ def parse_arg(argv): status_parser.add_argument( "--diff", action="store_true", - help="check if all endpoints in conf are in cluster. If set, need to set `--conf_file`", + help="check if all endpoints in conf are in cluster. If set, need to set `-f,--conf_file`", ) # TODO action support in all python 3.x? status_parser.add_argument( "--conn", @@ -313,10 +331,10 @@ def parse_arg(argv): # sub inspect inspect_parser = subparsers.add_parser( "inspect", - help="Inspect online and offline. Use `inspect [online/offline]` to inspect one.", + help="Get full inspect report, --nocolor for batch mode, --table_width for partition tables display", ) - # inspect online & offline inspect_parser.set_defaults(command=inspect) + inspect_sub = inspect_parser.add_subparsers() # inspect online online = inspect_sub.add_parser("online", help="only inspect online table.") @@ -325,7 +343,9 @@ def parse_arg(argv): "--dist", action="store_true", help="Inspect online distribution." ) # inspect offline - offline = inspect_sub.add_parser("offline", help="only inspect offline jobs.") + offline = inspect_sub.add_parser( + "offline", help="only inspect offline jobs, show failed jobs." + ) offline.set_defaults(command=inspect_offline) # inspect job ins_job = inspect_sub.add_parser( diff --git a/python/openmldb_tool/diagnostic_tool/inspect.py b/python/openmldb_tool/diagnostic_tool/inspect.py new file mode 100644 index 00000000000..03c8e8c9e94 --- /dev/null +++ b/python/openmldb_tool/diagnostic_tool/inspect.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2021 4Paradigm +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" gen multi-level readable reports for cluster devops """ +from absl import flags +import json +from collections import defaultdict +from prettytable import PrettyTable + +from .rpc import RPC + +# ANSI escape codes +flags.DEFINE_bool("nocolor", False, "disable color output", short_name="noc") +flags.DEFINE_integer( + "table_width", + 12, + "max columns in one row, 1 partition use r+1 cols, set k*(r+1)", + short_name="tw", +) +flags.DEFINE_integer( + "offset_diff_thresh", 100, "offset diff threshold", short_name="od" +) + +# color: red, green +RED = "\033[31m" +GREEN = "\033[32m" +BLUE = "\033[34m" +YELLOW = "\033[1;33m" +RESET = "\033[0m" + + +# switch by nocolor flag +def cr_print(color, obj): + if flags.FLAGS.nocolor or color == None: + print(obj) + else: + print(f"{color}{obj}{RESET}") + + +def server_ins(server_map): + print("\n\nServer Detail") + print(server_map) + offlines = [] + for component, value_list in server_map.items(): + for endpoint, status in value_list: + if status != "online": + offlines.append(f"[{component}]{endpoint}") + continue # offline tablet is needlessly to rpc + if offlines: + s = "\n".join(offlines) + cr_print(RED, f"offline servers:\n{s}") + else: + cr_print(GREEN, "all servers online (no backup tm and apiserver)") + return offlines + + +# support nocolor +def light(color, symbol, detail): + if flags.FLAGS.nocolor: + return f"{symbol} {detail}" + else: + return f"{color}{symbol}{RESET} {detail}" + + +def state2light(state): + state = state.ljust(15) # state str should be less than 15 + if not state.startswith("k"): + # meta mismatch status, all red + return light(RED, "X", state) + else: + # meta match, get the real state + state = state[1:] + if state.startswith("TableNormal"): + # green + return light(GREEN, "O", state) + else: + # ref https://github.com/4paradigm/OpenMLDB/blob/0462f8a9682f8d232e8d44df7513cff66870d686/tools/tool.py#L291 + # undefined is loading too: state == "kTableLoading" or state == "kTableUndefined" + # snapshot doesn't mean unhealthy: state == "kMakingSnapshot" or state == "kSnapshotPaused" + return light(YELLOW, "=", state) + + +# similar with `show table status` warnings field, but easier to read +# prettytable.colortable just make table border and header lines colorful, so we color the value +def check_table_info(t, replicas_on_tablet, tablet2idx): + pnum, rnum = t["partition_num"], t["replica_num"] + assert pnum == len(t["table_partition"]) + # multi-line for better display, max display columns in one row + valuable_cols = pnum * (rnum + 1) + display_width = min(flags.FLAGS.table_width, valuable_cols) + # if real multi-line, the last line may < width, padding with empty string + rest = valuable_cols % display_width + total_cols = valuable_cols + (0 if rest == 0 else display_width - rest) + + idx_row = [""] * total_cols + leader_row = [""] * total_cols + followers_row = [""] * total_cols + + table_mark = 0 + hint = "" + for i, p in enumerate(t["table_partition"]): + # each partition add 3 row, and rnum + 1 columns + # tablet idx pid | 1 | 4 | 5 + # leader 1 o + # followers o o + pid = p["pid"] + assert pid == i + + # sort by list tablets + replicas = [] + for r in p["partition_meta"]: + tablet = r["endpoint"] + # tablet_has_partition useless + # print(r["endpoint"], r["is_leader"], r["tablet_has_partition"]) + replicas_on_t = replicas_on_tablet[t["tid"]][p["pid"]] + # may can't find replica on tablet, e.g. tablet server is not ready + info_on_tablet = {} + if r["endpoint"] not in replicas_on_t: + info_on_tablet = {"state": "Miss", "mode": "Miss", "offset": -1} + else: + info_on_tablet = replicas_on_t[r["endpoint"]] + # print(info_on_tablet) + m = { + "role": "leader" if r["is_leader"] else "follower", + "state": info_on_tablet["state"], + "acrole": info_on_tablet["mode"], + "offset": info_on_tablet["offset"], + } + replicas.append((tablet2idx[tablet], m)) + + assert len(replicas) == rnum + replicas.sort(key=lambda x: x[0]) + leader_ind = [i for i, r in enumerate(replicas) if r[1]["role"] == "leader"] + # replica on offline tablet is still in the ns meta, so leader may > 1 + # assert len(ind) <= 1, f"should be only one leader or miss leader in {replicas}" + + # show partition idx and tablet server idx + cursor = i * (rnum + 1) + idx_row[cursor : cursor + rnum + 1] = ["p" + str(pid)] + [ + r[0] for r in replicas + ] + + # fulfill leader line + if leader_ind: + for leader in leader_ind: + # leader state + lrep = replicas[leader][1] + if lrep["state"] != "Miss" and lrep["acrole"] != "kTableLeader": + lrep["state"] = "NotLeaderOnT" # modify the state + leader_row[cursor + leader + 1] = state2light(lrep["state"]) + else: + # can't find leader in nameserver metadata, set in the first column(we can't find leader on any tablet) + leader_row[cursor] = state2light("NotFound") + + # fulfill follower line + for i, r in enumerate(replicas): + idx = cursor + i + 1 + if i in leader_ind: + continue + frep = r[1] + if frep["state"] != "Miss" and frep["acrole"] != "kTableFollower": + frep["state"] = "NotFollowerOnT" + followers_row[idx] = state2light(frep["state"]) + + # after state adjust, diag table + replicas = [r[1] for r in replicas] # tablet server is needless now + # fatal: leader replica is not normal, may read/write fail + # get one normal leader, the partition can work + if not leader_ind or not any( + [replicas[i]["state"] == "kTableNormal" for i in leader_ind] + ): + table_mark = max(4, table_mark) + hint += f"partition {pid} leader replica is not normal\n" + # warn: need repair(may auto repair by auto_failover), but not in emergency + # follower replica is not normal + if any([r["state"] != "kTableNormal" for r in replicas]): + table_mark = max(3, table_mark) + hint += f"partition {pid} has unhealthy replicas\n" + + # offset is not consistent, only check normal replicas + offsets = [r["offset"] for r in replicas if r["state"] == "kTableNormal"] + if offsets and max(offsets) - min(offsets) > flags.FLAGS.offset_diff_thresh: + table_mark = max(3, table_mark) + hint += ( + f"partition {pid} has offset diff > {flags.FLAGS.offset_diff_thresh}\n" + ) + + x = PrettyTable(align="l") + + x.field_names = [i for i in range(display_width)] + step = display_width + for i in range(0, len(idx_row), step): + x.add_row(idx_row[i : i + step]) + x.add_row(leader_row[i : i + step]) + # Upgrade prettytable version to support divider, need to upgrade sqlalchemy first + #x.add_row(followers_row[i : i + step], divider=True) + x.add_row(followers_row[i : i + step]) + + table_summary = "" + if table_mark >= 4: + table_summary = light( + RED, + "X", + f"Fatal table {t['db']}.{t['name']}, read/write may fail, need repair immediately", + ) + elif table_mark >= 3: + table_summary = light( + YELLOW, "=", f"Warn table {t['db']}.{t['name']}, still work, but need repair" + ) + if table_summary: + table_summary += "\n" + hint + return x, table_summary + + +def show_table_info(t, replicas_on_tablet, tablet2idx): + """check table info and display for ut""" + print( + f"Table {t['tid']} {t['db']}.{t['name']} {t['partition_num']} partitions {t['replica_num']} replicas" + ) + table, _ = check_table_info(t, replicas_on_tablet, tablet2idx) + print(table.get_string(border=True, header=False)) + + +def table_ins(connect): + print("\n\nTable Healthy Detail") + rs = connect.execfetch("show table status like '%';") + rs.sort(key=lambda x: x[0]) + print(f"summary: {len(rs)} tables(including system tables)") + warn_tables = [] + for t in rs: + # any warning means unhealthy, partition_unalive may be 0 but already unhealthy, warnings is accurate? + if t[13]: + warn_tables.append(t) + if warn_tables: + # only show tables name + s = "\n".join([f"{t[2]}.{t[1]}" for t in warn_tables]) + cr_print(RED, f"unhealthy tables:\n{s}") + else: + cr_print(GREEN, "all tables are healthy") + return warn_tables + + +def partition_ins(server_map, related_ops): + print("\n\nTable Partition Detail") + # ns table info + rpc = RPC("ns") + res = rpc.rpc_exec("ShowTable", {"show_all": True}) + if not res: + cr_print(RED, "get table info failed or empty from nameserver") + return + res = json.loads(res) + all_table_info = res["table_info"] + + # get table info from tablet server + # >> + replicas = defaultdict(lambda: defaultdict(dict)) + tablets = server_map["tablet"] # has status + invalid_tablets = set() + for tablet, status in tablets: + if status == "offline": + invalid_tablets.add(tablet) + continue + # GetTableStatusRequest empty field means get all + rpc = RPC(tablet) + res = None + try: + res = json.loads(rpc.rpc_exec("GetTableStatus", {})) + except Exception as e: + print(f"rpc {tablet} failed") + # may get empty when tablet server is not ready + if not res or res["code"] != 0: + cr_print(RED, f"get table status failed or empty from {tablet}(online)") + invalid_tablets.add(tablet) + continue + if "all_table_status" not in res: + # just empty replica on tablet, skip + continue + for rep in res["all_table_status"]: + rep["tablet"] = tablet + # tid, pid are int + tid, pid = rep["tid"], rep["pid"] + replicas[tid][pid][tablet] = rep + + tablet2idx = {tablet[0]: i + 1 for i, tablet in enumerate(tablets)} + print(f"tablet server order: {tablet2idx}") + if invalid_tablets: + cr_print( + RED, + f"some tablet servers are offline/bad, can't get table info(exclude empty table server): {invalid_tablets}", + ) + + # display, depends on table info, replicas are used to check + all_table_info.sort(key=lambda x: x["tid"]) + # related op map + related_ops_map = {} + for op in related_ops: + db = op[9] + table = op[10] + if db not in related_ops_map: + related_ops_map[db] = {} + if table not in related_ops_map[db]: + related_ops_map[db][table] = [] + related_ops_map[db][table].append(op) + # print(f"related ops: {related_ops_map}") + print("") # for better display + diag_result = [] + for t in all_table_info: + # no need to print healthy table + table, diag_hint = check_table_info(t, replicas, tablet2idx) + if diag_hint: + print( + f"Table {t['tid']} {t['db']}.{t['name']} {t['partition_num']} partitions {t['replica_num']} replicas" + ) + print(table.get_string(header=False)) + if t["db"] in related_ops_map and t["name"] in related_ops_map[t["db"]]: + diag_hint += f"related op: {sorted(related_ops_map[t['db']][t['name']], key=lambda x: x[11])}" # 11 is pid + diag_result.append(diag_hint) + # comment for table info display, only for unhealthy table TODO: draw a example + if diag_result: + print( + """ +Example: +tablet server order: {'xxx': 1, 'xxx': 2, 'xxx': 3} -> get real tablet addr by idx ++----+-------------------+------------------+------------------+ +| p0 | 1 | 2 | 3 | -> p0: partition 0, 1-3: tablet server idx +| | [light] status | | | -> leader replica is on tablet 1 +| | | [light] status | [light] status | -> follower replicas are on tablet 2, 3 ++----+-------------------+------------------+------------------+ +light: +Green O -> OK +Yellow = -> replica meta is ok but state is not normal +Red X -> NotFound/Miss/NotFollowerOnT/NotLeaderOnT""" + ) + return diag_result + + +def ops_ins(connect): + # op sorted by id TODO: detail to show all include succ op? + rs = connect.execfetch("show jobs from NameServer;") + should_warn = [] + from datetime import datetime + # already in order + ops = [list(op) for op in rs] + for i in range(len(ops)): + op = ops[i] + op[3] = str(datetime.fromtimestamp(int(op[3]) / 1000)) if op[4] else "..." + op[4] = str(datetime.fromtimestamp(int(op[4]) / 1000)) if op[4] else "..." + if op[2] != "FINISHED": + should_warn.append(op) + + recover_type = ["kRecoverTableOP", "kChangeLeaderOP", "kReAddReplicaOP", "kOfflineReplicaOP"] + related_ops = [ + op + for op in should_warn + if op[1] in recover_type and op[2] in ["Submitted", "RUNNING"] + ] + return ops[-1] if ops else None, should_warn, related_ops + +def ops_hint(last_one, should_warn): + print("\n\nOps Detail") + print("> failed ops do not mean cluster is unhealthy, just for reference") + # peek last one to let user know if cluster has tried to recover, or we should wait + if last_one: + print("last one op(check time): ", last_one) + else: + print("no ops in nameserver") + if not should_warn: + print("all nameserver ops are finished") + else: + print("last 10 ops != finished:") + print(*should_warn[-10:], sep="\n") + +def inspect_hint(server_hint, table_hints): + print( + """ + +================== +Summary & Hint +================== +Server: +""" + ) + if server_hint: + cr_print(RED, f"offline servers {server_hint}, restart them first") + else: + cr_print(GREEN, "all servers online") + print("\nTable:\n") + for h in table_hints: + print(h) + if table_hints: + print( + """ + Make sure all servers online, and no ops for the table is running. + Repair table manually, run recoverdata, check https://openmldb.ai/docs/zh/main/maintain/openmldb_ops.html. + Check 'Table Partitions Detail' above for detail. + """ + ) + else: + cr_print(GREEN, "all tables are healthy") diff --git a/python/openmldb_tool/diagnostic_tool/pb.py b/python/openmldb_tool/diagnostic_tool/pb.py new file mode 100644 index 00000000000..06219d00b61 --- /dev/null +++ b/python/openmldb_tool/diagnostic_tool/pb.py @@ -0,0 +1,118 @@ +# Copyright 2021 4Paradigm +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf.descriptor import Descriptor, FieldDescriptor +from absl import flags + + +class DescriptorHelper: + def __init__(self, service): + # lazy import + assert flags.FLAGS.pbdir, "pbdir not set" + import sys + from pathlib import Path + + sys.path.append(Path(flags.FLAGS.pbdir).as_posix()) + import tablet_pb2 + import name_server_pb2 + import taskmanager_pb2 + + # google.protobuf.symbol_database can get service desc by name, but we have already included all pb2 files we need + # just use one file + pb_map = { + "TabletServer": tablet_pb2, + "NameServer": name_server_pb2, + "TaskManagerServer": taskmanager_pb2, + # "ApiServer": api_server_pb2, + # "DataSync": data_sync_pb2, + } + self.descriptor = pb_map[service].DESCRIPTOR.services_by_name[service] + # from google.protobuf import symbol_database + # self.sym_db = symbol_database.Default() + + def get_input_json(self, method): + m = self.descriptor.FindMethodByName(method) + if not m: + return False, f"method {method} not found" + if not m.input_type.fields: # e.g. ShowTabletRequest is emtpy + return False, f"method {method} has no input" + # GeneratedProtocolMessageType __dict__ is complex, can't use it directly + # cl = self.sym_db.GetSymbol(m.input_type.full_name) + + # fields build a map, message is Descriptor, fields in msg is FieldDescriptor + return True, Field.to_json(m.input_type) + + +class Field: + def to_str(typ): + typ2str = { + FieldDescriptor.TYPE_DOUBLE: "double", + FieldDescriptor.TYPE_FLOAT: "float", + FieldDescriptor.TYPE_INT64: "int64", + FieldDescriptor.TYPE_UINT64: "uint64", + FieldDescriptor.TYPE_INT32: "int32", + FieldDescriptor.TYPE_FIXED64: "fixed64", + FieldDescriptor.TYPE_FIXED32: "fixed32", + FieldDescriptor.TYPE_BOOL: "bool", + FieldDescriptor.TYPE_STRING: "string", + FieldDescriptor.TYPE_GROUP: "group", + FieldDescriptor.TYPE_MESSAGE: "message", + FieldDescriptor.TYPE_BYTES: "bytes", + FieldDescriptor.TYPE_UINT32: "uint32", + } + return typ2str[typ] + + def to_json(field): + # label optional, required, or repeated. + label = {1: "optional", 2: "required", 3: "repeated"} + + def is_map(f): + # I'm a map(containing_type = who includes me and my fields name are key-value) + # e.g. tm RunBatchSql --hint the conf field is map + return f.containing_type and [ff.name for ff in f.fields] == [ + "key", + "value", + ] + + if isinstance(field, FieldDescriptor): + if field.message_type: + # message_type is a Descriptor, check if it's a map + if is_map(field.message_type): + m = field.message_type + # treat key-value as map type, can't figure out custom type, no nested, so just generate here + return { + f"<{m.fields[0].name}>": f"<{m.fields[1].name}>", + "...": "...", + } + else: + # normal nested message + return Field.to_json(field.message_type) + elif field.type == FieldDescriptor.TYPE_ENUM: + return "/".join([n.name for n in field.enum_type.values]) + else: + return f"<{Field.to_str(field.type)}>" + + elif isinstance(field, Descriptor): + d = {} + for f in field.fields: + # each one is FieldDescriptor + # map is repeated too, but it's not a list + if f.label == 3 and not is_map(f.message_type): + # json list style + d[f"({label[f.label]})" + f.name] = [Field.to_json(f), "..."] + else: + d[f"({label[f.label]})" + f.name] = Field.to_json(f) + return d + else: + raise ValueError(f"unknown type {type(field)}") diff --git a/python/openmldb_tool/diagnostic_tool/rpc.py b/python/openmldb_tool/diagnostic_tool/rpc.py index 686734e7641..07d9ff7e964 100644 --- a/python/openmldb_tool/diagnostic_tool/rpc.py +++ b/python/openmldb_tool/diagnostic_tool/rpc.py @@ -12,103 +12,45 @@ # See the License for the specific language governing permissions and # limitations under the License. -from absl import flags import json import requests -from bs4 import BeautifulSoup -from google.protobuf.descriptor import FieldDescriptor from .server_checker import StatusChecker from .connector import Connector +from absl import flags + +# used by pb.py but set here for simplicity, we will check pbdir before call hint(import pb) flags.DEFINE_string( "pbdir", "/tmp/diag_cache", "pb2 root dir, if not set, will use the /pb2 directory in the same directory as this script", ) +def validate_ip_address(ip_string): + # localhost:xxxx is valid ip too, ip must have at least one ":" + return ip_string.find(":") != -1 -class DescriptorHelper: - def __init__(self, service): - # TODO(hw): symbol_database is useful? - # lazy import - assert flags.FLAGS.pbdir, "pbdir not set" - import sys - from pathlib import Path - sys.path.append(Path(flags.FLAGS.pbdir).as_posix()) - import tablet_pb2 - import name_server_pb2 - import taskmanager_pb2 - - pb_map = { - "TabletServer": tablet_pb2, - "NameServer": name_server_pb2, - "TaskManagerServer": taskmanager_pb2, - # "ApiServer": api_server_pb2, - # "DataSync": data_sync_pb2, - } - self.descriptor = pb_map[service].DESCRIPTOR.services_by_name[service] - - def get_input_json(self, method): - inp = self.descriptor.FindMethodByName(method).input_type - return Field.to_json(inp) - - -class Field: - def to_str(typ): - typ2str = { - FieldDescriptor.TYPE_DOUBLE: "double", - FieldDescriptor.TYPE_FLOAT: "float", - FieldDescriptor.TYPE_INT64: "int64", - FieldDescriptor.TYPE_UINT64: "uint64", - FieldDescriptor.TYPE_INT32: "int32", - FieldDescriptor.TYPE_FIXED64: "fixed64", - FieldDescriptor.TYPE_FIXED32: "fixed32", - FieldDescriptor.TYPE_BOOL: "bool", - FieldDescriptor.TYPE_STRING: "string", - FieldDescriptor.TYPE_GROUP: "group", - FieldDescriptor.TYPE_MESSAGE: "message", - FieldDescriptor.TYPE_BYTES: "bytes", - FieldDescriptor.TYPE_UINT32: "uint32", - } - return typ2str[typ] - - def to_json(field): - # label optional, required, or repeated. - label = {1: "optional", 2: "required", 3: "repeated"} - if isinstance(field, FieldDescriptor): - key = f"({label[field.label]})" + field.name - if field.type == FieldDescriptor.TYPE_MESSAGE: - value = Field.to_json(field.message_type) - elif field.type == FieldDescriptor.TYPE_ENUM: - value = "/".join([n.name for n in field.enum_type.values]) - else: - value = Field.to_str(field.type) - if field.label == 3: - # json list style - return {key: [value, "..."]} - else: - return {key: value} - else: - # field is a message - if field.containing_type and [f.name for f in field.fields] == [ - "key", - "value", - ]: - # treat key-value as map type, can't figure out custom type - # TODO(hw): it's ok to pass a json list to proto map? - return {"k": "v", "...": "..."} - d = {} - for f in field.fields: - d.update(Field.to_json(f)) - return d + +host2service = { + "nameserver": "NameServer", + "taskmanager": "openmldb.taskmanager.TaskManagerServer", + "tablet": "TabletServer", +} class RPC: """rpc service""" def __init__(self, host) -> None: - self.host, self.endpoint, self.service = RPC.get_endpoint_service(host.lower()) + if validate_ip_address(host): + self.endpoint = host + self.host = "tablet" # TODO: you can get ns/tm by name, it's not necessary to input ip + self.service = host2service[self.host] + else: + self.host, self.endpoint, self.service = RPC.get_endpoint_service( + host.lower() + ) def rpc_help(self): if self.host == "taskmanager": @@ -123,26 +65,31 @@ def rpc_exec(self, operation, field): ) return r.text - def hint(self, info): - if not info: + def hint(self, method): + if not method: # show service name and all rpc methods print(self.rpc_help()) return - # input message to json style - # if taskmanager, service in pb2 is TaskManagerServer service = ( self.service if not self.service.endswith("TaskManagerServer") else "TaskManagerServer" ) + from .pb import DescriptorHelper - helper = DescriptorHelper(service) - json_str = json.dumps(helper.get_input_json(info), indent=4) + ok, input_json = DescriptorHelper(service).get_input_json(method) + if not ok: + print(input_json) # if not ok, it's message + return + # input message to json style + json_str = json.dumps(input_json, indent=4) print( - f"You should input json like this, ignore round brackets in the key and double quotation marks in the value: --field '{json_str}'" + f"You should input json like this:\n --field '{json_str}'" ) + print("ignore round brackets in the key, e.g. (required)") + print('"<>" shows the data type, e.g. "" means you should set string') def search_in(self, typ, info): for item in typ: @@ -168,14 +115,12 @@ def get_endpoint_service(host): host = "nameserver" if host == "ns" else "taskmanager" assert host in components_map, f"{host} not found in cluster" endpoint = components_map[host][num][0] - host2service = { - "nameserver": "NameServer", - "taskmanager": "openmldb.taskmanager.TaskManagerServer", - "tablet": "TabletServer", - } + service = host2service[host] return host, endpoint, service def parse_html(html): + from bs4 import BeautifulSoup + soup = BeautifulSoup(html, "html.parser") return soup.get_text("\n") diff --git a/python/openmldb_tool/diagnostic_tool/table_checker.py b/python/openmldb_tool/diagnostic_tool/table_checker.py index 969e7d110e4..f9703054d5c 100644 --- a/python/openmldb_tool/diagnostic_tool/table_checker.py +++ b/python/openmldb_tool/diagnostic_tool/table_checker.py @@ -24,11 +24,11 @@ class TableChecker: def __init__(self, conn: Connector): self.conn = conn - def check_distribution(self, dbs: list): + def check_distribution(self, dbs: list = None): exist_dbs = [db[0] for db in self.conn.execfetch("SHOW DATABASES")] if not exist_dbs: return - if dbs == ['']: + if not dbs or len(dbs) == 0: dbs = exist_dbs assert all([db in exist_dbs for db in dbs]), "some databases are not exist" @@ -36,67 +36,87 @@ def check_distribution(self, dbs: list): url = f"http://{ns_leader}/NameServer/ShowTable" res = requests.get(url, json={"show_all": True}) tables = res.json()["table_info"] - + if not tables or len(tables) == 0: + print("no table") + return tablet2partition = {} tablet2count = {} tablet2mem = {} tablet2dused = {} table_infos = [] - max_values = {'mp': 0, 'mc': 0, 'mm': 0, 'md': 0} + max_values = {"mp": 0, "mc": 0, "mm": 0, "md": 0} for table in tables: - if table['db'] not in dbs: + if table["db"] not in dbs: continue t = {} - t['name'] = table['db'] + "." + table['name'] - parts = table['table_partition'] - part_dist = self._collect(parts, '') - count_dist = self._collect(parts, 'record_cnt') - mem_dist = self._collect(parts, 'record_byte_size') - dused_dist = self._collect(parts, 'diskused') - max_values['mp'] = max(max_values['mp'], *part_dist.values()) - max_values['mc'] = max(max_values['mc'], *count_dist.values()) - max_values['mm'] = max(max_values['mm'], *mem_dist.values()) - max_values['md'] = max(max_values['md'], *dused_dist.values()) - t['part_size'] = len(parts) - t['part_dist'] = part_dist - t['count_dist'] = count_dist - t['mem_dist'] = mem_dist - t['dused_dist'] = dused_dist + t["name"] = table["db"] + "." + table["name"] + parts = table["table_partition"] + part_dist = self._collect(parts, "") + count_dist = self._collect(parts, "record_cnt") + mem_dist = self._collect(parts, "record_byte_size") + dused_dist = self._collect(parts, "diskused") + t["part_size"] = len(parts) + t["part_dist"] = part_dist + t["count_dist"] = count_dist + t["mem_dist"] = mem_dist + t["dused_dist"] = dused_dist table_infos.append(t) self._add_merge(tablet2partition, part_dist) self._add_merge(tablet2count, count_dist) self._add_merge(tablet2mem, mem_dist) self._add_merge(tablet2dused, dused_dist) - max_values['mm'] = round(max_values['mm'] / 1024 / 1024, 4) - max_values['md'] = round(max_values['md'] / 1024 / 1024, 4) + def get_max(di): + return max(di.values()) + + max_values["mp"] = get_max(tablet2partition) + max_values["mc"] = get_max(tablet2count) + max_values["mm"] = round(get_max(tablet2mem) / 1024 / 1024, 4) + max_values["md"] = round(get_max(tablet2dused) / 1024 / 1024, 4) + max_width = 40 for t in table_infos: print() - print(t['name']) - print('partition size:', t['part_size']) - print('partition dist(include replica)') - self._show_dist(t['part_dist'], max_width=max_width * max(*t['part_dist'].values()) / max_values['mp']) - print('record count dist(include replica)') - self._show_dist(t['count_dist'], max_width=0 if max_values['mc'] == 0 else max_width * max(*t['count_dist'].values()) / max_values['mc']) - print('mem dist(include replica)(MB)') - self._byte2mb(t['mem_dist']) - self._show_dist(t['mem_dist'], max_width=0 if max_values['mm'] == 0 else max_width * max(*t['mem_dist'].values()) / max_values['mm']) - print('diskused dist(include replica)(MB)') - self._byte2mb(t['dused_dist']) - self._show_dist(t['dused_dist'], max_width=max_width * max(*t['dused_dist'].values()) / max_values['md']) + print(t["name"], "distribution") + print("partition size:", t["part_size"]) + print("partition dist(include replica)") + self._show_dist( + t["part_dist"], + max_width=max_width * get_max(t["part_dist"]) / max_values["mp"], + ) + print("record count dist(include replica)") + self._show_dist( + t["count_dist"], + max_width=0 + if max_values["mc"] == 0 + else max_width * get_max(t["count_dist"]) / max_values["mc"], + ) + print("mem dist(include replica)(MB)") + self._byte2mb(t["mem_dist"]) + self._show_dist( + t["mem_dist"], + max_width=0 + if max_values["mm"] == 0 + else max_width * get_max(t["mem_dist"]) / max_values["mm"], + ) + print("diskused dist(include replica)(MB)") + self._byte2mb(t["dused_dist"]) + self._show_dist( + t["dused_dist"], + max_width=max_width * get_max(t["dused_dist"]) / max_values["md"], + ) print() - print('total') - print('tablet2partition') + print("tablet server load distribution") + print("tablet2partition") self._show_dist(tablet2partition) - print('tablet2count') + print("tablet2count(row)") self._show_dist(tablet2count) - print('tablet2mem(MB)') + print("tablet2mem(MB)") self._byte2mb(tablet2mem) self._show_dist(tablet2mem) - print('tablet2diskused(MB)') + print("tablet2diskused(MB)") self._byte2mb(tablet2dused) self._show_dist(tablet2dused) @@ -106,16 +126,24 @@ def _byte2mb(self, dist: dict): def _show_dist(self, dist: dict, max_width=40): figc = tpl.figure() - figc.barh(list(dist.values()), labels=list(dist.keys()), max_width=max_width) + if not dist: # protect barh args + print("no data") + return + figc.barh( + list(dist.values()), + labels=list(dist.keys()), + max_width=max_width, + force_ascii=True, + ) figc.show() def _collect(self, parts, field): dist = {} for part in parts: - for replica in part['partition_meta']: - if replica['endpoint'] not in dist: - dist[replica['endpoint']] = 0 - dist[replica['endpoint']] += replica[field] if field else 1 + for replica in part["partition_meta"]: + if replica["endpoint"] not in dist: + dist[replica["endpoint"]] = 0 + dist[replica["endpoint"]] += replica[field] if field else 1 return dist def _add_merge(self, dist, dist2): diff --git a/python/openmldb_tool/setup.py b/python/openmldb_tool/setup.py index 097b3c229c8..555e5b51153 100644 --- a/python/openmldb_tool/setup.py +++ b/python/openmldb_tool/setup.py @@ -18,7 +18,7 @@ setup( name="openmldb-tool", - version="0.8.2a0", + version='0.8.3a0', author="OpenMLDB Team", author_email=" ", url="https://github.com/4paradigm/OpenMLDB", @@ -28,7 +28,7 @@ "Programming Language :: Python :: 3", ], install_requires=[ - "openmldb >= 0.6.9", + "openmldb >= 0.8.1", "absl-py", "pyyaml", "paramiko", @@ -36,12 +36,12 @@ "requests", ], extras_require={ - "rpc": [ + "pb": [ "protobuf==3.6.1", "beautifulsoup4", ], "test": [ - "openmldb-tool[rpc]", + "openmldb-tool[pb]", "pytest", ], }, diff --git a/python/openmldb_tool/tests/inspect_test.py b/python/openmldb_tool/tests/inspect_test.py new file mode 100644 index 00000000000..6f5ece39c05 --- /dev/null +++ b/python/openmldb_tool/tests/inspect_test.py @@ -0,0 +1,355 @@ +import pytest +from diagnostic_tool.inspect import show_table_info +from absl import flags + +flags.FLAGS["nocolor"].parse(False) +flags.FLAGS["table_width"].parse(12) + + +def test_show(): + # assume 3 tablet server + tablets = ["0.0.0.0:1111", "0.0.0.0:2222", "0.0.0.0:3333"] + tablet2idx = {tablet: i + 1 for i, tablet in enumerate(tablets)} + # simple + t_info = { + "name": "TABLE_A", + "table_partition": [ + { + "pid": 0, + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": True, + # "offset": 0, + # "record_cnt": 0, + # "record_byte_size": 0, + # "tablet_has_partition": true, + # "diskused": 9025, + }, + { + "endpoint": tablets[1], + "is_leader": False, + }, + ], + # "term_offset": [{"term": 1, "offset": 0}], + # "record_cnt": 0, + # "record_byte_size": 0, + # "diskused": 9025, + } + ], + "tid": 0, + "partition_num": 1, + "replica_num": 2, + "db": "DB_A", + } + + replicas = { + 0: { + 0: { + tablets[0]: { + "tid": 0, # actually not used in show_table_info + "pid": 0, # not used in show_table_info + "offset": 5, # check offset on tablet, not ns + "mode": "kTableLeader", + "state": "kTableNormal", + # "is_expire": True, + # "record_cnt": 1, + # "idx_cnt": 1, + # "ts_idx_status": [ + # {"idx_name": "id", "seg_cnts": [0, 0, 0, 0, 0, 0, 1, 0]} + # ], + "name": "Foo", + # "record_byte_size": 127, + # "record_idx_byte_size": 177, + # "record_pk_cnt": 1, + # "compress_type": "kNoCompress", + # "skiplist_height": 1, + # "diskused": 10074, + # "storage_mode": "kMemory", + "tablet": tablets[0], + }, + tablets[1]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[1], + }, + } + } + } + + show_table_info(t_info, replicas, tablet2idx) + + print("healthy ns meta, but replicas on tablet are all follower") + t_info = { + "name": "TABLE_A", + "table_partition": [ + { + "pid": 0, + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": True, + }, + { + "endpoint": tablets[1], + "is_leader": False, + }, + { + "endpoint": tablets[2], + "is_leader": False, + }, + ], + } + ], + "tid": 0, + "partition_num": 1, + "replica_num": 3, + "db": "DB_A", + } + replicas = { + 0: { + 0: { + tablets[0]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[0], + }, + tablets[1]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[1], + }, + tablets[2]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[2], + }, + } + } + } + show_table_info(t_info, replicas, tablet2idx) + + print("ns meta all followers, no leader") + t_info = { + "name": "TABLE_A", + "table_partition": [ + { + "pid": 0, + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": False, + }, + { + "endpoint": tablets[1], + "is_leader": False, + }, + { + "endpoint": tablets[2], + "is_leader": False, + }, + ], + } + ], + "tid": 0, + "partition_num": 1, + "replica_num": 3, + "db": "DB_A", + } + replicas = { + 0: { + 0: { + tablets[0]: { + "mode": "kTableLeader", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[0], + }, + tablets[1]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[1], + }, + tablets[2]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[2], + }, + } + } + } + show_table_info(t_info, replicas, tablet2idx) + + print("no corresponding replica on tablet server") + t_info = { + "name": "TABLE_A", + "table_partition": [ + { + "pid": 0, + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": True, + }, + { + "endpoint": tablets[1], + "is_leader": False, + }, + { + "endpoint": tablets[2], + "is_leader": False, + }, + ], + } + ], + "tid": 0, + "partition_num": 1, + "replica_num": 3, + "db": "DB_A", + } + replicas = { + 0: { + 0: { + tablets[1]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[1], + }, + tablets[2]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[2], + }, + } + } + } + show_table_info(t_info, replicas, tablet2idx) + + print("meta match, but state is not normal") + t_info = { + "name": "TABLE_A", + "table_partition": [ + { + "pid": 0, + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": True, + }, + { + "endpoint": tablets[1], + "is_leader": False, + }, + { + "endpoint": tablets[2], + "is_leader": False, + }, + ], + }, + { + "pid": 1, + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": True, + }, + { + "endpoint": tablets[1], + "is_leader": False, + }, + { + "endpoint": tablets[2], + "is_leader": False, + }, + ], + }, + ], + "tid": 0, + "partition_num": 2, + "replica_num": 3, + "db": "DB_A", + } + replicas = { + 0: { + 0: { + tablets[0]: { + "mode": "kTableFollower", + "state": "kTableLoading", + "offset": 0, + "tablet": tablets[0], + }, + tablets[1]: { + "mode": "kTableFollower", + "state": "kMakingSnapshot", + "offset": 0, + "tablet": tablets[1], + }, + tablets[2]: { + "mode": "kTableFollower", + "state": "kSnapshotPaused", + "offset": 0, + "tablet": tablets[2], + }, + }, + 1: { + tablets[1]: { + "mode": "kTableFollower", + "state": "kTableUndefined", + "offset": 0, + }, + tablets[2]: { + "mode": "kTableFollower", + "state": "kTableNormal", + "offset": 0, + }, + }, + } + } + show_table_info(t_info, replicas, tablet2idx) + + print("more partitions, display well") + partnum = 13 + meta_pattern = { + "partition_meta": [ + { + "endpoint": tablets[0], + "is_leader": True, + }, + ], + } + t_info = { + "name": "TABLE_A", + "table_partition": [], + "tid": 0, + "partition_num": partnum, + "replica_num": 1, + "db": "DB_A", + } + replicas = {0: {}} + + for i in range(partnum): + t_info["table_partition"].append({"pid": i, **meta_pattern}) + + for i in range(partnum): + replicas[0][i] = { + tablets[0]: { + "mode": "kTableLeader", + "state": "kTableNormal", + "offset": 0, + "tablet": tablets[0], + } + } + print(t_info, replicas) + show_table_info(t_info, replicas, tablet2idx) + + print("nocolor") + flags.FLAGS["nocolor"].parse(True) + show_table_info(t_info, replicas, tablet2idx) diff --git a/release/conf/apiserver.flags.template b/release/conf/apiserver.flags.template index 539bcc8e4a4..df0735c0fb5 100644 --- a/release/conf/apiserver.flags.template +++ b/release/conf/apiserver.flags.template @@ -3,8 +3,11 @@ --role=apiserver --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb +#--zk_cert=user:passwd --openmldb_log_dir=./logs --log_level=info #--thread_pool_size=16 +--bvar_max_dump_multi_dimension_metric_number=10 +--bvar_dump_interval=75 \ No newline at end of file diff --git a/release/conf/nameserver.flags.template b/release/conf/nameserver.flags.template index 445833d194a..b738503bfcc 100644 --- a/release/conf/nameserver.flags.template +++ b/release/conf/nameserver.flags.template @@ -3,6 +3,7 @@ --role=nameserver --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb +#--zk_cert=user:passwd --openmldb_log_dir=./logs --log_level=info diff --git a/release/conf/openmldb-env.sh b/release/conf/openmldb-env.sh index c86d84aebd1..5ba917c49e7 100644 --- a/release/conf/openmldb-env.sh +++ b/release/conf/openmldb-env.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash -export OPENMLDB_VERSION=0.8.3 +export OPENMLDB_VERSION=0.8.4 # openmldb mode: standalone / cluster -export OPENMLDB_MODE=${OPENMLDB_MODE:=standalone} +export OPENMLDB_MODE=${OPENMLDB_MODE:=cluster} # tablet port export OPENMLDB_TABLET_PORT=10921 # nameserver port diff --git a/release/conf/tablet.flags.template b/release/conf/tablet.flags.template index 3d126d74123..d5109a9abaf 100644 --- a/release/conf/tablet.flags.template +++ b/release/conf/tablet.flags.template @@ -6,6 +6,7 @@ --zk_cluster=127.0.0.1:2181 --zk_root_path=/openmldb +#--zk_cert=user:passwd # thread_pool_size建议和cpu核数一致 --thread_pool_size=24 @@ -98,3 +99,5 @@ # turn this option on to export openmldb metric status # --enable_status_service=false +--bvar_max_dump_multi_dimension_metric_number=10 +--bvar_dump_interval=75 diff --git a/src/apiserver/api_server_impl.cc b/src/apiserver/api_server_impl.cc index c24b76c40ce..cb13414798f 100644 --- a/src/apiserver/api_server_impl.cc +++ b/src/apiserver/api_server_impl.cc @@ -22,11 +22,18 @@ #include #include "apiserver/interface_provider.h" + +#include "absl/cleanup/cleanup.h" #include "brpc/server.h" +#include "butil/time.h" namespace openmldb { namespace apiserver { +APIServerImpl::APIServerImpl(const std::string& endpoint) + : md_recorder_("rpc_server_" + endpoint.substr(endpoint.find(":") + 1), "http_method", {"method"}), + provider_("rpc_server_" + endpoint.substr(endpoint.find(":") + 1)) {} + APIServerImpl::~APIServerImpl() = default; bool APIServerImpl::Init(const sdk::ClusterOptions& options) { @@ -72,7 +79,6 @@ void APIServerImpl::Process(google::protobuf::RpcController* cntl_base, const Ht google::protobuf::Closure* done) { brpc::ClosureGuard done_guard(done); auto* cntl = dynamic_cast(cntl_base); - // The unresolved path has no slashes at the beginning(guaranteed by brpc), it's not good for url parsing auto unresolved_path = "/" + cntl->http_request().unresolved_path(); auto method = cntl->http_request().method(); @@ -81,7 +87,6 @@ void APIServerImpl::Process(google::protobuf::RpcController* cntl_base, const Ht JsonWriter writer; provider_.handle(unresolved_path, method, req_body, writer); - cntl->response_attachment().append(writer.GetString()); } @@ -110,6 +115,12 @@ std::map mode_map{ void APIServerImpl::RegisterQuery() { provider_.post("/dbs/:db_name", [this](const InterfaceProvider::Params& param, const butil::IOBuf& req_body, JsonWriter& writer) { + auto start = absl::Now(); + absl::Cleanup method_latency = [this, start]() { + // TODO(hw): query should split into async/sync, online/offline? + absl::Duration time = absl::Now() - start; + *md_recorder_.get_stats({"query"}) << absl::ToInt64Microseconds(time); + }; auto resp = GeneralResp(); auto db_it = param.find("db_name"); if (db_it == param.end()) { @@ -153,14 +164,16 @@ void APIServerImpl::RegisterQuery() { } QueryResp query_resp; + // we set write_nan_and_inf_null here instead of create a new JsonWriter with flags, cuz JsonWriter is not a + // good impl for template flag + query_resp.write_nan_and_inf_null = req.write_nan_and_inf_null; query_resp.rs = rs; writer << query_resp; }); } -bool APIServerImpl::JsonArray2SQLRequestRow(const butil::rapidjson::Value& non_common_cols_v, - const butil::rapidjson::Value& common_cols_v, - std::shared_ptr row) { +absl::Status APIServerImpl::JsonArray2SQLRequestRow(const Value& non_common_cols_v, const Value& common_cols_v, + std::shared_ptr row) { auto sch = row->GetSchema(); // scan all strings to init the total string length @@ -186,23 +199,24 @@ bool APIServerImpl::JsonArray2SQLRequestRow(const butil::rapidjson::Value& non_c for (decltype(sch->GetColumnCnt()) i = 0; i < sch->GetColumnCnt(); ++i) { if (sch->IsConstant(i)) { if (!AppendJsonValue(common_cols_v[common_idx], sch->GetColumnType(i), sch->IsColumnNotNull(i), row)) { - return false; + return absl::InvalidArgumentError( + absl::StrCat("trans const ", sch->GetColumnName(i), "[", sch->GetColumnType(i), "] failed")); } ++common_idx; } else { if (!AppendJsonValue(non_common_cols_v[non_common_idx], sch->GetColumnType(i), sch->IsColumnNotNull(i), row)) { - return false; + return absl::InvalidArgumentError( + absl::StrCat("trans ", sch->GetColumnName(i), "[", sch->GetColumnType(i), "] failed")); } ++non_common_idx; } } - return true; + return absl::OkStatus(); } template -bool APIServerImpl::AppendJsonValue(const butil::rapidjson::Value& v, hybridse::sdk::DataType type, bool is_not_null, - T row) { +bool APIServerImpl::AppendJsonValue(const Value& v, hybridse::sdk::DataType type, bool is_not_null, T row) { // check if null if (v.IsNull()) { if (is_not_null) { @@ -237,13 +251,14 @@ bool APIServerImpl::AppendJsonValue(const butil::rapidjson::Value& v, hybridse:: return row->AppendInt64(v.GetInt64()); } case hybridse::sdk::kTypeFloat: { - if (!v.IsDouble()) { + if (!v.IsNumber()) { // relax check, int can get as double and support set float NaN&Inf return false; } - return row->AppendFloat(boost::lexical_cast(v.GetDouble())); + // IEEE 754 arithmetic allows cast nan/inf to float + return row->AppendFloat(v.GetFloat()); } case hybridse::sdk::kTypeDouble: { - if (!v.IsDouble()) { + if (!v.IsLosslessDouble()) { return false; } return row->AppendDouble(v.GetDouble()); @@ -281,9 +296,8 @@ bool APIServerImpl::AppendJsonValue(const butil::rapidjson::Value& v, hybridse:: } // common_cols_v is still an array, but non_common_cols_v is map, should find the value by the column name -bool APIServerImpl::JsonMap2SQLRequestRow(const butil::rapidjson::Value& non_common_cols_v, - const butil::rapidjson::Value& common_cols_v, - std::shared_ptr row) { +absl::Status APIServerImpl::JsonMap2SQLRequestRow(const Value& non_common_cols_v, const Value& common_cols_v, + std::shared_ptr row) { auto sch = row->GetSchema(); // scan all strings to init the total string length @@ -300,8 +314,7 @@ bool APIServerImpl::JsonMap2SQLRequestRow(const butil::rapidjson::Value& non_com if (sch->GetColumnType(i) == hybridse::sdk::kTypeString) { auto v = non_common_cols_v.FindMember(sch->GetColumnName(i).c_str()); if (v == non_common_cols_v.MemberEnd()) { - LOG(WARNING) << "can't find " << sch->GetColumnName(i); - return false; + return absl::InvalidArgumentError("can't find col " + sch->GetColumnName(i)); } str_len_sum += v->value.GetStringLength(); } @@ -313,23 +326,22 @@ bool APIServerImpl::JsonMap2SQLRequestRow(const butil::rapidjson::Value& non_com for (decltype(sch->GetColumnCnt()) i = 0; i < sch->GetColumnCnt(); ++i) { if (sch->IsConstant(i)) { if (!AppendJsonValue(common_cols_v[common_idx], sch->GetColumnType(i), sch->IsColumnNotNull(i), row)) { - LOG(WARNING) << "set " << sch->GetColumnName(i) << " failed"; - return false; + return absl::InvalidArgumentError( + absl::StrCat("trans const ", sch->GetColumnName(i), "[", sch->GetColumnType(i), "] failed")); } ++common_idx; } else { auto v = non_common_cols_v.FindMember(sch->GetColumnName(i).c_str()); if (v == non_common_cols_v.MemberEnd()) { - LOG(WARNING) << "can't find " << sch->GetColumnName(i); - return false; + return absl::InvalidArgumentError("can't find " + sch->GetColumnName(i)); } if (!AppendJsonValue(v->value, sch->GetColumnType(i), sch->IsColumnNotNull(i), row)) { - LOG(WARNING) << "set " << sch->GetColumnName(i) << " failed"; - return false; + return absl::InvalidArgumentError( + absl::StrCat("trans ", sch->GetColumnName(i), "[", sch->GetColumnType(i), "] failed")); } } } - return true; + return absl::OkStatus(); } void APIServerImpl::RegisterPut() { @@ -347,7 +359,7 @@ void APIServerImpl::RegisterPut() { // json2doc, then generate an insert sql Document document; - if (document.Parse(req_body.to_string().c_str()).HasParseError()) { + if (document.Parse(req_body.to_string().c_str()).HasParseError()) { DLOG(INFO) << "rapidjson doc parse [" << req_body.to_string().c_str() << "] failed, code " << document.GetParseError() << ", offset " << document.GetErrorOffset(); writer << resp.Set("Json parse failed, error code: " + std::to_string(document.GetParseError())); @@ -420,6 +432,11 @@ void APIServerImpl::RegisterExecSP() { void APIServerImpl::ExecuteProcedure(bool has_common_col, const InterfaceProvider::Params& param, const butil::IOBuf& req_body, JsonWriter& writer) { + auto start = absl::Now(); + absl::Cleanup method_latency = [this, start, has_common_col]() { + absl::Duration time = absl::Now() - start; + *md_recorder_.get_stats({has_common_col ? "sp" : "deployment"}) << absl::ToInt64Microseconds(time); + }; auto resp = GeneralResp(); auto db_it = param.find("db_name"); auto sp_it = param.find("sp_name"); @@ -430,13 +447,14 @@ void APIServerImpl::ExecuteProcedure(bool has_common_col, const InterfaceProvide auto db = db_it->second; auto sp = sp_it->second; + // TODO(hw): JsonReader can't set SQLRequestRow simply(cuz common_cols), use raw rapidjson here Document document; - if (document.Parse(req_body.to_string().c_str()).HasParseError()) { + if (document.Parse(req_body.to_string().c_str()).HasParseError()) { writer << resp.Set("Request body json parse failed"); return; } - butil::rapidjson::Value common_cols_v; + Value common_cols_v; if (has_common_col) { auto common_cols = document.FindMember("common_cols"); if (common_cols != document.MemberEnd()) { @@ -459,6 +477,12 @@ void APIServerImpl::ExecuteProcedure(bool has_common_col, const InterfaceProvide } const auto& rows = input->value; + auto write_nan_and_inf_null = false; + auto write_nan_and_inf_null_option = document.FindMember("write_nan_and_inf_null"); + if (write_nan_and_inf_null_option != document.MemberEnd() && write_nan_and_inf_null_option->value.IsBool()) { + write_nan_and_inf_null = write_nan_and_inf_null_option->value.GetBool(); + } + hybridse::sdk::Status status; // We need to use ShowProcedure to get input schema(should know which column is constant). // GetRequestRowByProcedure can't do that. @@ -498,13 +522,15 @@ void APIServerImpl::ExecuteProcedure(bool has_common_col, const InterfaceProvide writer << resp.Set("Invalid input data size in row " + std::to_string(i)); return; } - if (!JsonArray2SQLRequestRow(rows[i], common_cols_v, row)) { - writer << resp.Set("Translate to request row failed in array row " + std::to_string(i)); + if (auto st = JsonArray2SQLRequestRow(rows[i], common_cols_v, row); !st.ok()) { + writer << resp.Set("Translate to request row failed in array row " + std::to_string(i) + ", " + + st.ToString()); return; } } else if (rows[i].IsObject()) { - if (!JsonMap2SQLRequestRow(rows[i], common_cols_v, row)) { - writer << resp.Set("Translate to request row failed in map row " + std::to_string(i)); + if (auto st = JsonMap2SQLRequestRow(rows[i], common_cols_v, row); !st.ok()) { + writer << resp.Set("Translate to request row failed in map row " + std::to_string(i) + ", " + + st.ToString()); return; } } else { @@ -522,6 +548,7 @@ void APIServerImpl::ExecuteProcedure(bool has_common_col, const InterfaceProvide } ExecSPResp sp_resp; + sp_resp.write_nan_and_inf_null = write_nan_and_inf_null; // output schema in sp_info is needed for encoding data, so we need a bool in ExecSPResp to know whether to // print schema sp_resp.sp_info = sp_info; @@ -720,6 +747,9 @@ JsonReader& operator&(JsonReader& ar, QueryReq& s) { // NOLINT if (ar.HasMember("input")) { ar.Member("input") & s.parameter; } + if (ar.HasMember("write_nan_and_inf_null")) { + ar.Member("write_nan_and_inf_null") & s.write_nan_and_inf_null; + } return ar.EndObject(); } @@ -877,7 +907,18 @@ void WriteSchema(JsonWriter& ar, const std::string& name, const hybridse::sdk::S ar.EndArray(); } -void WriteValue(JsonWriter& ar, std::shared_ptr rs, int i) { // NOLINT +void WriteDoubleHelper(JsonWriter& ar, double d, bool write_nan_and_inf_null) { // NOLINT + if (write_nan_and_inf_null) { + if (std::isnan(d) || std::isinf(d)) { + ar.SetNull(); + return; + } + } + ar& d; +} + +void WriteValue(JsonWriter& ar, std::shared_ptr rs, int i, // NOLINT + bool write_nan_and_inf_null) { auto schema = rs->GetSchema(); if (rs->IsNULL(i)) { if (schema->IsColumnNotNull(i)) { @@ -908,13 +949,13 @@ void WriteValue(JsonWriter& ar, std::shared_ptr rs, in case hybridse::sdk::kTypeFloat: { float value = 0; rs->GetFloat(i, &value); - ar& static_cast(value); + WriteDoubleHelper(ar, value, write_nan_and_inf_null); break; } case hybridse::sdk::kTypeDouble: { double value = 0; rs->GetDouble(i, &value); - ar& value; + WriteDoubleHelper(ar, value, write_nan_and_inf_null); break; } case hybridse::sdk::kTypeString: { @@ -980,7 +1021,7 @@ JsonWriter& operator&(JsonWriter& ar, ExecSPResp& s) { // NOLINT for (decltype(schema.GetColumnCnt()) i = 0; i < schema.GetColumnCnt(); i++) { if (!schema.IsConstant(i)) { ar.Member(schema.GetColumnName(i).c_str()); - WriteValue(ar, rs, i); + WriteValue(ar, rs, i, s.write_nan_and_inf_null); } } ar.EndObject(); @@ -988,7 +1029,7 @@ JsonWriter& operator&(JsonWriter& ar, ExecSPResp& s) { // NOLINT ar.StartArray(); for (decltype(schema.GetColumnCnt()) i = 0; i < schema.GetColumnCnt(); i++) { if (!schema.IsConstant(i)) { - WriteValue(ar, rs, i); + WriteValue(ar, rs, i, s.write_nan_and_inf_null); } } ar.EndArray(); // one row end @@ -1004,7 +1045,7 @@ JsonWriter& operator&(JsonWriter& ar, ExecSPResp& s) { // NOLINT ar.StartArray(); for (decltype(schema.GetColumnCnt()) i = 0; i < schema.GetColumnCnt(); i++) { if (schema.IsConstant(i)) { - WriteValue(ar, rs, i); + WriteValue(ar, rs, i, s.write_nan_and_inf_null); } } ar.EndArray(); // one row end @@ -1255,7 +1296,7 @@ JsonWriter& operator&(JsonWriter& ar, QueryResp& s) { // NOLINT while (rs->Next()) { ar.StartArray(); for (decltype(schema.GetColumnCnt()) i = 0; i < schema.GetColumnCnt(); i++) { - WriteValue(ar, rs, i); + WriteValue(ar, rs, i, s.write_nan_and_inf_null); } ar.EndArray(); } diff --git a/src/apiserver/api_server_impl.h b/src/apiserver/api_server_impl.h index 9c936c9748e..ee41e34935b 100644 --- a/src/apiserver/api_server_impl.h +++ b/src/apiserver/api_server_impl.h @@ -24,19 +24,23 @@ #include #include +#include "absl/status/status.h" #include "apiserver/interface_provider.h" #include "apiserver/json_helper.h" -#include "json2pb/rapidjson.h" // rapidjson's DOM-style API +#include "rapidjson/document.h" // raw rapidjson 1.1.0, not in butil #include "proto/api_server.pb.h" #include "sdk/sql_cluster_router.h" #include "sdk/sql_request_row.h" +#include "absl/status/status.h" +#include "bvar/bvar.h" +#include "bvar/multi_dimension.h" // latency recorder + namespace openmldb { namespace apiserver { -using butil::rapidjson::Document; -using butil::rapidjson::StringBuffer; -using butil::rapidjson::Writer; +using rapidjson::Document; +using rapidjson::Value; // APIServer is a service for brpc::Server. The entire implement is `StartAPIServer()` in src/cmd/openmldb.cc // Every request is handled by `Process()`, we will choose the right method of the request by `InterfaceProvider`. @@ -45,7 +49,7 @@ using butil::rapidjson::Writer; // Both input and output are json data. We use rapidjson to handle it. class APIServerImpl : public APIServer { public: - APIServerImpl() = default; + explicit APIServerImpl(const std::string& endpoint); ~APIServerImpl() override; bool Init(const sdk::ClusterOptions& options); bool Init(::openmldb::sdk::DBSDK* cluster); @@ -69,26 +73,32 @@ class APIServerImpl : public APIServer { void ExecuteProcedure(bool has_common_col, const InterfaceProvider::Params& param, const butil::IOBuf& req_body, JsonWriter& writer); // NOLINT - static bool JsonArray2SQLRequestRow(const butil::rapidjson::Value& non_common_cols_v, - const butil::rapidjson::Value& common_cols_v, - std::shared_ptr row); - static bool JsonMap2SQLRequestRow(const butil::rapidjson::Value& non_common_cols_v, - const butil::rapidjson::Value& common_cols_v, - std::shared_ptr row); + static absl::Status JsonArray2SQLRequestRow(const Value& non_common_cols_v, + const Value& common_cols_v, + std::shared_ptr row); + static absl::Status JsonMap2SQLRequestRow(const Value& non_common_cols_v, + const Value& common_cols_v, + std::shared_ptr row); template - static bool AppendJsonValue(const butil::rapidjson::Value& v, hybridse::sdk::DataType type, bool is_not_null, + static bool AppendJsonValue(const Value& v, hybridse::sdk::DataType type, bool is_not_null, T row); // may get segmentation fault when throw boost::bad_lexical_cast, so we use std::from_chars template static bool FromString(const std::string& s, T& value) { // NOLINT - auto res = std::from_chars(s.data(), s.data() + s.size(), value); - return res.ec == std::errc() && (res.ptr - s.data() == s.size()); + if (auto res = std::from_chars(s.data(), s.data() + s.size(), value); res.ec == std::errc()) { + auto len = res.ptr - s.data(); + return len >= 0 ? (uint64_t)len == s.size() : false; + } else { + return false; + } } private: - std::shared_ptr sql_router_; + bvar::MultiDimension md_recorder_; InterfaceProvider provider_; + + std::shared_ptr sql_router_; // cluster_sdk_ is not owned by this class. ::openmldb::sdk::DBSDK* cluster_sdk_ = nullptr; }; @@ -98,6 +108,7 @@ struct QueryReq { int timeout = -1; // only for offline jobs std::string sql; std::shared_ptr parameter; + bool write_nan_and_inf_null = false; }; JsonReader& operator&(JsonReader& ar, QueryReq& s); // NOLINT @@ -112,12 +123,13 @@ struct ExecSPResp { bool need_schema = false; bool json_result = false; std::shared_ptr rs; + bool write_nan_and_inf_null = false; }; void WriteSchema(JsonWriter& ar, const std::string& name, const hybridse::sdk::Schema& schema, // NOLINT bool only_const); -void WriteValue(JsonWriter& ar, std::shared_ptr rs, int i); // NOLINT +void WriteValue(JsonWriter& ar, std::shared_ptr rs, int i, bool write_nan_and_inf_null); // NOLINT // ExecSPResp reading is unsupported now, cuz we decode ResultSet with Schema here, it's irreversible JsonWriter& operator&(JsonWriter& ar, ExecSPResp& s); // NOLINT @@ -147,6 +159,8 @@ struct QueryResp { int code = 0; std::string msg = "ok"; std::shared_ptr rs; + // option, won't write to result + bool write_nan_and_inf_null = false; }; JsonWriter& operator&(JsonWriter& ar, QueryResp& s); // NOLINT diff --git a/src/apiserver/api_server_test.cc b/src/apiserver/api_server_test.cc index d14037ae506..6abe8ddd051 100644 --- a/src/apiserver/api_server_test.cc +++ b/src/apiserver/api_server_test.cc @@ -24,7 +24,8 @@ #include "butil/logging.h" #include "gflags/gflags.h" #include "gtest/gtest.h" -#include "json2pb/rapidjson.h" +#include "rapidjson/error/en.h" +#include "rapidjson/rapidjson.h" #include "sdk/mini_cluster.h" namespace openmldb::apiserver { @@ -49,7 +50,7 @@ class APIServerTestEnv : public testing::Environment { // Owned by queue_svc cluster_sdk = new ::openmldb::sdk::ClusterSDK(cluster_options); ASSERT_TRUE(cluster_sdk->Init()) << "Fail to connect to db"; - queue_svc = std::make_shared(); + queue_svc = std::make_shared("127.0.0.1:8010"); // fake endpoint for metrics ASSERT_TRUE(queue_svc->Init(cluster_sdk)); sdk::SQLRouterOptions sql_opt; @@ -117,7 +118,8 @@ class APIServerTest : public ::testing::Test { }; TEST_F(APIServerTest, jsonFormat) { - butil::rapidjson::Document document; + // test raw document + rapidjson::Document document; // Check the format of put request if (document @@ -127,7 +129,7 @@ TEST_F(APIServerTest, jsonFormat) { ] })") .HasParseError()) { - ASSERT_TRUE(false) << "json parse failed with code " << document.GetParseError(); + ASSERT_TRUE(false) << "json parse failed: " << rapidjson::GetParseError_En(document.GetParseError()); } hybridse::sdk::Status status; @@ -136,13 +138,102 @@ TEST_F(APIServerTest, jsonFormat) { ASSERT_EQ(1, value.Size()); const auto& arr = value[0]; ASSERT_EQ(7, arr.Size()); - ASSERT_EQ(butil::rapidjson::kStringType, arr[0].GetType()); - ASSERT_EQ(butil::rapidjson::kNumberType, arr[1].GetType()); - ASSERT_EQ(butil::rapidjson::kNumberType, arr[2].GetType()); - ASSERT_EQ(butil::rapidjson::kStringType, arr[3].GetType()); - ASSERT_EQ(butil::rapidjson::kNumberType, arr[4].GetType()); - ASSERT_EQ(butil::rapidjson::kTrueType, arr[5].GetType()); - ASSERT_EQ(butil::rapidjson::kNullType, arr[6].GetType()); + ASSERT_EQ(rapidjson::kStringType, arr[0].GetType()); + ASSERT_EQ(rapidjson::kNumberType, arr[1].GetType()); + ASSERT_EQ(rapidjson::kNumberType, arr[2].GetType()); + ASSERT_EQ(rapidjson::kStringType, arr[3].GetType()); + ASSERT_EQ(rapidjson::kNumberType, arr[4].GetType()); + ASSERT_EQ(rapidjson::kTrueType, arr[5].GetType()); + ASSERT_EQ(rapidjson::kNullType, arr[6].GetType()); + + // raw document with default flags can't parse unquoted nan&inf + ASSERT_TRUE(document.Parse("[NaN,Infinity]").HasParseError()); + ASSERT_EQ(rapidjson::kParseErrorValueInvalid, document.GetParseError()) << document.GetParseError(); + + // test json reader + // can read inf number to inf + { + JsonReader reader("1.797693134862316e308"); + ASSERT_TRUE(reader); + double d_res = -1.0; + reader >> d_res; + ASSERT_EQ(0x7ff0000000000000, *reinterpret_cast(&d_res)) + << std::hex << std::setprecision(16) << *reinterpret_cast(&d_res); + ASSERT_TRUE(std::isinf(d_res)); + } + + // read unquoted inf&nan, legal words + { + JsonReader reader("[NaN, Inf, -Inf, Infinity, -Infinity]"); + ASSERT_TRUE(reader); + double d_res = -1.0; + reader.StartArray(); + reader >> d_res; + ASSERT_TRUE(std::isnan(d_res)); + // nan hex + reader >> d_res; + ASSERT_TRUE(std::isinf(d_res)); + reader >> d_res; + ASSERT_TRUE(std::isinf(d_res)); + reader >> d_res; + ASSERT_TRUE(std::isinf(d_res)); + reader >> d_res; + ASSERT_TRUE(std::isinf(d_res)); + } + { + // float nan inf + // IEEE 754 arithmetic allows cast nan/inf to float, so GetFloat is fine + JsonReader reader("[NaN, Infinity, -Infinity]"); + ASSERT_TRUE(reader); + float f_res = -1.0; + reader.StartArray(); + reader >> f_res; + ASSERT_TRUE(std::isnan(f_res)); + reader >> f_res; + ASSERT_TRUE(std::isinf(f_res)); + reader >> f_res; + ASSERT_TRUE(std::isinf(f_res)); + // raw way for put and procedure(common cols) + f_res = -1.0; + rapidjson::Document document; + document.Parse("[NaN, Infinity, -Infinity]"); + document.StartArray(); + f_res = document[0].GetFloat(); + ASSERT_TRUE(std::isnan(f_res)); + f_res = document[1].GetFloat(); + ASSERT_TRUE(std::isinf(f_res)); + f_res = document[2].GetFloat(); + ASSERT_TRUE(std::isinf(f_res)); + } + { // illegal words + JsonReader reader("nan"); + ASSERT_FALSE(reader); + } + { // illegal words + JsonReader reader("+Inf"); + ASSERT_FALSE(reader); + } + { // string, not double + JsonReader reader("\"NaN\""); + ASSERT_TRUE(reader); + double d = -1.0; + reader >> d; + ASSERT_FALSE(reader); // get double failed + ASSERT_FLOAT_EQ(d, -1.0); // won't change + } + + // test json writer + JsonWriter writer; + // about double nan, inf + double nan = std::numeric_limits::quiet_NaN(); + double inf = std::numeric_limits::infinity(); + writer.StartArray(); + writer << nan; + writer << inf; + double ninf = -inf; + writer << ninf; + writer.EndArray(); + ASSERT_STREQ("[NaN,Infinity,-Infinity]", writer.GetString()); } TEST_F(APIServerTest, query) { @@ -168,7 +259,7 @@ TEST_F(APIServerTest, query) { LOG(INFO) << "exec query resp:\n" << cntl.response_attachment().to_string(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << cntl.response_attachment().to_string(); @@ -229,7 +320,7 @@ TEST_F(APIServerTest, parameterizedQuery) { LOG(INFO) << "exec query resp:\n" << cntl.response_attachment().to_string(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << cntl.response_attachment().to_string(); @@ -274,7 +365,7 @@ TEST_F(APIServerTest, parameterizedQuery) { LOG(INFO) << "exec query resp:\n" << cntl.response_attachment().to_string(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << cntl.response_attachment().to_string(); @@ -587,7 +678,7 @@ TEST_F(APIServerTest, procedure) { ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); LOG(INFO) << "get sp resp: " << show_cntl.response_attachment(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -713,7 +804,7 @@ TEST_F(APIServerTest, testResultType) { ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); LOG(INFO) << "exec deployment resp:\n" << cntl.response_attachment().to_string(); - butil::rapidjson::Document document; + rapidjson::Document document; // check resp data if (document.Parse(cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() @@ -781,7 +872,7 @@ TEST_F(APIServerTest, no_common) { ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); LOG(INFO) << "get sp resp: " << show_cntl.response_attachment(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -868,7 +959,7 @@ TEST_F(APIServerTest, no_common_not_first_string) { ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); LOG(INFO) << "get sp resp: " << show_cntl.response_attachment(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -925,7 +1016,7 @@ TEST_F(APIServerTest, getDBs) { brpc::Controller show_cntl; // default is GET show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -957,7 +1048,7 @@ TEST_F(APIServerTest, getDBs) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -991,7 +1082,7 @@ TEST_F(APIServerTest, getTables) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs/" + db_name + "/tables"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -1022,7 +1113,7 @@ TEST_F(APIServerTest, getTables) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs/" + db_name + "/tables"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -1047,7 +1138,7 @@ TEST_F(APIServerTest, getTables) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs/db_not_exist/tables"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -1060,7 +1151,7 @@ TEST_F(APIServerTest, getTables) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs/" + db_name + "/tables/" + table; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -1076,7 +1167,7 @@ TEST_F(APIServerTest, getTables) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs/" + db_name + "/tables/not_exist"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -1089,7 +1180,7 @@ TEST_F(APIServerTest, getTables) { show_cntl.http_request().uri() = "http://127.0.0.1:8010/dbs/db_not_exist/tables/apple"; env->http_channel.CallMethod(NULL, &show_cntl, NULL, NULL, NULL); ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); - butil::rapidjson::Document document; + rapidjson::Document document; if (document.Parse(show_cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() << ", raw resp: " << show_cntl.response_attachment().to_string(); @@ -1145,7 +1236,8 @@ TEST_F(APIServerTest, jsonInput) { ASSERT_FALSE(show_cntl.Failed()) << show_cntl.ErrorText(); LOG(INFO) << "get sp resp: " << show_cntl.response_attachment(); - // call deployment in json style input(won't check if it's a sp or deployment) + // call sp in deployment api with json style input(won't check if it's a sp or deployment), so it'll have field + // `common_cols_data` { brpc::Controller cntl; cntl.http_request().set_method(brpc::HTTP_METHOD_POST); @@ -1158,7 +1250,7 @@ TEST_F(APIServerTest, jsonInput) { ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); LOG(INFO) << "exec deployment resp:\n" << cntl.response_attachment().to_string(); - butil::rapidjson::Document document; + rapidjson::Document document; // check resp data if (document.Parse(cntl.response_attachment().to_string().c_str()).HasParseError()) { ASSERT_TRUE(false) << "response parse failed with code " << document.GetParseError() diff --git a/src/apiserver/interface_provider.cc b/src/apiserver/interface_provider.cc index c4be99c4726..4a482b3aab0 100644 --- a/src/apiserver/interface_provider.cc +++ b/src/apiserver/interface_provider.cc @@ -25,6 +25,8 @@ #include #include "boost/algorithm/string/split.hpp" +#include "butil/time.h" +#include "bvar/bvar.h" #include "glog/logging.h" namespace openmldb { @@ -159,6 +161,8 @@ void InterfaceProvider::registerRequest(brpc::HttpMethod type, std::string const bool InterfaceProvider::handle(const std::string& path, const brpc::HttpMethod& method, const butil::IOBuf& req_body, JsonWriter& writer) { + butil::Timer tm; + tm.start(); auto err = GeneralResp(); Url url; @@ -190,6 +194,9 @@ bool InterfaceProvider::handle(const std::string& path, const brpc::HttpMethod& } auto params = extractParameters(url, request->url); + tm.stop(); + route_recorder_ << tm.u_elapsed(); + request->callback(params, req_body, writer); return true; } diff --git a/src/apiserver/interface_provider.h b/src/apiserver/interface_provider.h index e2ffe4661a4..8589b9229c5 100644 --- a/src/apiserver/interface_provider.h +++ b/src/apiserver/interface_provider.h @@ -32,6 +32,7 @@ #include "apiserver/json_helper.h" #include "brpc/http_method.h" // HttpMethod #include "butil/iobuf.h" // IOBuf +#include "bvar/bvar.h" // latency recorder #include "proto/api_server.pb.h" namespace openmldb { @@ -111,7 +112,7 @@ class ReducedUrlParser { class InterfaceProvider { public: - InterfaceProvider() = default; + explicit InterfaceProvider(const std::string& metric_prefix) : route_recorder_(metric_prefix, "http_route") {} InterfaceProvider& operator=(InterfaceProvider const&) = delete; InterfaceProvider(InterfaceProvider const&) = delete; @@ -160,6 +161,9 @@ class InterfaceProvider { void registerRequest(brpc::HttpMethod, const std::string& path, std::function&& callback); private: + // we only record route latency, method latency is recorded in callback(you may need record in parts), defined in + // api server impl + bvar::LatencyRecorder route_recorder_; std::unordered_map> requests_; }; diff --git a/src/apiserver/json_helper.cc b/src/apiserver/json_helper.cc index 163bd3454ba..ccf228c40cc 100644 --- a/src/apiserver/json_helper.cc +++ b/src/apiserver/json_helper.cc @@ -18,17 +18,9 @@ #include -#include "json2pb/rapidjson.h" // rapidjson's DOM-style API - namespace openmldb { namespace apiserver { -using butil::rapidjson::Document; -using butil::rapidjson::SizeType; -using butil::rapidjson::StringBuffer; -using butil::rapidjson::Value; -using butil::rapidjson::Writer; - struct JsonReaderStackItem { enum State { BeforeStart, //!< An object/array is in the stack but it is not yet called by StartObject()/StartArray(). @@ -52,7 +44,8 @@ typedef std::stack JsonReaderStack; JsonReader::JsonReader(const char* json) : document_(), stack_(), error_(false) { document_ = new Document; - DOCUMENT->Parse(json); + // only support unquoted NaN & Inf.., so quoted string won't be parsed wrong + DOCUMENT->Parse(json); if (DOCUMENT->HasParseError()) { error_ = true; } else { @@ -273,12 +266,17 @@ void JsonReader::Next() { //////////////////////////////////////////////////////////////////////////////// // JsonWriter // We use Writer instead of PrettyWriter for performance reasons -#define WRITER (reinterpret_cast*>(writer_)) +#define WRITER \ + (reinterpret_cast, rapidjson::UTF8<>, rapidjson::CrtAllocator, \ + rapidjson::kWriteNanAndInfFlag>*>(writer_)) #define STREAM (reinterpret_cast(stream_)) -JsonWriter::JsonWriter() { // : writer_(), stream_() +// it's ok to set nan/inf flag even if we don't use them when we write them to null +// if need template, try to use boost::mpl +JsonWriter::JsonWriter() : writer_(), stream_() { stream_ = new StringBuffer; - writer_ = new Writer(*STREAM); + writer_ = new Writer, rapidjson::UTF8<>, rapidjson::CrtAllocator, + rapidjson::kWriteNanAndInfFlag>(*STREAM); } JsonWriter::~JsonWriter() { @@ -325,22 +323,22 @@ JsonWriter& JsonWriter::operator&(const bool& b) { } JsonWriter& JsonWriter::operator&(const unsigned& u) { - WRITER->AddUint(u); + WRITER->Uint(u); return *this; } JsonWriter& JsonWriter::operator&(const int& i) { - WRITER->AddInt(i); + WRITER->Int(i); return *this; } JsonWriter& JsonWriter::operator&(const int64_t& i) { - WRITER->AddInt64(i); + WRITER->Int64(i); return *this; } JsonWriter& JsonWriter::operator&(uint64_t i) { - WRITER->AddUint64(i); + WRITER->Uint64(i); return *this; } diff --git a/src/apiserver/json_helper.h b/src/apiserver/json_helper.h index b3fdf5157b5..77d445a08fa 100644 --- a/src/apiserver/json_helper.h +++ b/src/apiserver/json_helper.h @@ -20,9 +20,18 @@ #include #include +#include "rapidjson/document.h" // rapidjson's DOM-style API +#include "rapidjson/writer.h" + namespace openmldb { namespace apiserver { +using rapidjson::Document; +using rapidjson::SizeType; +using rapidjson::StringBuffer; +using rapidjson::Value; +using rapidjson::Writer; + /** \class Archiver \brief Archiver concept @@ -46,6 +55,7 @@ class JsonReader { /** \param json A non-const source json string for in-situ parsing. \note in-situ means the source JSON string will be modified after parsing. + just pass document for template read flags */ explicit JsonReader(const char* json); @@ -80,10 +90,10 @@ class JsonReader { static const bool IsReader = true; static const bool IsWriter = !IsReader; - private: - JsonReader(const JsonReader&); - JsonReader& operator=(const JsonReader&); + JsonReader& operator=(const JsonReader&) = delete; + JsonReader(const JsonReader&) = delete; + private: // PIMPL void* document_; ///< DOM result of parsing. void* stack_; ///< Stack for iterating the DOM diff --git a/src/base/ddl_parser_test.cc b/src/base/ddl_parser_test.cc index 3439a694a15..6b6aaed90a0 100644 --- a/src/base/ddl_parser_test.cc +++ b/src/base/ddl_parser_test.cc @@ -385,18 +385,19 @@ TEST_F(DDLParserTest, joinExtract) { LOG(INFO) << "after add index:\n" << DDLParser::PhysicalPlan(sql, db); } - { - ClearAllIndex(); - // left join - auto sql = "SELECT t1.col1, t1.col2, t2.col1, t2.col2 FROM t1 left join t2 on t1.col1 = t2.col2;"; - - auto index_map = ExtractIndexesWithSingleDB(sql, db); - // {t2[col_name: "col2" ttl { ttl_type: kLatestTime lat_ttl: 1 }, ]} - CheckEqual(index_map, {{"t2", {"col2;;lat,0,1"}}}); - // the added index only has key, no ts - AddIndexToDB(index_map, &db); - LOG(INFO) << "after add index:\n" << DDLParser::PhysicalPlan(sql, db); - } + // TODO: fix later + // { + // ClearAllIndex(); + // // left join + // auto sql = "SELECT t1.col1, t1.col2, t2.col1, t2.col2 FROM t1 left join t2 on t1.col1 = t2.col2;"; + // + // auto index_map = ExtractIndexesWithSingleDB(sql, db); + // // {t2[col_name: "col2" ttl { ttl_type: kLatestTime lat_ttl: 1 }, ]} + // CheckEqual(index_map, {{"t2", {"col2;;lat,0,1"}}}); + // // the added index only has key, no ts + // AddIndexToDB(index_map, &db); + // LOG(INFO) << "after add index:\n" << DDLParser::PhysicalPlan(sql, db); + // } } TEST_F(DDLParserTest, complexJoin) { @@ -418,26 +419,26 @@ TEST_F(DDLParserTest, complexJoin) { LOG(INFO) << "after add index:\n" << DDLParser::PhysicalPlan(sql, db); } - { - ClearAllIndex(); - // no simple equal condition, won't extract index - auto sql = - "SELECT t1.col1, t1.col2, t2.col1, t2.col2 FROM t1 left join t2 on timestamp(int64(t1.col6)) = " - "timestamp(int64(t2.col6));"; - auto index_map = ExtractIndexesWithSingleDB(sql, db); - ASSERT_TRUE(index_map.empty()); - // must have a simple equal condition - sql = - "SELECT t1.col1, t1.col2, t2.col1, t2.col2 FROM t1 left join t2 on timestamp(int64(t1.col6)) = " - "timestamp(int64(t2.col6)) and t1.col1 = t2.col2;"; - index_map = ExtractIndexesWithSingleDB(sql, db); - // index is on t2.col2 {t2[col_name: "col2" ttl { ttl_type: kLatestTime lat_ttl: 1 }, ]} - CheckEqual(index_map, {{"t2", {"col2;;lat,0,1"}}}); - - // the added index only has key, no ts - AddIndexToDB(index_map, &db); - LOG(INFO) << "after add index:\n" << DDLParser::PhysicalPlan(sql, db); - } + // { + // ClearAllIndex(); + // // no simple equal condition, won't extract index + // auto sql = + // "SELECT t1.col1, t1.col2, t2.col1, t2.col2 FROM t1 left join t2 on timestamp(int64(t1.col6)) = " + // "timestamp(int64(t2.col6));"; + // auto index_map = ExtractIndexesWithSingleDB(sql, db); + // ASSERT_TRUE(index_map.empty()); + // // must have a simple equal condition + // sql = + // "SELECT t1.col1, t1.col2, t2.col1, t2.col2 FROM t1 left join t2 on timestamp(int64(t1.col6)) = " + // "timestamp(int64(t2.col6)) and t1.col1 = t2.col2;"; + // index_map = ExtractIndexesWithSingleDB(sql, db); + // // index is on t2.col2 {t2[col_name: "col2" ttl { ttl_type: kLatestTime lat_ttl: 1 }, ]} + // CheckEqual(index_map, {{"t2", {"col2;;lat,0,1"}}}); + // + // // the added index only has key, no ts + // AddIndexToDB(index_map, &db); + // LOG(INFO) << "after add index:\n" << DDLParser::PhysicalPlan(sql, db); + // } } TEST_F(DDLParserTest, multiJoin) { diff --git a/src/base/hash.h b/src/base/hash.h index 6e98be06d7f..df6962d3c5a 100644 --- a/src/base/hash.h +++ b/src/base/hash.h @@ -104,8 +104,8 @@ static uint64_t MurmurHash64A(const void* key, int len, unsigned int seed) { return h; } -static inline int64_t hash64(const std::string& key) { - uint64_t raw_value = MurmurHash64A(key.c_str(), key.length(), 0xe17a1465); +static inline int64_t hash64(const void* ptr, int len) { + uint64_t raw_value = MurmurHash64A(ptr, len, 0xe17a1465); int64_t cur_value = (int64_t)raw_value; // convert to signed integer as same as java client if (cur_value < 0) { @@ -114,6 +114,10 @@ static inline int64_t hash64(const std::string& key) { return cur_value; } +static inline int64_t hash64(const std::string& key) { + return hash64(key.c_str(), key.length()); +} + } // namespace base } // namespace openmldb diff --git a/src/base/kv_iterator_test.cc b/src/base/kv_iterator_test.cc index 3c35d6ba472..11e4228c5b3 100644 --- a/src/base/kv_iterator_test.cc +++ b/src/base/kv_iterator_test.cc @@ -77,13 +77,12 @@ TEST_F(KvIteratorTest, Iterator) { TEST_F(KvIteratorTest, HasPK) { auto response = std::make_shared<::openmldb::api::TraverseResponse>(); - std::string* pairs = response->mutable_pairs(); - pairs->resize(52); - char* data = reinterpret_cast(&((*pairs)[0])); ::openmldb::storage::DataBlock* db1 = new ::openmldb::storage::DataBlock(1, "hello", 5); ::openmldb::storage::DataBlock* db2 = new ::openmldb::storage::DataBlock(1, "hell1", 5); - ::openmldb::codec::EncodeFull("test1", 9527, db1, data, 0); - ::openmldb::codec::EncodeFull("test2", 9528, db2, data, 26); + butil::IOBuf buf; + ::openmldb::codec::EncodeFull("test1", 9527, db1->data, db1->size, &buf); + ::openmldb::codec::EncodeFull("test2", 9528, db2->data, db2->size, &buf); + buf.copy_to(response->mutable_pairs()); TraverseKvIterator kv_it(response); ASSERT_TRUE(kv_it.Valid()); ASSERT_STREQ("test1", kv_it.GetPK().c_str()); @@ -100,19 +99,18 @@ TEST_F(KvIteratorTest, HasPK) { TEST_F(KvIteratorTest, NextPK) { auto response = std::make_shared<::openmldb::api::TraverseResponse>(); - std::string* pairs = response->mutable_pairs(); - pairs->resize(16*9 + 90); std::string value("hello"); - char* data = reinterpret_cast(&((*pairs)[0])); uint32_t offset = 0; + butil::IOBuf buf; for (int i = 0; i < 3; i++) { std::string pk = "test" + std::to_string(i); uint64_t ts = 9500; for (int j = 0; j < 3; j++) { - ::openmldb::codec::EncodeFull(pk, ts - j, value.data(), value.size(), data, offset); + ::openmldb::codec::EncodeFull(pk, ts - j, value.data(), value.size(), &buf); offset += 16 + 10; } } + buf.copy_to(response->mutable_pairs()); TraverseKvIterator kv_it(response); int count = 0; while (kv_it.Valid()) { diff --git a/src/base/status.h b/src/base/status.h index 4a4eb867724..5995138edd6 100644 --- a/src/base/status.h +++ b/src/base/status.h @@ -19,6 +19,8 @@ #include +#include "absl/strings/str_cat.h" + #include "base/slice.h" #include "version.h" // NOLINT @@ -93,6 +95,7 @@ enum ReturnCode { kExceedMaxMemory = 160, kInvalidArgs = 161, kCheckIndexFailed = 162, + kCatalogUpdateFailed = 163, kNameserverIsNotLeader = 300, kAutoFailoverIsEnabled = 301, kEndpointIsNotExist = 302, @@ -127,7 +130,10 @@ enum ReturnCode { kCheckParameterFailed = 331, kCreateProcedureFailedOnTablet = 332, kCreateFunctionFailedOnTablet = 333, - kOPAlreadyExists = 317, + kOPAlreadyExists = 334, + kOffsetMismatch = 335, + kGetTabletFailed = 336, + kTruncateTableFailed = 337, kReplicaClusterAliasDuplicate = 400, kConnectRelicaClusterZkFailed = 401, kNotSameReplicaName = 402, @@ -185,6 +191,7 @@ struct Status { inline bool OK() const { return code == ReturnCode::kOk; } inline const std::string& GetMsg() const { return msg; } inline int GetCode() const { return code; } + inline std::string ToString() const { return absl::StrCat("ReturnCode[", code, "]", msg); } int code; std::string msg; }; diff --git a/src/catalog/distribute_iterator.cc b/src/catalog/distribute_iterator.cc index e99431728d5..b82afbb81fd 100644 --- a/src/catalog/distribute_iterator.cc +++ b/src/catalog/distribute_iterator.cc @@ -175,20 +175,19 @@ const ::hybridse::codec::Row& FullTableIterator::GetValue() { } valid_value_ = true; + base::Slice slice_row; if (it_ && it_->Valid()) { - value_ = ::hybridse::codec::Row( - ::hybridse::base::RefCountedSlice::Create(it_->GetValue().data(), it_->GetValue().size())); - return value_; + slice_row = it_->GetValue(); } else { - auto slice_row = kv_it_->GetValue(); - size_t sz = slice_row.size(); - int8_t* copyed_row_data = reinterpret_cast(malloc(sz)); - memcpy(copyed_row_data, slice_row.data(), sz); - auto shared_slice = ::hybridse::base::RefCountedSlice::CreateManaged(copyed_row_data, sz); - buffered_slices_.push_back(shared_slice); - value_.Reset(shared_slice); - return value_; + slice_row = kv_it_->GetValue(); } + size_t sz = slice_row.size(); + int8_t* copyed_row_data = reinterpret_cast(malloc(sz)); + memcpy(copyed_row_data, slice_row.data(), sz); + auto shared_slice = ::hybridse::base::RefCountedSlice::CreateManaged(copyed_row_data, sz); + buffered_slices_.push_back(shared_slice); + value_.Reset(shared_slice); + return value_; } DistributeWindowIterator::DistributeWindowIterator(uint32_t tid, uint32_t pid_num, std::shared_ptr tables, @@ -424,7 +423,7 @@ const ::hybridse::codec::Row& RemoteWindowIterator::GetValue() { memcpy(copyed_row_data, slice_row.data(), sz); auto shared_slice = ::hybridse::base::RefCountedSlice::CreateManaged(copyed_row_data, sz); row_.Reset(shared_slice); - DLOG(INFO) << "get value pk " << pk_ << " ts_key " << kv_it_->GetKey() << " ts " << ts_; + LOG(INFO) << "get value pk " << pk_ << " ts_key " << kv_it_->GetKey() << " ts " << ts_; valid_value_ = true; return row_; } diff --git a/src/catalog/tablet_catalog.cc b/src/catalog/tablet_catalog.cc index a9e74ff7061..233077f32fb 100644 --- a/src/catalog/tablet_catalog.cc +++ b/src/catalog/tablet_catalog.cc @@ -213,7 +213,7 @@ void TabletTableHandler::AddTable(std::shared_ptr<::openmldb::storage::Table> ta do { old_tables = std::atomic_load_explicit(&tables_, std::memory_order_acquire); new_tables = std::make_shared(*old_tables); - new_tables->emplace(table->GetPid(), table); + new_tables->insert_or_assign(table->GetPid(), table); } while (!atomic_compare_exchange_weak(&tables_, &old_tables, new_tables)); } @@ -503,7 +503,7 @@ bool TabletCatalog::UpdateTableInfo(const ::openmldb::nameserver::TableInfo& tab return false; } db_it->second.emplace(table_name, handler); - LOG(INFO) << "add table " << table_name << "to db " << db_name << " tid " << table_info.tid(); + LOG(INFO) << "add table " << table_name << " to db " << db_name << " tid " << table_info.tid(); } if (bool updated = false; !handler->Update(table_info, client_manager_, &updated)) { return false; diff --git a/src/client/ns_client.cc b/src/client/ns_client.cc index eb37aa2719f..ebf2bca2416 100644 --- a/src/client/ns_client.cc +++ b/src/client/ns_client.cc @@ -221,17 +221,16 @@ base::Status NsClient::ShowOPStatus(const std::string& name, uint32_t pid, return {base::ReturnCode::kError, response->msg()}; } -bool NsClient::CancelOP(uint64_t op_id, std::string& msg) { +base::Status NsClient::CancelOP(uint64_t op_id) { ::openmldb::nameserver::CancelOPRequest request; ::openmldb::nameserver::GeneralResponse response; request.set_op_id(op_id); - bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::CancelOP, &request, &response, - FLAGS_request_timeout_ms, 1); - msg = response.msg(); - if (ok && response.code() == 0) { - return true; + auto st = client_.SendRequestSt(&::openmldb::nameserver::NameServer_Stub::CancelOP, &request, &response, + FLAGS_request_timeout_ms, 1); + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } bool NsClient::AddTableField(const std::string& table_name, const ::openmldb::common::ColumnDesc& column_desc, @@ -297,6 +296,19 @@ bool NsClient::DropTable(const std::string& db, const std::string& name, std::st return false; } +base::Status NsClient::TruncateTable(const std::string& db, const std::string& name) { + ::openmldb::nameserver::TruncateTableRequest request; + request.set_name(name); + request.set_db(db); + ::openmldb::nameserver::TruncateTableResponse response; + bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::TruncateTable, &request, &response, + FLAGS_request_timeout_ms, 1); + if (ok && response.code() == 0) { + return {}; + } + return {response.code(), response.msg()}; +} + bool NsClient::SyncTable(const std::string& name, const std::string& cluster_alias, uint32_t pid, std::string& msg) { ::openmldb::nameserver::SyncTableRequest request; request.set_name(name); @@ -329,10 +341,10 @@ bool NsClient::SetSdkEndpoint(const std::string& server_name, const std::string& return false; } -bool NsClient::AddReplica(const std::string& name, const std::set& pid_set, const std::string& endpoint, - std::string& msg) { +base::Status NsClient::AddReplica(const std::string& name, const std::set& pid_set, + const std::string& endpoint) { if (pid_set.empty()) { - return false; + return {base::ReturnCode::kError, "arg pid set is empty"}; } ::openmldb::nameserver::AddReplicaNSRequest request; ::openmldb::nameserver::GeneralResponse response; @@ -345,13 +357,12 @@ bool NsClient::AddReplica(const std::string& name, const std::set& pid request.add_pid_group(pid); } } - bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::AddReplicaNS, &request, &response, - FLAGS_request_timeout_ms, 1); - msg = response.msg(); - if (ok && response.code() == 0) { - return true; + auto st = client_.SendRequestSt(&::openmldb::nameserver::NameServer_Stub::AddReplicaNS, &request, &response, + FLAGS_request_timeout_ms, 1); + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } bool NsClient::AddReplicaNS(const std::string& name, const std::vector& endpoint_vec, uint32_t pid, @@ -380,10 +391,10 @@ bool NsClient::AddReplicaNS(const std::string& name, const std::vector& pid_set, const std::string& endpoint, - std::string& msg) { +base::Status NsClient::DelReplica(const std::string& name, const std::set& pid_set, + const std::string& endpoint) { if (pid_set.empty()) { - return false; + return {base::ReturnCode::kError, "arg pid set is empty"}; } ::openmldb::nameserver::DelReplicaNSRequest request; ::openmldb::nameserver::GeneralResponse response; @@ -396,13 +407,12 @@ bool NsClient::DelReplica(const std::string& name, const std::set& pid request.add_pid_group(pid); } } - bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::DelReplicaNS, &request, &response, - FLAGS_request_timeout_ms, 1); - msg = response.msg(); - if (ok && response.code() == 0) { - return true; + auto st = client_.SendRequestSt(&::openmldb::nameserver::NameServer_Stub::DelReplicaNS, &request, &response, + FLAGS_request_timeout_ms, 1); + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } bool NsClient::ConfSet(const std::string& key, const std::string& value, std::string& msg) { @@ -445,7 +455,7 @@ bool NsClient::ConfGet(const std::string& key, std::map& pid_set, - const std::string& des_endpoint, std::string& msg) { +base::Status NsClient::Migrate(const std::string& src_endpoint, const std::string& name, + const std::set& pid_set, const std::string& des_endpoint) { ::openmldb::nameserver::MigrateRequest request; ::openmldb::nameserver::GeneralResponse response; request.set_src_endpoint(src_endpoint); @@ -490,13 +499,12 @@ bool NsClient::Migrate(const std::string& src_endpoint, const std::string& name, for (auto pid : pid_set) { request.add_pid(pid); } - bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::Migrate, &request, &response, + auto st = client_.SendRequestSt(&::openmldb::nameserver::NameServer_Stub::Migrate, &request, &response, FLAGS_request_timeout_ms, 1); - msg = response.msg(); - if (ok && response.code() == 0) { - return true; + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } bool NsClient::RecoverEndpoint(const std::string& endpoint, bool need_restore, uint32_t concurrency, std::string& msg) { @@ -516,20 +524,19 @@ bool NsClient::RecoverEndpoint(const std::string& endpoint, bool need_restore, u return false; } -bool NsClient::RecoverTable(const std::string& name, uint32_t pid, const std::string& endpoint, std::string& msg) { +base::Status NsClient::RecoverTable(const std::string& name, uint32_t pid, const std::string& endpoint) { ::openmldb::nameserver::RecoverTableRequest request; ::openmldb::nameserver::GeneralResponse response; request.set_name(name); request.set_pid(pid); request.set_endpoint(endpoint); request.set_db(GetDb()); - bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::RecoverTable, &request, &response, + auto st = client_.SendRequestSt(&::openmldb::nameserver::NameServer_Stub::RecoverTable, &request, &response, FLAGS_request_timeout_ms, 1); - msg = response.msg(); - if (ok && response.code() == 0) { - return true; + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } bool NsClient::ConnectZK(std::string& msg) { @@ -590,8 +597,8 @@ bool NsClient::GetTablePartition(const std::string& name, uint32_t pid, return false; } -bool NsClient::UpdateTableAliveStatus(const std::string& endpoint, std::string& name, uint32_t pid, bool is_alive, - std::string& msg) { +base::Status NsClient::UpdateTableAliveStatus(const std::string& endpoint, const std::string& name, uint32_t pid, + bool is_alive) { ::openmldb::nameserver::UpdateTableAliveRequest request; ::openmldb::nameserver::GeneralResponse response; request.set_endpoint(endpoint); @@ -601,21 +608,29 @@ bool NsClient::UpdateTableAliveStatus(const std::string& endpoint, std::string& if (pid < UINT32_MAX) { request.set_pid(pid); } - bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::UpdateTableAliveStatus, &request, &response, - FLAGS_request_timeout_ms, 1); - msg = response.msg(); - if (ok && response.code() == 0) { - return true; + auto st = client_.SendRequestSt(&::openmldb::nameserver::NameServer_Stub::UpdateTableAliveStatus, &request, + &response, FLAGS_request_timeout_ms, 1); + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } bool NsClient::UpdateTTL(const std::string& name, const ::openmldb::type::TTLType& type, uint64_t abs_ttl, uint64_t lat_ttl, const std::string& index_name, std::string& msg) { + return UpdateTTL(GetDb(), name, type, abs_ttl, lat_ttl, index_name, msg); +} + +bool NsClient::UpdateTTL(const std::string& db, const std::string& name, const ::openmldb::type::TTLType& type, + uint64_t abs_ttl, uint64_t lat_ttl, const std::string& index_name, std::string& msg) { ::openmldb::nameserver::UpdateTTLRequest request; ::openmldb::nameserver::UpdateTTLResponse response; request.set_name(name); - request.set_db(GetDb()); + if (db.empty()) { + request.set_db(GetDb()); + } else { + request.set_db(db); + } ::openmldb::common::TTLSt* ttl_desc = request.mutable_ttl_desc(); ttl_desc->set_ttl_type(type); ttl_desc->set_abs_ttl(abs_ttl); @@ -623,7 +638,6 @@ bool NsClient::UpdateTTL(const std::string& name, const ::openmldb::type::TTLTyp if (!index_name.empty()) { request.set_index_name(index_name); } - request.set_db(GetDb()); bool ok = client_.SendRequest(&::openmldb::nameserver::NameServer_Stub::UpdateTTL, &request, &response, FLAGS_request_timeout_ms, 1); msg = response.msg(); diff --git a/src/client/ns_client.h b/src/client/ns_client.h index f069ccce2d3..eb26aca55d6 100644 --- a/src/client/ns_client.h +++ b/src/client/ns_client.h @@ -94,12 +94,11 @@ class NsClient : public Client { bool MakeSnapshot(const std::string& name, const std::string& db, uint32_t pid, uint64_t end_offset, std::string& msg); // NOLINT - base::Status ShowOPStatus(const std::string& name, uint32_t pid, - nameserver::ShowOPStatusResponse* response); + base::Status ShowOPStatus(const std::string& name, uint32_t pid, nameserver::ShowOPStatusResponse* response); base::Status ShowOPStatus(uint64_t op_id, ::openmldb::nameserver::ShowOPStatusResponse* response); - bool CancelOP(uint64_t op_id, std::string& msg); // NOLINT + base::Status CancelOP(uint64_t op_id); bool AddTableField(const std::string& table_name, const ::openmldb::common::ColumnDesc& column_desc, std::string& msg); // NOLINT @@ -112,6 +111,8 @@ class NsClient : public Client { bool DropTable(const std::string& db, const std::string& name, std::string& msg); // NOLINT + base::Status TruncateTable(const std::string& db, const std::string& name); + bool SyncTable(const std::string& name, const std::string& cluster_alias, uint32_t pid, std::string& msg); // NOLINT @@ -144,14 +145,12 @@ class NsClient : public Client { const ::openmldb::nameserver::ZoneInfo& zone_info, std::string& msg); // NOLINT - bool AddReplica(const std::string& name, const std::set& pid_set, const std::string& endpoint, - std::string& msg); // NOLINT + base::Status AddReplica(const std::string& name, const std::set& pid_set, const std::string& endpoint); bool AddReplicaNS(const std::string& name, const std::vector& endpoint_vec, uint32_t pid, const ::openmldb::nameserver::ZoneInfo& zone_info, const ::openmldb::api::TaskInfo& task_info); - bool DelReplica(const std::string& name, const std::set& pid_set, const std::string& endpoint, - std::string& msg); // NOLINT + base::Status DelReplica(const std::string& name, const std::set& pid_set, const std::string& endpoint); bool ConfSet(const std::string& key, const std::string& value, std::string& msg); // NOLINT @@ -159,20 +158,19 @@ class NsClient : public Client { bool ConfGet(const std::string& key, std::map& conf_map, // NOLINT std::string& msg); // NOLINT - bool ChangeLeader(const std::string& name, uint32_t pid, - std::string& candidate_leader, // NOLINT - std::string& msg); // NOLINT + base::Status ChangeLeader(const std::string& name, uint32_t pid, + std::string& candidate_leader); // NOLINT bool OfflineEndpoint(const std::string& endpoint, uint32_t concurrency, std::string& msg); // NOLINT - bool Migrate(const std::string& src_endpoint, const std::string& name, const std::set& pid_set, - const std::string& des_endpoint, std::string& msg); // NOLINT + base::Status Migrate(const std::string& src_endpoint, const std::string& name, const std::set& pid_set, + const std::string& des_endpoint); bool RecoverEndpoint(const std::string& endpoint, bool need_restore, uint32_t concurrency, std::string& msg); // NOLINT - bool RecoverTable(const std::string& name, uint32_t pid, const std::string& endpoint, std::string& msg); // NOLINT + base::Status RecoverTable(const std::string& name, uint32_t pid, const std::string& endpoint); bool ConnectZK(std::string& msg); // NOLINT @@ -185,14 +183,15 @@ class NsClient : public Client { ::openmldb::nameserver::TablePartition& table_partition, // NOLINT std::string& msg); // NOLINT - bool UpdateTableAliveStatus(const std::string& endpoint, - std::string& name, // NOLINT - uint32_t pid, bool is_alive, - std::string& msg); // NOLINT + base::Status UpdateTableAliveStatus(const std::string& endpoint, const std::string& name, uint32_t pid, + bool is_alive); bool UpdateTTL(const std::string& name, const ::openmldb::type::TTLType& type, uint64_t abs_ttl, uint64_t lat_ttl, const std::string& ts_name, std::string& msg); // NOLINT + bool UpdateTTL(const std::string& db, const std::string& name, const ::openmldb::type::TTLType& type, + uint64_t abs_ttl, uint64_t lat_ttl, const std::string& ts_name, std::string& msg); // NOLINT + bool AddReplicaClusterByNs(const std::string& alias, const std::string& name, uint64_t term, std::string& msg); // NOLINT diff --git a/src/client/tablet_client.cc b/src/client/tablet_client.cc index 9357b23e29a..878d2a5f3cc 100644 --- a/src/client/tablet_client.cc +++ b/src/client/tablet_client.cc @@ -153,6 +153,20 @@ bool TabletClient::SQLBatchRequestQuery(const std::string& db, const std::string return true; } +base::Status TabletClient::TruncateTable(uint32_t tid, uint32_t pid) { + ::openmldb::api::TruncateTableRequest request; + ::openmldb::api::TruncateTableResponse response; + request.set_tid(tid); + request.set_pid(pid); + if (!client_.SendRequest(&::openmldb::api::TabletServer_Stub::TruncateTable, &request, &response, + FLAGS_request_timeout_ms, 1)) { + return {base::ReturnCode::kRPCError, "send request failed!"}; + } else if (response.code() == 0) { + return {}; + } + return {response.code(), response.msg()}; +} + base::Status TabletClient::CreateTable(const ::openmldb::api::TableMeta& table_meta) { ::openmldb::api::CreateTableRequest request; ::openmldb::api::TableMeta* table_meta_ptr = request.mutable_table_meta(); @@ -189,16 +203,23 @@ bool TabletClient::UpdateTableMetaForAddField(uint32_t tid, const std::vector>& dimensions) { - ::openmldb::api::PutRequest request; - request.set_time(time); - request.set_value(value); - request.set_tid(tid); - request.set_pid(pid); + ::google::protobuf::RepeatedPtrField<::openmldb::api::Dimension> pb_dimensions; for (size_t i = 0; i < dimensions.size(); i++) { - ::openmldb::api::Dimension* d = request.add_dimensions(); + ::openmldb::api::Dimension* d = pb_dimensions.Add(); d->set_key(dimensions[i].first); d->set_idx(dimensions[i].second); } + return Put(tid, pid, time, base::Slice(value), &pb_dimensions); +} + +bool TabletClient::Put(uint32_t tid, uint32_t pid, uint64_t time, const base::Slice& value, + ::google::protobuf::RepeatedPtrField<::openmldb::api::Dimension>* dimensions) { + ::openmldb::api::PutRequest request; + request.set_time(time); + request.set_value(value.data(), value.size()); + request.set_tid(tid); + request.set_pid(pid); + request.mutable_dimensions()->Swap(dimensions); ::openmldb::api::PutResponse response; bool ok = client_.SendRequest(&::openmldb::api::TabletServer_Stub::Put, &request, &response, FLAGS_request_timeout_ms, 1); @@ -314,12 +335,13 @@ bool TabletClient::SendSnapshot(uint32_t tid, uint32_t remote_tid, uint32_t pid, return false; } -bool TabletClient::LoadTable(const std::string& name, uint32_t id, uint32_t pid, uint64_t ttl, uint32_t seg_cnt) { +base::Status TabletClient::LoadTable(const std::string& name, uint32_t id, uint32_t pid, uint64_t ttl, + uint32_t seg_cnt) { return LoadTable(name, id, pid, ttl, false, seg_cnt); } -bool TabletClient::LoadTable(const std::string& name, uint32_t tid, uint32_t pid, uint64_t ttl, bool leader, - uint32_t seg_cnt, std::shared_ptr task_info) { +base::Status TabletClient::LoadTable(const std::string& name, uint32_t tid, uint32_t pid, uint64_t ttl, bool leader, + uint32_t seg_cnt, std::shared_ptr task_info) { ::openmldb::api::TableMeta table_meta; table_meta.set_name(name); table_meta.set_tid(tid); @@ -330,10 +352,11 @@ bool TabletClient::LoadTable(const std::string& name, uint32_t tid, uint32_t pid } else { table_meta.set_mode(::openmldb::api::TableMode::kTableFollower); } - return LoadTable(table_meta, task_info); + return LoadTableInternal(table_meta, task_info); } -bool TabletClient::LoadTable(const ::openmldb::api::TableMeta& table_meta, std::shared_ptr task_info) { +base::Status TabletClient::LoadTableInternal(const ::openmldb::api::TableMeta& table_meta, + std::shared_ptr task_info) { ::openmldb::api::LoadTableRequest request; ::openmldb::api::TableMeta* cur_table_meta = request.mutable_table_meta(); cur_table_meta->CopyFrom(table_meta); @@ -341,28 +364,21 @@ bool TabletClient::LoadTable(const ::openmldb::api::TableMeta& table_meta, std:: request.mutable_task_info()->CopyFrom(*task_info); } ::openmldb::api::GeneralResponse response; - bool ok = client_.SendRequest(&::openmldb::api::TabletServer_Stub::LoadTable, &request, &response, - FLAGS_request_timeout_ms, 1); - if (ok && response.code() == 0) { - return true; + auto st = client_.SendRequestSt(&::openmldb::api::TabletServer_Stub::LoadTable, &request, &response, + FLAGS_request_timeout_ms, 1); + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } -bool TabletClient::LoadTable(uint32_t tid, uint32_t pid, std::string* msg) { - ::openmldb::api::LoadTableRequest request; - ::openmldb::api::TableMeta* table_meta = request.mutable_table_meta(); - table_meta->set_tid(tid); - table_meta->set_pid(pid); - table_meta->set_mode(::openmldb::api::TableMode::kTableLeader); - ::openmldb::api::GeneralResponse response; - bool ok = client_.SendRequest(&::openmldb::api::TabletServer_Stub::LoadTable, &request, &response, - FLAGS_request_timeout_ms, 1); - msg->swap(*response.mutable_msg()); - if (ok && response.code() == 0) { - return true; +bool TabletClient::LoadTable(const ::openmldb::api::TableMeta& table_meta, std::shared_ptr task_info) { + auto st = LoadTableInternal(table_meta, task_info); + // can't return msg, log here + if (!st.OK()) { + LOG(WARNING) << st.ToString(); } - return false; + return st.OK(); } bool TabletClient::ChangeRole(uint32_t tid, uint32_t pid, bool leader, uint64_t term) { @@ -492,37 +508,36 @@ bool TabletClient::GetManifest(uint32_t tid, uint32_t pid, ::openmldb::common::S return true; } -bool TabletClient::GetTableStatus(::openmldb::api::GetTableStatusResponse& response) { +base::Status TabletClient::GetTableStatus(::openmldb::api::GetTableStatusResponse& response) { ::openmldb::api::GetTableStatusRequest request; - bool ret = client_.SendRequest(&::openmldb::api::TabletServer_Stub::GetTableStatus, &request, &response, + auto st = client_.SendRequestSt(&::openmldb::api::TabletServer_Stub::GetTableStatus, &request, &response, FLAGS_request_timeout_ms, 1); - if (ret) { - return true; + if (st.OK()) { + return {response.code(), response.msg()}; } - return false; + return st; } -bool TabletClient::GetTableStatus(uint32_t tid, uint32_t pid, ::openmldb::api::TableStatus& table_status) { +base::Status TabletClient::GetTableStatus(uint32_t tid, uint32_t pid, ::openmldb::api::TableStatus& table_status) { return GetTableStatus(tid, pid, false, table_status); } -bool TabletClient::GetTableStatus(uint32_t tid, uint32_t pid, bool need_schema, +base::Status TabletClient::GetTableStatus(uint32_t tid, uint32_t pid, bool need_schema, ::openmldb::api::TableStatus& table_status) { ::openmldb::api::GetTableStatusRequest request; request.set_tid(tid); request.set_pid(pid); request.set_need_schema(need_schema); ::openmldb::api::GetTableStatusResponse response; - bool ret = client_.SendRequest(&::openmldb::api::TabletServer_Stub::GetTableStatus, &request, &response, + auto st = client_.SendRequestSt(&::openmldb::api::TabletServer_Stub::GetTableStatus, &request, &response, FLAGS_request_timeout_ms, 1); - if (!ret) { - return false; + if (!st.OK()) { + return st; } - if (response.all_table_status_size() > 0) { + if (response.code() == 0 && response.all_table_status_size() > 0) { table_status = response.all_table_status(0); - return true; } - return false; + return {response.code(), response.msg()}; } std::shared_ptr TabletClient::Scan(uint32_t tid, uint32_t pid, @@ -680,25 +695,27 @@ bool TabletClient::SetExpire(uint32_t tid, uint32_t pid, bool is_expire) { return true; } -bool TabletClient::GetTableFollower(uint32_t tid, uint32_t pid, uint64_t& offset, - std::map& info_map, std::string& msg) { +base::Status TabletClient::GetTableFollower(uint32_t tid, uint32_t pid, uint64_t& offset, + std::map& info_map) { ::openmldb::api::GetTableFollowerRequest request; ::openmldb::api::GetTableFollowerResponse response; request.set_tid(tid); request.set_pid(pid); - bool ok = client_.SendRequest(&::openmldb::api::TabletServer_Stub::GetTableFollower, &request, &response, - FLAGS_request_timeout_ms, 1); - if (response.has_msg()) { - msg = response.msg(); - } - if (!ok || response.code() != 0) { - return false; - } - for (int idx = 0; idx < response.follower_info_size(); idx++) { - info_map.insert(std::make_pair(response.follower_info(idx).endpoint(), response.follower_info(idx).offset())); + auto st = client_.SendRequestSt(&::openmldb::api::TabletServer_Stub::GetTableFollower, &request, &response, + FLAGS_request_timeout_ms, 1); + if (st.OK()) { + if (response.code() == 0) { + offset = response.offset(); + for (int idx = 0; idx < response.follower_info_size(); idx++) { + info_map.insert( + std::make_pair(response.follower_info(idx).endpoint(), response.follower_info(idx).offset())); + } + return {}; + } else { + return {response.code(), response.msg()}; + } } - offset = response.offset(); - return true; + return st; } bool TabletClient::Get(uint32_t tid, uint32_t pid, const std::string& pk, uint64_t time, std::string& value, @@ -1150,7 +1167,7 @@ bool TabletClient::SubBatchRequestQuery(const ::openmldb::api::SQLBatchRequestQu if (callback == nullptr) { return false; } - return client_.SendRequest(&::openmldb::api::TabletServer_Stub::SQLBatchRequestQuery, + return client_.SendRequest(&::openmldb::api::TabletServer_Stub::SubBatchRequestQuery, callback->GetController().get(), &request, callback->GetResponse().get(), callback); } diff --git a/src/client/tablet_client.h b/src/client/tablet_client.h index f955040a157..9fee8e08392 100644 --- a/src/client/tablet_client.h +++ b/src/client/tablet_client.h @@ -39,7 +39,6 @@ namespace sdk { class SQLRequestRowBatch; } // namespace sdk - namespace client { using ::openmldb::api::TaskInfo; const uint32_t INVALID_REMOTE_TID = UINT32_MAX; @@ -56,6 +55,8 @@ class TabletClient : public Client { base::Status CreateTable(const ::openmldb::api::TableMeta& table_meta); + base::Status TruncateTable(uint32_t tid, uint32_t pid); + bool UpdateTableMetaForAddField(uint32_t tid, const std::vector& cols, const openmldb::common::VersionPair& pair, std::string& msg); // NOLINT @@ -76,9 +77,13 @@ class TabletClient : public Client { bool Put(uint32_t tid, uint32_t pid, uint64_t time, const std::string& value, const std::vector>& dimensions); + bool Put(uint32_t tid, uint32_t pid, uint64_t time, const base::Slice& value, + ::google::protobuf::RepeatedPtrField<::openmldb::api::Dimension>* dimensions); + bool Get(uint32_t tid, uint32_t pid, const std::string& pk, uint64_t time, std::string& value, // NOLINT uint64_t& ts, // NOLINT - std::string& msg); ; // NOLINT + std::string& msg); + ; // NOLINT bool Get(uint32_t tid, uint32_t pid, const std::string& pk, uint64_t time, const std::string& idx_name, std::string& value, // NOLINT @@ -89,21 +94,20 @@ class TabletClient : public Client { std::string& msg); // NOLINT base::Status Delete(uint32_t tid, uint32_t pid, const std::map& index_val, - const std::string& ts_name, const std::optional start_ts, const std::optional& end_ts); + const std::string& ts_name, const std::optional start_ts, + const std::optional& end_ts); bool Count(uint32_t tid, uint32_t pid, const std::string& pk, const std::string& idx_name, bool filter_expired_data, uint64_t& value, std::string& msg); // NOLINT + std::shared_ptr Scan(uint32_t tid, uint32_t pid, const std::string& pk, + const std::string& idx_name, uint64_t stime, uint64_t etime, + uint32_t limit, uint32_t skip_record_num, + std::string& msg); // NOLINT - std::shared_ptr Scan(uint32_t tid, uint32_t pid, - const std::string& pk, const std::string& idx_name, - uint64_t stime, uint64_t etime, - uint32_t limit, uint32_t skip_record_num, std::string& msg); // NOLINT - - std::shared_ptr Scan(uint32_t tid, uint32_t pid, - const std::string& pk, const std::string& idx_name, - uint64_t stime, uint64_t etime, - uint32_t limit, std::string& msg); // NOLINT + std::shared_ptr Scan(uint32_t tid, uint32_t pid, const std::string& pk, + const std::string& idx_name, uint64_t stime, uint64_t etime, + uint32_t limit, std::string& msg); // NOLINT bool Scan(const ::openmldb::api::ScanRequest& request, brpc::Controller* cntl, ::openmldb::api::ScanResponse* response); @@ -135,15 +139,14 @@ class TabletClient : public Client { bool RecoverSnapshot(uint32_t tid, uint32_t pid, std::shared_ptr task_info = std::shared_ptr()); - bool LoadTable(const std::string& name, uint32_t id, uint32_t pid, uint64_t ttl, uint32_t seg_cnt); + base::Status LoadTable(const std::string& name, uint32_t id, uint32_t pid, uint64_t ttl, uint32_t seg_cnt); - bool LoadTable(const std::string& name, uint32_t id, uint32_t pid, uint64_t ttl, bool leader, uint32_t seg_cnt, - std::shared_ptr task_info = std::shared_ptr()); + base::Status LoadTable(const std::string& name, uint32_t id, uint32_t pid, uint64_t ttl, bool leader, + uint32_t seg_cnt, std::shared_ptr task_info = std::shared_ptr()); + // for ns WrapTaskFun, must return bool bool LoadTable(const ::openmldb::api::TableMeta& table_meta, std::shared_ptr task_info); - bool LoadTable(uint32_t tid, uint32_t pid, std::string* msg); - bool ChangeRole(uint32_t tid, uint32_t pid, bool leader, uint64_t term); bool ChangeRole(uint32_t tid, uint32_t pid, bool leader, const std::vector& endpoints, uint64_t term, @@ -160,26 +163,25 @@ class TabletClient : public Client { bool GetTermPair(uint32_t tid, uint32_t pid, ::openmldb::common::StorageMode storage_mode, // NOLINT - uint64_t& term, // NOLINT - uint64_t& offset, bool& has_table, // NOLINT - bool& is_leader); // NOLINT + uint64_t& term, // NOLINT + uint64_t& offset, bool& has_table, // NOLINT + bool& is_leader); // NOLINT bool GetManifest(uint32_t tid, uint32_t pid, ::openmldb::common::StorageMode storage_mode, ::openmldb::api::Manifest& manifest); // NOLINT - bool GetTableStatus(::openmldb::api::GetTableStatusResponse& response); // NOLINT - bool GetTableStatus(uint32_t tid, uint32_t pid, - ::openmldb::api::TableStatus& table_status); // NOLINT - bool GetTableStatus(uint32_t tid, uint32_t pid, bool need_schema, - ::openmldb::api::TableStatus& table_status); // NOLINT + base::Status GetTableStatus(::openmldb::api::GetTableStatusResponse& response); // NOLINT + base::Status GetTableStatus(uint32_t tid, uint32_t pid, + ::openmldb::api::TableStatus& table_status); // NOLINT + base::Status GetTableStatus(uint32_t tid, uint32_t pid, bool need_schema, + ::openmldb::api::TableStatus& table_status); // NOLINT bool FollowOfNoOne(uint32_t tid, uint32_t pid, uint64_t term, uint64_t& offset); // NOLINT - bool GetTableFollower(uint32_t tid, uint32_t pid, - uint64_t& offset, // NOLINT - std::map& info_map, // NOLINT - std::string& msg); // NOLINT + base::Status GetTableFollower(uint32_t tid, uint32_t pid, + uint64_t& offset, // NOLINT + std::map& info_map); // NOLINT bool GetAllSnapshotOffset(std::map>& tid_pid_offset); // NOLINT @@ -188,8 +190,9 @@ class TabletClient : public Client { bool DisConnectZK(); std::shared_ptr Traverse(uint32_t tid, uint32_t pid, - const std::string& idx_name, const std::string& pk, uint64_t ts, - uint32_t limit, bool skip_current_pk, uint32_t ts_pos, uint32_t& count); // NOLINT + const std::string& idx_name, const std::string& pk, + uint64_t ts, uint32_t limit, bool skip_current_pk, + uint32_t ts_pos, uint32_t& count); // NOLINT bool SetMode(bool mode); @@ -198,9 +201,8 @@ class TabletClient : public Client { bool AddIndex(uint32_t tid, uint32_t pid, const ::openmldb::common::ColumnKey& column_key, std::shared_ptr task_info); - bool AddMultiIndex(uint32_t tid, uint32_t pid, - const std::vector<::openmldb::common::ColumnKey>& column_keys, - std::shared_ptr task_info); + bool AddMultiIndex(uint32_t tid, uint32_t pid, const std::vector<::openmldb::common::ColumnKey>& column_keys, + std::shared_ptr task_info); bool GetCatalog(uint64_t* version); @@ -210,8 +212,7 @@ class TabletClient : public Client { bool LoadIndexData(uint32_t tid, uint32_t pid, uint32_t partition_num, std::shared_ptr task_info); bool ExtractIndexData(uint32_t tid, uint32_t pid, uint32_t partition_num, - const std::vector<::openmldb::common::ColumnKey>& column_key, - uint64_t offset, bool dump_data, + const std::vector<::openmldb::common::ColumnKey>& column_key, uint64_t offset, bool dump_data, std::shared_ptr task_info); bool CancelOP(const uint64_t op_id); @@ -230,9 +231,9 @@ class TabletClient : public Client { uint64_t timeout_ms); base::Status CallSQLBatchRequestProcedure(const std::string& db, const std::string& sp_name, - const base::Slice& meta, const base::Slice& data, - bool is_debug, uint64_t timeout_ms, - brpc::Controller* cntl, openmldb::api::SQLBatchRequestQueryResponse* response); + const base::Slice& meta, const base::Slice& data, bool is_debug, + uint64_t timeout_ms, brpc::Controller* cntl, + openmldb::api::SQLBatchRequestQueryResponse* response); bool DropProcedure(const std::string& db_name, const std::string& sp_name); @@ -256,17 +257,19 @@ class TabletClient : public Client { uint64_t timeout_ms, openmldb::RpcCallback* callback); - base::Status CallSQLBatchRequestProcedure(const std::string& db, const std::string& sp_name, - const base::Slice& meta, const base::Slice& data, - bool is_debug, uint64_t timeout_ms, - openmldb::RpcCallback* callback); + base::Status CallSQLBatchRequestProcedure( + const std::string& db, const std::string& sp_name, const base::Slice& meta, const base::Slice& data, + bool is_debug, uint64_t timeout_ms, + openmldb::RpcCallback* callback); - bool CreateAggregator(const ::openmldb::api::TableMeta& base_table_meta, - uint32_t aggr_tid, uint32_t aggr_pid, uint32_t index_pos, - const ::openmldb::base::LongWindowInfo& window_info); + bool CreateAggregator(const ::openmldb::api::TableMeta& base_table_meta, uint32_t aggr_tid, uint32_t aggr_pid, + uint32_t index_pos, const ::openmldb::base::LongWindowInfo& window_info); bool GetAndFlushDeployStats(::openmldb::api::DeployStatsResponse* res); + private: + base::Status LoadTableInternal(const ::openmldb::api::TableMeta& table_meta, std::shared_ptr task_info); + private: ::openmldb::RpcClient<::openmldb::api::TabletServer_Stub> client_; }; diff --git a/src/cmd/display.h b/src/cmd/display.h index 6f01549bada..714a9ca6a73 100644 --- a/src/cmd/display.h +++ b/src/cmd/display.h @@ -106,13 +106,14 @@ __attribute__((unused)) static void PrintColumnKey( t.add("ttl"); t.add("ttl_type"); t.end_of_row(); - + int index_pos = 1; for (int i = 0; i < column_key_field.size(); i++) { const auto& column_key = column_key_field.Get(i); if (column_key.flag() == 1) { continue; } - t.add(std::to_string(i + 1)); + t.add(std::to_string(index_pos)); + index_pos++; t.add(column_key.index_name()); std::string key; for (const auto& name : column_key.col_name()) { @@ -146,7 +147,7 @@ __attribute__((unused)) static void PrintColumnKey( stream << t; } -__attribute__((unused)) static void ShowTableRows(bool is_compress, ::openmldb::codec::SDKCodec* codec, +__attribute__((unused)) static void ShowTableRows(::openmldb::codec::SDKCodec* codec, ::openmldb::cmd::SDKIterator* it) { std::vector row = codec->GetColNames(); if (!codec->HasTSCol()) { @@ -160,12 +161,7 @@ __attribute__((unused)) static void ShowTableRows(bool is_compress, ::openmldb:: while (it->Valid()) { std::vector vrow; openmldb::base::Slice data = it->GetValue(); - std::string value; - if (is_compress) { - ::snappy::Uncompress(data.data(), data.size(), &value); - } else { - value.assign(data.data(), data.size()); - } + std::string value(data.data(), data.size()); codec->DecodeRow(value, &vrow); if (!codec->HasTSCol()) { vrow.insert(vrow.begin(), std::to_string(it->GetKey())); @@ -186,19 +182,16 @@ __attribute__((unused)) static void ShowTableRows(bool is_compress, ::openmldb:: __attribute__((unused)) static void ShowTableRows(const ::openmldb::api::TableMeta& table_info, ::openmldb::cmd::SDKIterator* it) { ::openmldb::codec::SDKCodec codec(table_info); - bool is_compress = table_info.compress_type() == ::openmldb::type::CompressType::kSnappy ? true : false; - ShowTableRows(is_compress, &codec, it); + ShowTableRows(&codec, it); } __attribute__((unused)) static void ShowTableRows(const ::openmldb::nameserver::TableInfo& table_info, ::openmldb::cmd::SDKIterator* it) { ::openmldb::codec::SDKCodec codec(table_info); - bool is_compress = table_info.compress_type() == ::openmldb::type::CompressType::kSnappy ? true : false; - ShowTableRows(is_compress, &codec, it); + ShowTableRows(&codec, it); } -__attribute__((unused)) static void ShowTableRows(const std::string& key, ::openmldb::cmd::SDKIterator* it, - const ::openmldb::type::CompressType compress_type) { +__attribute__((unused)) static void ShowTableRows(const std::string& key, ::openmldb::cmd::SDKIterator* it) { ::baidu::common::TPrinter tp(4, FLAGS_max_col_display_length); std::vector row; row.push_back("#"); @@ -209,11 +202,6 @@ __attribute__((unused)) static void ShowTableRows(const std::string& key, ::open uint32_t index = 1; while (it->Valid()) { std::string value = it->GetValue().ToString(); - if (compress_type == ::openmldb::type::CompressType::kSnappy) { - std::string uncompressed; - ::snappy::Uncompress(value.c_str(), value.length(), &uncompressed); - value = uncompressed; - } row.clear(); row.push_back(std::to_string(index)); row.push_back(key); diff --git a/src/cmd/openmldb.cc b/src/cmd/openmldb.cc index 3cf22b2df6d..b4d12210cdf 100644 --- a/src/cmd/openmldb.cc +++ b/src/cmd/openmldb.cc @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -63,6 +62,8 @@ DECLARE_string(nameserver); DECLARE_int32(port); DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_int32(thread_pool_size); DECLARE_int32(put_concurrency_limit); DECLARE_int32(get_concurrency_limit); @@ -341,11 +342,6 @@ ::openmldb::base::Status PutSchemaData(const ::openmldb::nameserver::TableInfo& return ::openmldb::base::Status(-1, "Encode data error"); } - if (table_info.compress_type() == ::openmldb::type::CompressType::kSnappy) { - std::string compressed; - ::snappy::Compress(value.c_str(), value.length(), &compressed); - value = compressed; - } const int tid = table_info.tid(); PutData(tid, dimensions, ts, value, table_info.table_partition()); @@ -480,7 +476,8 @@ void HandleNSClientSetTTL(const std::vector& parts, ::openmldb::cli bool ok = client->UpdateTTL(parts[1], type, abs_ttl, lat_ttl, index_name, err); if (ok) { std::cout << "Set ttl ok ! Note that, " - "it will take effect after two garbage collection intervals (i.e. gc_interval)." << std::endl; + "it will take effect after two garbage collection intervals (i.e. gc_interval)." + << std::endl; } else { std::cout << "Set ttl failed! " << err << std::endl; } @@ -495,17 +492,16 @@ void HandleNSClientCancelOP(const std::vector& parts, ::openmldb::c return; } try { - std::string err; if (boost::lexical_cast(parts[1]) <= 0) { std::cout << "Invalid args. op_id should be large than zero" << std::endl; return; } uint64_t op_id = boost::lexical_cast(parts[1]); - bool ok = client->CancelOP(op_id, err); - if (ok) { - std::cout << "Cancel op ok!" << std::endl; + auto st = client->CancelOP(op_id); + if (st.OK()) { + std::cout << "Cancel op ok" << std::endl; } else { - std::cout << "Cancel op failed! " << err << std::endl; + std::cout << "Cancel op failed, error msg: " << st.ToString() << std::endl; } } catch (std::exception const& e) { std::cout << "Invalid args. op_id should be uint64_t" << std::endl; @@ -697,10 +693,10 @@ void HandleNSAddReplica(const std::vector& parts, ::openmldb::clien std::cout << "has not valid pid" << std::endl; return; } - std::string msg; - bool ok = client->AddReplica(parts[1], pid_set, parts[3], msg); - if (!ok) { - std::cout << "Fail to addreplica. error msg:" << msg << std::endl; + + auto st = client->AddReplica(parts[1], pid_set, parts[3]); + if (!st.OK()) { + std::cout << "Fail to addreplica. error msg:" << st.GetMsg() << std::endl; return; } std::cout << "AddReplica ok" << std::endl; @@ -720,10 +716,9 @@ void HandleNSDelReplica(const std::vector& parts, ::openmldb::clien std::cout << "has not valid pid" << std::endl; return; } - std::string msg; - bool ok = client->DelReplica(parts[1], pid_set, parts[3], msg); - if (!ok) { - std::cout << "Fail to delreplica. error msg:" << msg << std::endl; + auto st = client->DelReplica(parts[1], pid_set, parts[3]); + if (!st.OK()) { + std::cout << "Fail to delreplica. error msg:" << st.GetMsg() << std::endl; return; } std::cout << "DelReplica ok" << std::endl; @@ -900,17 +895,17 @@ void HandleNSClientChangeLeader(const std::vector& parts, ::openmld if (parts.size() > 3) { candidate_leader = parts[3]; } - bool ret = client->ChangeLeader(parts[1], pid, candidate_leader, msg); - if (!ret) { - std::cout << "failed to change leader. error msg: " << msg << std::endl; + auto st = client->ChangeLeader(parts[1], pid, candidate_leader); + if (!st.OK()) { + std::cout << "failed to change leader. error msg: " << st.GetMsg() << std::endl; return; } } catch (const std::exception& e) { std::cout << "Invalid args. pid should be uint32_t" << std::endl; return; } - std::cout << "change leader ok. " - "If there are writing operations while changing a leader, it may cause data loss." << std::endl; + std::cout << "change leader ok. If there are writing operations while changing a leader, it may cause data loss." + << std::endl; } void HandleNSClientOfflineEndpoint(const std::vector& parts, ::openmldb::client::NsClient* client) { @@ -961,9 +956,9 @@ void HandleNSClientMigrate(const std::vector& parts, ::openmldb::cl std::cout << "has not valid pid" << std::endl; return; } - bool ret = client->Migrate(parts[1], parts[2], pid_set, parts[4], msg); - if (!ret) { - std::cout << "failed to migrate partition. error msg: " << msg << std::endl; + auto st = client->Migrate(parts[1], parts[2], pid_set, parts[4]); + if (!st.OK()) { + std::cout << "failed to migrate partition. error msg: " << st.GetMsg() << std::endl; return; } std::cout << "partition migrate ok" << std::endl; @@ -1016,10 +1011,9 @@ void HandleNSClientRecoverTable(const std::vector& parts, ::openmld } try { uint32_t pid = boost::lexical_cast(parts[2]); - std::string msg; - bool ok = client->RecoverTable(parts[1], pid, parts[3], msg); - if (!ok) { - std::cout << "Fail to recover table. error msg:" << msg << std::endl; + auto st = client->RecoverTable(parts[1], pid, parts[3]); + if (!st.OK()) { + std::cout << "Fail to recover table. error msg:" << st.GetMsg() << std::endl; return; } std::cout << "recover table ok" << std::endl; @@ -1396,11 +1390,6 @@ void HandleNSGet(const std::vector& parts, ::openmldb::client::NsCl std::string msg; bool ok = tb_client->Get(tid, pid, key, timestamp, value, ts, msg); if (ok) { - if (tables[0].compress_type() == ::openmldb::type::CompressType::kSnappy) { - std::string uncompressed; - ::snappy::Uncompress(value.c_str(), value.length(), &uncompressed); - value = uncompressed; - } std::cout << "value :" << value << std::endl; } else { std::cout << "Get failed. error msg: " << msg << std::endl; @@ -1447,11 +1436,6 @@ void HandleNSGet(const std::vector& parts, ::openmldb::client::NsCl return; } } - if (tables[0].compress_type() == ::openmldb::type::CompressType::kSnappy) { - std::string uncompressed; - ::snappy::Uncompress(value.c_str(), value.length(), &uncompressed); - value.swap(uncompressed); - } row.clear(); codec.DecodeRow(value, &row); ::openmldb::cmd::TransferString(&row); @@ -1588,7 +1572,7 @@ void HandleNSScan(const std::vector& parts, ::openmldb::client::NsC std::vector> iter_vec; iter_vec.push_back(std::move(it)); ::openmldb::cmd::SDKIterator sdk_it(iter_vec, limit); - ::openmldb::cmd::ShowTableRows(key, &sdk_it, tables[0].compress_type()); + ::openmldb::cmd::ShowTableRows(key, &sdk_it); } } else { if (parts.size() < 6) { @@ -1848,25 +1832,14 @@ void HandleNSPreview(const std::vector& parts, ::openmldb::client:: row.push_back(std::to_string(index)); if (no_schema) { - std::string value = it->GetValue().ToString(); - if (tables[0].compress_type() == ::openmldb::type::CompressType::kSnappy) { - std::string uncompressed; - ::snappy::Uncompress(value.c_str(), value.length(), &uncompressed); - value = uncompressed; - } row.push_back(it->GetPK()); row.push_back(std::to_string(it->GetKey())); - row.push_back(value); + row.push_back(it->GetValue().ToString()); } else { if (!has_ts_col) { row.push_back(std::to_string(it->GetKey())); } - std::string value; - if (tables[0].compress_type() == ::openmldb::type::CompressType::kSnappy) { - ::snappy::Uncompress(it->GetValue().data(), it->GetValue().size(), &value); - } else { - value.assign(it->GetValue().data(), it->GetValue().size()); - } + std::string value(it->GetValue().data(), it->GetValue().size()); codec.DecodeRow(value, &row); ::openmldb::cmd::TransferString(&row); uint64_t row_size = row.size(); @@ -2696,9 +2669,9 @@ void HandleNSClientUpdateTableAlive(const std::vector& parts, ::ope return; } } - std::string msg; - if (!client->UpdateTableAliveStatus(endpoint, name, pid, is_alive, msg)) { - std::cout << "Fail to update table alive. error msg: " << msg << std::endl; + + if (auto st = client->UpdateTableAliveStatus(endpoint, name, pid, is_alive); !st.OK()) { + std::cout << "Fail to update table alive. error msg: " << st.GetMsg() << std::endl; return; } std::cout << "update ok" << std::endl; @@ -3110,19 +3083,20 @@ void HandleClientGetTableStatus(const std::vector parts, ::openmldb if (parts.size() == 3) { ::openmldb::api::TableStatus table_status; try { - if (client->GetTableStatus(boost::lexical_cast(parts[1]), boost::lexical_cast(parts[2]), - table_status)) { + if (auto st = client->GetTableStatus(boost::lexical_cast(parts[1]), + boost::lexical_cast(parts[2]), table_status); + st.OK()) { status_vec.push_back(table_status); } else { - std::cout << "gettablestatus failed" << std::endl; + std::cout << "gettablestatus failed, error msg: " << st.GetMsg() << std::endl; } } catch (boost::bad_lexical_cast& e) { std::cout << "Bad gettablestatus format" << std::endl; } } else if (parts.size() == 1) { ::openmldb::api::GetTableStatusResponse response; - if (!client->GetTableStatus(response)) { - std::cout << "gettablestatus failed" << std::endl; + if (auto st = client->GetTableStatus(response); !st.OK()) { + std::cout << "gettablestatus failed, error msg: " << st.GetMsg() << std::endl; return; } for (int idx = 0; idx < response.all_table_status_size(); idx++) { @@ -3227,12 +3201,13 @@ void HandleClientLoadTable(const std::vector parts, ::openmldb::cli return; } } - bool ok = client->LoadTable(parts[1], boost::lexical_cast(parts[2]), + // TODO(): get status msg + auto st = client->LoadTable(parts[1], boost::lexical_cast(parts[2]), boost::lexical_cast(parts[3]), ttl, is_leader, seg_cnt); - if (ok) { + if (st.OK()) { std::cout << "LoadTable ok" << std::endl; } else { - std::cout << "Fail to LoadTable" << std::endl; + std::cout << "Fail to LoadTable: " << st.ToString() << std::endl; } } catch (boost::bad_lexical_cast& e) { std::cout << "Bad LoadTable format" << std::endl; @@ -3303,8 +3278,8 @@ void HandleClientPreview(const std::vector& parts, ::openmldb::clie return; } ::openmldb::api::TableStatus table_status; - if (!client->GetTableStatus(tid, pid, true, table_status)) { - std::cout << "Fail to get table status" << std::endl; + if (auto st = client->GetTableStatus(tid, pid, true, table_status); !st.OK()) { + std::cout << "Fail to get table status, error msg: " << st.GetMsg() << std::endl; return; } /*std::string schema = table_status.schema(); @@ -3394,9 +3369,8 @@ void HandleClientGetFollower(const std::vector& parts, ::openmldb:: } std::map info_map; uint64_t offset = 0; - std::string msg; - if (!client->GetTableFollower(tid, pid, offset, info_map, msg)) { - std::cout << "get failed. msg: " << msg << std::endl; + if (auto st = client->GetTableFollower(tid, pid, offset, info_map); !st.OK()) { + std::cout << "get failed, error msg: " << st.GetMsg() << std::endl; return; } std::vector header; @@ -3680,7 +3654,7 @@ void StartNsClient() { std::shared_ptr<::openmldb::zk::ZkClient> zk_client; if (!FLAGS_zk_cluster.empty()) { zk_client = std::make_shared<::openmldb::zk::ZkClient>(FLAGS_zk_cluster, "", - FLAGS_zk_session_timeout, "", FLAGS_zk_root_path); + FLAGS_zk_session_timeout, "", FLAGS_zk_root_path, FLAGS_zk_auth_schema, FLAGS_zk_cert); if (!zk_client->Init()) { std::cout << "zk client init failed" << std::endl; return; @@ -3878,7 +3852,7 @@ void StartAPIServer() { GetRealEndpoint(&real_endpoint); } - auto api_service = std::make_unique<::openmldb::apiserver::APIServerImpl>(); + auto api_service = std::make_unique<::openmldb::apiserver::APIServerImpl>(real_endpoint); if (!FLAGS_nameserver.empty()) { std::vector vec; boost::split(vec, FLAGS_nameserver, boost::is_any_of(":")); @@ -3903,6 +3877,8 @@ void StartAPIServer() { cluster_options.zk_cluster = FLAGS_zk_cluster; cluster_options.zk_path = FLAGS_zk_root_path; cluster_options.zk_session_timeout = FLAGS_zk_session_timeout; + cluster_options.zk_auth_schema = FLAGS_zk_auth_schema; + cluster_options.zk_cert = FLAGS_zk_cert; if (!api_service->Init(cluster_options)) { PDLOG(WARNING, "Fail to init"); exit(1); diff --git a/src/cmd/sql_cmd.h b/src/cmd/sql_cmd.h index 5cf1b99cd0c..6b8eae72afb 100644 --- a/src/cmd/sql_cmd.h +++ b/src/cmd/sql_cmd.h @@ -41,6 +41,8 @@ DEFINE_string(spark_conf, "", "The config file of Spark job"); // cluster mode DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_int32(zk_session_timeout); DECLARE_uint32(zk_log_level); DECLARE_string(zk_log_file); @@ -141,14 +143,59 @@ std::string ExecFetch(const std::string& sql) { return ss.str(); } -void HandleSQL(const std::string& sql) { - std::cout << ExecFetch(sql); +void HandleSQL(const std::string& sql) { std::cout << ExecFetch(sql); } + +std::string SafeGetString(std::shared_ptr rs, int idx) { + std::string tmp; + if (!rs->GetString(idx, &tmp)) { + LOG(WARNING) << "fail to get string in col " << idx; + return ""; + } + return tmp; +} + +bool CheckAllTableStatus() { + hybridse::sdk::Status status; + auto rs = sr->ExecuteSQL("show table status like '%'", &status); + bool is_ok = true; + if (status.IsOK()) { + // ref GetTableStatusSchema, just use idx to get column + while (rs->Next()) { + auto table = SafeGetString(rs, 1); + auto db = SafeGetString(rs, 2); + auto pu = SafeGetString(rs, 8); + auto warnings = SafeGetString(rs, 13); + std::string msg = absl::StrCat("table ", db, ".", table, " is broken, `show table status` to check detail"); + bool is_broken = false; + if (pu != "0") { + is_broken = true; + msg.append(", unalive partition: ").append(pu); + } + if (!warnings.empty()) { + is_broken = true; + msg.append(", warning preview: ").append(warnings.substr(0, 100)); + } + if (is_broken) { + is_ok = false; + std::cout << "ERROR: " << msg << std::endl; + } + } + } else { + std::cout << "ERROR: fail to get all table status, " << status.ToString() << std::endl; + is_ok = false; + } + return is_ok; } // cluster mode if zk_cluster is not empty, otherwise standalone mode void Shell() { DCHECK(cs); DCHECK(sr); + // before all, check all table status + if (!CheckAllTableStatus()) { + std::cout << "HINT: Use `openmldb_tool inspect` to get full report." << std::endl; + } + // If use FLAGS_cmd, non-interactive. No Logo and make sure router interactive is false if (!FLAGS_cmd.empty()) { std::string db = FLAGS_database; @@ -222,6 +269,8 @@ bool InitClusterSDK() { copt.zk_session_timeout = FLAGS_zk_session_timeout; copt.zk_log_level = FLAGS_zk_log_level; copt.zk_log_file = FLAGS_zk_log_file; + copt.zk_auth_schema = FLAGS_zk_auth_schema; + copt.zk_cert = FLAGS_zk_cert; cs = new ::openmldb::sdk::ClusterSDK(copt); if (!cs->Init()) { diff --git a/src/cmd/sql_cmd_test.cc b/src/cmd/sql_cmd_test.cc index 1896ac7c674..459aef2971e 100644 --- a/src/cmd/sql_cmd_test.cc +++ b/src/cmd/sql_cmd_test.cc @@ -331,6 +331,45 @@ TEST_P(DBSDKTest, Select) { ASSERT_TRUE(status.IsOK()); } +TEST_P(DBSDKTest, SelectSnappy) { + auto cli = GetParam(); + cs = cli->cs; + sr = cli->sr; + hybridse::sdk::Status status; + if (cs->IsClusterMode()) { + sr->ExecuteSQL("SET @@execute_mode='online';", &status); + ASSERT_TRUE(status.IsOK()) << "error msg: " + status.msg; + } + std::string db = "db" + GenRand(); + sr->ExecuteSQL("create database " + db + ";", &status); + ASSERT_TRUE(status.IsOK()); + sr->ExecuteSQL("use " + db + ";", &status); + ASSERT_TRUE(status.IsOK()); + std::string create_sql = + "create table trans (c1 string, c2 bigint, c3 date," + "index(key=c1, ts=c2, abs_ttl=0, ttl_type=absolute)) options (compress_type='snappy');"; + sr->ExecuteSQL(create_sql, &status); + ASSERT_TRUE(status.IsOK()); + int insert_num = 100; + for (int i = 0; i < insert_num; i++) { + auto insert_sql = absl::StrCat("insert into trans values ('aaa", i, "', 1635247427000, \"2021-05-20\");"); + sr->ExecuteSQL(insert_sql, &status); + ASSERT_TRUE(status.IsOK()); + } + auto rs = sr->ExecuteSQL("select * from trans", &status); + ASSERT_TRUE(status.IsOK()); + ASSERT_EQ(insert_num, rs->Size()); + int count = 0; + while (rs->Next()) { + count++; + } + EXPECT_EQ(count, insert_num); + sr->ExecuteSQL("drop table trans;", &status); + ASSERT_TRUE(status.IsOK()); + sr->ExecuteSQL("drop database " + db + ";", &status); + ASSERT_TRUE(status.IsOK()); +} + TEST_F(SqlCmdTest, SelectMultiPartition) { auto sr = cluster_cli.sr; std::string db_name = "test" + GenRand(); @@ -461,11 +500,11 @@ TEST_P(DBSDKTest, Desc) { " --- ------- ----------- ------ --------- \n"; std::string expect_options = - " -------------- \n" - " storage_mode \n" - " -------------- \n" - " Memory \n" - " -------------- \n\n"; + " --------------- -------------- \n" + " compress_type storage_mode \n" + " --------------- -------------- \n" + " NoCompress Memory \n" + " --------------- -------------- \n\n"; // index name is dynamically assigned. do not check here std::vector expect = {expect_schema, "", expect_options}; @@ -1039,6 +1078,47 @@ TEST_P(DBSDKTest, DeployWithBias) { ASSERT_TRUE(cs->GetNsClient()->DropDatabase(db, msg)); } +TEST_P(DBSDKTest, Truncate) { + auto cli = GetParam(); + sr = cli->sr; + std::string db_name = "test2"; + std::string table_name = "test1"; + std::string ddl = "create table test1 (c1 string, c2 int, c3 bigint, INDEX(KEY=c1, ts=c3));"; + ProcessSQLs(sr, { + "set @@execute_mode = 'online'", + absl::StrCat("create database ", db_name, ";"), + absl::StrCat("use ", db_name, ";"), + ddl, + }); + hybridse::sdk::Status status; + sr->ExecuteSQL(absl::StrCat("truncate table ", table_name, ";"), &status); + ASSERT_TRUE(status.IsOK()) << status.ToString(); + auto res = sr->ExecuteSQL(absl::StrCat("select * from ", table_name, ";"), &status); + ASSERT_EQ(res->Size(), 0); + for (int i = 0; i < 10; i++) { + std::string key = absl::StrCat("key", i); + for (int j = 0; j < 10; j++) { + uint64_t ts = 1000 + j; + sr->ExecuteSQL(absl::StrCat("insert into ", table_name, " values ('", key, "', 11, ", ts, ");"), &status); + } + } + + res = sr->ExecuteSQL(absl::StrCat("select * from ", table_name, ";"), &status); + ASSERT_EQ(res->Size(), 100); + sr->ExecuteSQL(absl::StrCat("truncate table ", table_name, ";"), &status); + ASSERT_TRUE(status.IsOK()) << status.ToString(); + res = sr->ExecuteSQL(absl::StrCat("select * from ", table_name, ";"), &status); + ASSERT_EQ(res->Size(), 0); + sr->ExecuteSQL(absl::StrCat("insert into ", table_name, " values ('aa', 11, 100);"), &status); + res = sr->ExecuteSQL(absl::StrCat("select * from ", table_name, ";"), &status); + ASSERT_EQ(res->Size(), 1); + ProcessSQLs(sr, { + absl::StrCat("use ", db_name, ";"), + absl::StrCat("drop table ", table_name), + absl::StrCat("drop database ", db_name), + }); +} + TEST_P(DBSDKTest, DeletetRange) { auto cli = GetParam(); sr = cli->sr; @@ -3697,7 +3777,7 @@ TEST_F(SqlCmdTest, SelectWithAddNewIndex) { // -------------------------------------------------------------------------------------- // basic functional UTs to test if it is correct for deploy query response time collection -// see NameServerImpl::SyncDeployStats & TabletImpl::TryCollectDeployStats +// see TabletImpl::CollectDeployStats // -------------------------------------------------------------------------------------- // a proxy class to create and cleanup deployment stats more gracefully @@ -3770,12 +3850,6 @@ struct DeploymentEnv { ASSERT_TRUE(status.IsOK()) << status.msg << "\n" << status.trace; } - void EnableDeployStats() { - ProcessSQLs(sr_, { - "set global deploy_stats = 'on'", - }); - } - sdk::SQLClusterRouter* sr_; absl::BitGen gen_; // variables generate randomly in SetUp @@ -3802,111 +3876,6 @@ struct DeploymentEnv { } }; -static const char QueryDeployResponseTime[] = "select * from INFORMATION_SCHEMA.DEPLOY_RESPONSE_TIME"; - -TEST_P(DBSDKTest, DeployStatsNotEnableByDefault) { - auto cli = GetParam(); - cs = cli->cs; - sr = cli->sr; - - DeploymentEnv env(sr); - env.SetUp(); - env.CallDeployProcedureBatch(); - env.CallDeployProcedure(); - - absl::SleepFor(absl::Seconds(3)); - - hybridse::sdk::Status status; - auto rs = sr->ExecuteSQLParameterized("", QueryDeployResponseTime, {}, &status); - ASSERT_TRUE(status.IsOK()); - ASSERT_EQ(0, rs->Size()); - - env.EnableDeployStats(); - - absl::SleepFor(absl::Seconds(3)); - - // HandleSQL exists only for purpose of printing - HandleSQL(QueryDeployResponseTime); - rs = sr->ExecuteSQLParameterized("", QueryDeployResponseTime, {}, &status); - ASSERT_TRUE(status.IsOK()); - ASSERT_EQ(0, rs->Size()); -} - -TEST_P(DBSDKTest, DeployStatsEnabledAfterSetGlobal) { - auto cli = GetParam(); - cs = cli->cs; - sr = cli->sr; - - // FIXME(#1547): test skiped due to Deploy Response Time can't enable in standalone mode - if (cs->IsClusterMode()) { - DeploymentEnv env(sr); - env.SetUp(); - env.EnableDeployStats(); - // sleep a while for global variable notification - absl::SleepFor(absl::Seconds(2)); - - hybridse::sdk::Status status; - auto rs = sr->ExecuteSQLParameterized("", QueryDeployResponseTime, {}, &status); - ASSERT_TRUE(status.IsOK()); - // as deploy stats in tablet is lazy managed, the deploy stats will stay empty util the first procedure call - // happens - ASSERT_EQ(0, rs->Size()); - - // warm up deploy stats - env.CallDeployProcedureBatch(); - env.CallDeployProcedure(); - - absl::SleepFor(absl::Seconds(3)); - - HandleSQL(QueryDeployResponseTime); - rs = sr->ExecuteSQLParameterized("", QueryDeployResponseTime, {}, &status); - ASSERT_TRUE(status.IsOK()); - ASSERT_EQ(TIME_DISTRIBUTION_BUCKET_COUNT, rs->Size()); - - int cnt = 0; - while (rs->Next()) { - EXPECT_EQ(absl::StrCat(env.db_, ".", env.dp_name_), rs->GetAsStringUnsafe(0)); - cnt += rs->GetInt32Unsafe(2); - } - EXPECT_EQ(2, cnt); - } -} - -TEST_P(DBSDKTest, DeployStatsOnlyCollectDeployProcedure) { - auto cli = GetParam(); - cs = cli->cs; - sr = cli->sr; - if (cs->IsClusterMode()) { - DeploymentEnv env(sr); - env.SetUp(); - - env.EnableDeployStats(); - absl::SleepFor(absl::Seconds(2)); - - for (int i = 0; i < 5; ++i) { - env.CallProcedure(); - } - - for (int i = 0; i < 10; ++i) { - env.CallDeployProcedureBatch(); - env.CallDeployProcedure(); - } - absl::SleepFor(absl::Seconds(3)); - - HandleSQL(QueryDeployResponseTime); - hybridse::sdk::Status status; - auto rs = sr->ExecuteSQLParameterized("", QueryDeployResponseTime, {}, &status); - ASSERT_TRUE(status.IsOK()); - ASSERT_EQ(TIME_DISTRIBUTION_BUCKET_COUNT, rs->Size()); - int cnt = 0; - while (rs->Next()) { - EXPECT_EQ(absl::StrCat(env.db_, ".", env.dp_name_), rs->GetAsStringUnsafe(0)); - cnt += rs->GetInt32Unsafe(2); - } - EXPECT_EQ(10 + 10, cnt); - } -} - class StripSpaceTest : public ::testing::TestWithParam> {}; std::vector> strip_cases = { diff --git a/src/codec/codec_bench_test.cc b/src/codec/codec_bench_test.cc index 3b90515d55f..aaf314782f4 100644 --- a/src/codec/codec_bench_test.cc +++ b/src/codec/codec_bench_test.cc @@ -41,8 +41,10 @@ void RunHasTs(::openmldb::storage::DataBlock* db) { datas.emplace_back(1000, std::move(::openmldb::base::Slice(db->data, db->size))); total_block_size += db->size; } - std::string pairs; - ::openmldb::codec::EncodeRows(datas, total_block_size, &pairs); + butil::IOBuf buf; + for (const auto& pair : datas) { + Encode(pair.first, pair.second.data(), pair.second.size(), &buf); + } } void RunNoneTs(::openmldb::storage::DataBlock* db) { @@ -53,8 +55,10 @@ void RunNoneTs(::openmldb::storage::DataBlock* db) { datas.push_back(::openmldb::base::Slice(db->data, db->size)); total_block_size += db->size; } - std::string pairs; - ::openmldb::codec::EncodeRows(datas, total_block_size, &pairs); + butil::IOBuf buf; + for (const auto& v : datas) { + Encode(0, v.data(), v.size(), &buf); + } } TEST_F(CodecBenchmarkTest, ProjectTest) { diff --git a/src/codec/codec_test.cc b/src/codec/codec_test.cc index 68a9c2d7552..6c6ae99f804 100644 --- a/src/codec/codec_test.cc +++ b/src/codec/codec_test.cc @@ -34,31 +34,21 @@ class CodecTest : public ::testing::Test { ~CodecTest() {} }; -TEST_F(CodecTest, EncodeRows_empty) { - boost::container::deque> data; - std::string pairs; - int32_t size = ::openmldb::codec::EncodeRows(data, 0, &pairs); - ASSERT_EQ(size, 0); -} - -TEST_F(CodecTest, EncodeRows_invalid) { - boost::container::deque> data; - int32_t size = ::openmldb::codec::EncodeRows(data, 0, NULL); - ASSERT_EQ(size, -1); -} - TEST_F(CodecTest, EncodeRows) { boost::container::deque> data; std::string test1 = "value1"; std::string test2 = "value2"; std::string empty; - uint32_t total_block_size = test1.length() + test2.length() + empty.length(); data.emplace_back(1, std::move(::openmldb::base::Slice(test1.c_str(), test1.length()))); data.emplace_back(2, std::move(::openmldb::base::Slice(test2.c_str(), test2.length()))); data.emplace_back(3, std::move(::openmldb::base::Slice(empty.c_str(), empty.length()))); + butil::IOBuf buf; + for (const auto& pair : data) { + Encode(pair.first, pair.second.data(), pair.second.size(), &buf); + } std::string pairs; - int32_t size = ::openmldb::codec::EncodeRows(data, total_block_size, &pairs); - ASSERT_EQ(size, 3 * 12 + 6 + 6); + buf.copy_to(&pairs); + ASSERT_EQ(pairs.size(), 3 * 12 + 6 + 6); std::vector> new_data; ::openmldb::codec::Decode(&pairs, new_data); ASSERT_EQ(data.size(), new_data.size()); diff --git a/src/codec/row_codec.cc b/src/codec/row_codec.cc index 64641d4f14c..f59e45b9d1e 100644 --- a/src/codec/row_codec.cc +++ b/src/codec/row_codec.cc @@ -243,6 +243,15 @@ void Encode(uint64_t time, const char* data, const size_t size, char* buffer, ui memcpy(buffer, static_cast(data), size); } +void Encode(uint64_t time, const char* data, const size_t size, butil::IOBuf* buf) { + uint32_t total_size = 8 + size; + memrev32ifbe(&total_size); + buf->append(&total_size, 4); + memrev64ifbe(&time); + buf->append(&time, 8); + buf->append(data, size); +} + void Encode(uint64_t time, const DataBlock* data, char* buffer, uint32_t offset) { return Encode(time, data->data, data->size, buffer, offset); } @@ -259,70 +268,18 @@ void Encode(const DataBlock* data, char* buffer, uint32_t offset) { return Encode(data->data, data->size, buffer, offset); } -int32_t EncodeRows(const std::vector<::openmldb::base::Slice>& rows, uint32_t total_block_size, - std::string* body) { - if (body == NULL) { - PDLOG(WARNING, "invalid output body"); - return -1; - } - - uint32_t total_size = rows.size() * 4 + total_block_size; - if (rows.size() > 0) { - body->resize(total_size); - } - uint32_t offset = 0; - char* rbuffer = reinterpret_cast(&((*body)[0])); - for (auto lit = rows.begin(); lit != rows.end(); ++lit) { - ::openmldb::codec::Encode(lit->data(), lit->size(), rbuffer, offset); - offset += (4 + lit->size()); - } - return total_size; -} - -int32_t EncodeRows(const boost::container::deque>& rows, - uint32_t total_block_size, std::string* pairs) { - if (pairs == NULL) { - PDLOG(WARNING, "invalid output pairs"); - return -1; - } - - uint32_t total_size = rows.size() * (8 + 4) + total_block_size; - if (rows.size() > 0) { - pairs->resize(total_size); - } - - char* rbuffer = reinterpret_cast(&((*pairs)[0])); - uint32_t offset = 0; - for (auto lit = rows.begin(); lit != rows.end(); ++lit) { - ::openmldb::codec::Encode(lit->first, lit->second.data(), lit->second.size(), rbuffer, offset); - offset += (4 + 8 + lit->second.size()); - } - return total_size; -} - -void EncodeFull(const std::string& pk, uint64_t time, const char* data, const size_t size, char* buffer, - uint32_t offset) { - buffer += offset; +void EncodeFull(const std::string& pk, uint64_t time, const char* data, const size_t size, butil::IOBuf* buf) { uint32_t pk_size = pk.length(); uint32_t total_size = 8 + pk_size + size; DEBUGLOG("encode total size %u pk size %u", total_size, pk_size); - memcpy(buffer, static_cast(&total_size), 4); - memrev32ifbe(buffer); - buffer += 4; - memcpy(buffer, static_cast(&pk_size), 4); - memrev32ifbe(buffer); - buffer += 4; - memcpy(buffer, static_cast(&time), 8); - memrev64ifbe(buffer); - buffer += 8; - memcpy(buffer, static_cast(pk.c_str()), pk_size); - buffer += pk_size; - memcpy(buffer, static_cast(data), size); -} - -void EncodeFull(const std::string& pk, uint64_t time, const DataBlock* data, char* buffer, - uint32_t offset) { - return EncodeFull(pk, time, data->data, data->size, buffer, offset); + memrev32ifbe(&total_size); + buf->append(&total_size, 4); + memrev32ifbe(&pk_size); + buf->append(&pk_size, 4); + memrev64ifbe(&time); + buf->append(&time, 8); + buf->append(pk); + buf->append(data, size); } void Decode(const std::string* str, std::vector>& pairs) { // NOLINT diff --git a/src/codec/row_codec.h b/src/codec/row_codec.h index 5f4f01b9690..f2ac1f69ea7 100644 --- a/src/codec/row_codec.h +++ b/src/codec/row_codec.h @@ -24,6 +24,7 @@ #include "base/status.h" #include "boost/container/deque.hpp" +#include "butil/iobuf.h" #include "codec/codec.h" #include "storage/segment.h" @@ -70,23 +71,15 @@ bool DecodeRows(const std::string& data, uint32_t count, const Schema& schema, void Encode(uint64_t time, const char* data, const size_t size, char* buffer, uint32_t offset); +void Encode(uint64_t time, const char* data, const size_t size, butil::IOBuf* buf); + void Encode(uint64_t time, const DataBlock* data, char* buffer, uint32_t offset); void Encode(const char* data, const size_t size, char* buffer, uint32_t offset); void Encode(const DataBlock* data, char* buffer, uint32_t offset); -int32_t EncodeRows(const std::vector<::openmldb::base::Slice>& rows, uint32_t total_block_size, - std::string* body); - -int32_t EncodeRows(const boost::container::deque>& rows, - uint32_t total_block_size, std::string* pairs); -// encode pk, ts and value -void EncodeFull(const std::string& pk, uint64_t time, const char* data, const size_t size, char* buffer, - uint32_t offset); - -void EncodeFull(const std::string& pk, uint64_t time, const DataBlock* data, char* buffer, - uint32_t offset); +void EncodeFull(const std::string& pk, uint64_t time, const char* data, const size_t size, butil::IOBuf* buf); void Decode(const std::string* str, std::vector>& pairs); // NOLINT diff --git a/src/datacollector/data_collector.cc b/src/datacollector/data_collector.cc index 8cf02a3ab2b..1af941226cf 100644 --- a/src/datacollector/data_collector.cc +++ b/src/datacollector/data_collector.cc @@ -33,6 +33,8 @@ DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_int32(thread_pool_size); DECLARE_int32(zk_session_timeout); DECLARE_int32(zk_keep_alive_check_interval); @@ -179,7 +181,8 @@ bool DataCollectorImpl::Init(const std::string& endpoint) { } bool DataCollectorImpl::Init(const std::string& zk_cluster, const std::string& zk_path, const std::string& endpoint) { zk_client_ = std::make_shared(zk_cluster, FLAGS_zk_session_timeout, endpoint, zk_path, - zk_path + kDataCollectorRegisterPath); + zk_path + kDataCollectorRegisterPath, + FLAGS_zk_auth_schema, FLAGS_zk_cert); if (!zk_client_->Init()) { LOG(WARNING) << "fail to init zk client"; return false; @@ -255,8 +258,8 @@ void DataCollectorImpl::CreateTaskEnv(const datasync::AddSyncTaskRequest* reques } auto tablet_client = tablet_client_map_[tablet_endpoint]; api::TableStatus table_status; - if (!tablet_client->GetTableStatus(tid, pid, table_status)) { - SET_RESP_AND_WARN(response, -1, "get table status from tablet server failed, maybe table doesn't exist"); + if (auto st = tablet_client->GetTableStatus(tid, pid, table_status); !st.OK()) { + SET_RESP_AND_WARN(response, -1, "get table status from tablet server failed, maybe table doesn't exist: " + st.GetMsg()); return; } if (!ValidateTableStatus(table_status)) { diff --git a/src/flags.cc b/src/flags.cc index bed34c0150d..42e085781eb 100644 --- a/src/flags.cc +++ b/src/flags.cc @@ -30,6 +30,8 @@ DEFINE_uint32(tablet_heartbeat_timeout, 5 * 60 * 1000, "config the heartbeat of DEFINE_uint32(tablet_offline_check_interval, 1000, "config the check interval of tablet offline. unit is milliseconds"); DEFINE_string(zk_cluster, "", "config the zookeeper cluster eg ip:2181,ip2:2181,ip3:2181"); DEFINE_string(zk_root_path, "/openmldb", "config the root path of zookeeper"); +DEFINE_string(zk_auth_schema, "digest", "config the id of authentication schema"); +DEFINE_string(zk_cert, "", "config the application credentials"); DEFINE_string(tablet, "", "config the endpoint of tablet"); DEFINE_string(nameserver, "", "config the endpoint of nameserver"); DEFINE_int32(zk_keep_alive_check_interval, 15000, "config the interval of keep alive check. unit is milliseconds"); diff --git a/src/nameserver/cluster_info.cc b/src/nameserver/cluster_info.cc index de30fc8d18f..ec685ce8b3f 100644 --- a/src/nameserver/cluster_info.cc +++ b/src/nameserver/cluster_info.cc @@ -94,7 +94,8 @@ void ClusterInfo::UpdateNSClient(const std::vector& children) { int ClusterInfo::Init(std::string& msg) { zk_client_ = std::make_shared<::openmldb::zk::ZkClient>(cluster_add_.zk_endpoints(), FLAGS_zk_session_timeout, "", - cluster_add_.zk_path(), cluster_add_.zk_path() + "/leader"); + cluster_add_.zk_path(), cluster_add_.zk_path() + "/leader", + cluster_add_.zk_auth_schema(), cluster_add_.zk_cert()); bool ok = zk_client_->Init(); for (int i = 1; i < 3; i++) { if (ok) { diff --git a/src/nameserver/name_server_create_remote_test.cc b/src/nameserver/name_server_create_remote_test.cc index 0075999b645..def3d1d0a07 100644 --- a/src/nameserver/name_server_create_remote_test.cc +++ b/src/nameserver/name_server_create_remote_test.cc @@ -43,8 +43,6 @@ DECLARE_uint32(name_server_task_max_concurrency); DECLARE_uint32(system_table_replica_num); DECLARE_bool(auto_failover); -using ::openmldb::zk::ZkClient; - namespace openmldb { namespace nameserver { diff --git a/src/nameserver/name_server_impl.cc b/src/nameserver/name_server_impl.cc index 862ee42d320..d9ce3aff439 100644 --- a/src/nameserver/name_server_impl.cc +++ b/src/nameserver/name_server_impl.cc @@ -51,6 +51,8 @@ DECLARE_string(endpoint); DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_string(tablet); DECLARE_int32(zk_session_timeout); DECLARE_int32(zk_keep_alive_check_interval); @@ -72,7 +74,6 @@ DECLARE_int32(make_snapshot_time); DECLARE_int32(make_snapshot_check_interval); DECLARE_bool(use_name); DECLARE_bool(enable_distsql); -DECLARE_uint32(sync_deploy_stats_timeout); namespace openmldb { namespace nameserver { @@ -1411,7 +1412,8 @@ bool NameServerImpl::Init(const std::string& zk_cluster, const std::string& zk_p zone_info_.set_replica_alias(""); zone_info_.set_zone_term(1); LOG(INFO) << "zone name " << zone_info_.zone_name(); - zk_client_ = new ZkClient(zk_cluster, real_endpoint, FLAGS_zk_session_timeout, endpoint, zk_path); + zk_client_ = new ZkClient(zk_cluster, real_endpoint, FLAGS_zk_session_timeout, endpoint, zk_path, + FLAGS_zk_auth_schema, FLAGS_zk_cert); if (!zk_client_->Init()) { PDLOG(WARNING, "fail to init zookeeper with cluster[%s]", zk_cluster.c_str()); return false; @@ -1470,8 +1472,6 @@ bool NameServerImpl::Init(const std::string& zk_cluster, const std::string& zk_p task_vec_.resize(FLAGS_name_server_task_max_concurrency + FLAGS_name_server_task_concurrency_for_replica_cluster); task_thread_pool_.DelayTask(FLAGS_make_snapshot_check_interval, boost::bind(&NameServerImpl::SchedMakeSnapshot, this)); - task_thread_pool_.DelayTask(FLAGS_sync_deploy_stats_timeout, - boost::bind(&NameServerImpl::ScheduleSyncDeployStats, this)); return true; } @@ -3713,6 +3713,69 @@ void NameServerImpl::CreateTable(RpcController* controller, const CreateTableReq } } +void NameServerImpl::TruncateTable(RpcController* controller, const TruncateTableRequest* request, + TruncateTableResponse* response, Closure* done) { + brpc::ClosureGuard done_guard(done); + const std::string& db = request->db(); + const std::string& name = request->name(); + std::shared_ptr<::openmldb::nameserver::TableInfo> table_info; + { + std::lock_guard lock(mu_); + if (!GetTableInfoUnlock(request->name(), request->db(), &table_info)) { + PDLOG(WARNING, "table[%s] does not exist in db [%s]", name.c_str(), db.c_str()); + response->set_code(::openmldb::base::ReturnCode::kTableIsNotExist); + response->set_msg("table does not exist"); + return; + } + if (IsExistActiveOp(db, name)) { + PDLOG(WARNING, "there is active op. db [%s] name [%s]", db.c_str(), name.c_str()); + response->set_code(::openmldb::base::ReturnCode::kOPAlreadyExists); + response->set_msg("there is active op"); + return; + } + } + uint32_t tid = table_info->tid(); + for (const auto& partition : table_info->table_partition()) { + uint32_t offset = 0; + for (const auto& partition_meta : partition.partition_meta()) { + if (partition_meta.offset() != offset) { + if (offset == 0) { + offset = partition_meta.offset(); + } else { + PDLOG(WARNING, "table[%s] partition [%d] offset mismatch", name.c_str(), partition.pid()); + response->set_code(::openmldb::base::ReturnCode::kOffsetMismatch); + response->set_msg("partition offset mismatch"); + return; + } + } + } + } + for (const auto& partition : table_info->table_partition()) { + uint32_t pid = partition.pid(); + for (const auto& partition_meta : partition.partition_meta()) { + const auto& endpoint = partition_meta.endpoint(); + auto tablet_ptr = GetTablet(endpoint); + if (!tablet_ptr) { + PDLOG(WARNING, "endpoint[%s] can not find client", endpoint.c_str()); + response->set_code(::openmldb::base::ReturnCode::kGetTabletFailed); + response->set_msg("fail to get client, endpint " + endpoint); + return; + } + auto status = tablet_ptr->client_->TruncateTable(tid, pid); + if (!status.OK()) { + PDLOG(WARNING, "truncate failed, tid[%u] pid[%u] endpoint[%s] msg [%s]", + tid, pid, endpoint.c_str(), status.GetMsg().c_str()); + response->set_code(::openmldb::base::ReturnCode::kTruncateTableFailed); + response->set_msg(status.GetMsg()); + return; + } + } + } + PDLOG(INFO, "truncate success, db[%s] name[%s]", db.c_str(), name.c_str()); + response->set_code(::openmldb::base::ReturnCode::kOk); + response->set_msg("ok"); +} + bool NameServerImpl::SaveTableInfo(std::shared_ptr table_info) { std::string table_value; table_info->SerializeToString(&table_value); @@ -5009,8 +5072,8 @@ void NameServerImpl::UpdateTableStatus() { pos_response.reserve(16); for (const auto& kv : tablet_ptr_map) { ::openmldb::api::GetTableStatusResponse tablet_status_response; - if (!kv.second->client_->GetTableStatus(tablet_status_response)) { - PDLOG(WARNING, "get table status failed! endpoint[%s]", kv.first.c_str()); + if (auto st = kv.second->client_->GetTableStatus(tablet_status_response); !st.OK()) { + PDLOG(WARNING, "get table status failed! endpoint[%s], %s", kv.first.c_str(), st.GetMsg()); continue; } for (int pos = 0; pos < tablet_status_response.all_table_status_size(); pos++) { @@ -9328,21 +9391,6 @@ void NameServerImpl::DropProcedureOnTablet(const std::string& db_name, const std } } - bool is_deployment_procedure = false; - { - std::lock_guard lock(mu_); - auto it = db_sp_info_map_.find(db_name); - if (it != db_sp_info_map_.end()) { - auto iit = it->second.find(sp_name); - is_deployment_procedure = iit != it->second.end() && iit->second->type() == type::kReqDeployment; - } - } - std::shared_ptr info; - auto success = GetTableInfo(DEPLOY_RESPONSE_TIME, INFORMATION_SCHEMA_DB, &info); - // NOTE: deploy stats records will delete even when global setting deploy_stats is turned off while there are - // records for previous deploy query (during deploy_stats = 'on') - bool drop_deploy_stats = is_deployment_procedure && success && info != nullptr; - for (auto tb_client : tb_client_vec) { if (!tb_client->DropProcedure(db_name, sp_name)) { PDLOG(WARNING, "drop procedure on tablet failed. db_name[%s], sp_name[%s], endpoint[%s]", db_name.c_str(), @@ -9353,44 +9401,6 @@ void NameServerImpl::DropProcedureOnTablet(const std::string& db_name, const std PDLOG(INFO, "drop procedure on tablet success. db_name[%s], sp_name[%s], endpoint[%s]", db_name.c_str(), sp_name.c_str(), tb_client->GetEndpoint().c_str()); } - - if (drop_deploy_stats && info->table_partition_size() > 0) { - std::string endpoint; - for (auto& meta : info->table_partition()[0].partition_meta()) { - if (meta.is_leader()) { - endpoint = meta.endpoint(); - } - } - auto tablet_info = GetTablet(endpoint); - if (tablet_info == nullptr) { - PDLOG(ERROR, "no leader exists for system table %s", DEPLOY_RESPONSE_TIME); - return; - } - - auto tb_client = tablet_info->client_; - - auto deploy_name = absl::StrCat(db_name, ".", sp_name); - uint32_t pid = static_cast(::openmldb::base::hash64(deploy_name) % info->table_partition_size()); - auto time = absl::Microseconds(1); - int cnt = 0; - std::string msg; - while (cnt++ < TIME_DISTRIBUTION_BUCKET_COUNT - 1) { - auto key = absl::StrCat(deploy_name, "|", statistics::GetDurationAsStr(time, statistics::TimeUnit::SECOND)); - if (!tb_client->Delete(info->tid(), pid, key, "", msg)) { - // NOTE: some warning appears but is expected, just ingore: - // 1. when you create a deploy query but not call it any time before delete it - // 2. deploy_stats is always turned off - PDLOG(WARNING, "failed to delete entry in %s in tablet %s where key = %s : %s", DEPLOY_RESPONSE_TIME, - tb_client->GetEndpoint(), key, msg); - } - time *= 10; - } - auto key = absl::StrCat(deploy_name, "|", MAX_STRING); - if (!tb_client->Delete(info->tid(), pid, key, "", msg)) { - PDLOG(WARNING, "failed to delete entry in %s in tablet %s where key = %s : %s", DEPLOY_RESPONSE_TIME, - tb_client->GetEndpoint(), key, msg); - } - } } void NameServerImpl::DropProcedure(RpcController* controller, const api::DropProcedureRequest* request, @@ -9908,143 +9918,6 @@ base::Status NameServerImpl::InitGlobalVarTable() { return {}; } -static const std::string& QueryDeployStats() { - static const std::string query_deploy_stats = - absl::StrCat("select * from ", nameserver::INFORMATION_SCHEMA_DB, ".", nameserver::DEPLOY_RESPONSE_TIME); - return query_deploy_stats; -} - -static const std::string& QueryDeployStatsIsOn() { - static const std::string query_deploy_stats_is_on = - absl::StrCat("select * from ", nameserver::INFORMATION_SCHEMA_DB, ".", nameserver::GLOBAL_VARIABLES, - " where Variable_name = 'deploy_stats'"); - return query_deploy_stats_is_on; -} - -void NameServerImpl::SyncDeployStats() { - // Step one: check condition for deploy stats. only all of those meet: - // 1. current ns is master - // 2. deploy_stats global variable is set to 'on' or 'true' - if (startup_mode_ == type::kStandalone && running_.load(std::memory_order_acquire)) { - DLOG(INFO) << "sync deploy stats skipped for non-leader ns on cluster"; - FreeSdkConnection(); - return; - } - - if (!GetSdkConnection()) { - LOG(ERROR) << "failed to get sdk connection"; - return; - } - auto sr = std::atomic_load_explicit(&sr_, std::memory_order_acquire); - if (sr == nullptr) { - LOG(ERROR) << "sdk connection is null"; - return; - } - ::hybridse::sdk::Status s; - auto rs = sr->ExecuteSQLParameterized("", QueryDeployStatsIsOn(), {}, &s); - if (!s.IsOK()) { - LOG(ERROR) << "[ERROR] query global variable deploy_stats: " << s.msg; - return; - } - - bool sync_stats = false; - while (rs->Next()) { - auto val = rs->GetStringUnsafe(1); - sync_stats = (val == "on" || val == "true"); - } - - if (!sync_stats) { - DLOG(INFO) << "sync deploy stats skipped when deploy_stats is off"; - return; - } - - // Step two: Fetch And Flush deploy stats from each tablet - std::unordered_map> active_tablets; - { - std::lock_guard lock(mu_); - for (auto& kv : tablets_) { - if (kv.second->Health()) { - active_tablets.emplace(kv.first, kv.second->client_); - } - } - } - statistics::DeployResponseTimeRowReducer reducer; - for (auto& client : active_tablets) { - ::openmldb::api::DeployStatsResponse res; - if (!client.second->GetAndFlushDeployStats(&res)) { - LOG(ERROR) << "GetAndFlushDeployStats from " << client.first << " failed "; - continue; - } - - for (auto& r : res.rows()) { - reducer.Reduce(r.deploy_name(), - statistics::ParseDurationFromStr(r.time(), statistics::TimeUnit::MICRO_SECOND), r.count(), - statistics::ParseDurationFromStr(r.total(), statistics::TimeUnit::MICRO_SECOND)); - } - } - - // Step three: Query old deploy response time from table - rs = sr->ExecuteSQLParameterized("", QueryDeployStats(), {}, &s); - if (!s.IsOK()) { - LOG(ERROR) << "[ERROR] querying DEPLOY_RESPONSE_TIME" << s.msg; - return; - } - - // old reducer help find those rows already in table and but there is - // no new incremental stats reduced from all tablets - statistics::DeployResponseTimeRowReducer old_reducer; - while (rs->Next()) { - auto name = rs->GetAsStringUnsafe(0); - auto time = rs->GetAsStringUnsafe(1); - uint64_t cnt = 0; - if (rs->GetSchema()->GetColumnType(2) == hybridse::sdk::DataType::kTypeInt64) { - cnt = static_cast(rs->GetInt64Unsafe(2)); - } else { - cnt = static_cast(rs->GetInt32Unsafe(2)); - } - auto total = rs->GetAsStringUnsafe(3); - - auto ts = statistics::ParseDurationFromStr(time, statistics::TimeUnit::SECOND); - auto tt = statistics::ParseDurationFromStr(total, statistics::TimeUnit::SECOND); - - reducer.Reduce(name, ts, cnt, tt); - old_reducer.Reduce(name, ts, cnt, tt); - } - - // Step four: update DEPLOY_RESPONSE_TIME table by the new rows - // only for rows that meet any of conditiions below: - // 1. the incremental count and total is bigger than 0 - // 2. original table do not have row for that (deploy_name + time) key yet - std::string insert_deploy_stat = absl::StrCat("insert into ", nameserver::INFORMATION_SCHEMA_DB, ".", - nameserver::DEPLOY_RESPONSE_TIME, " values "); - for (auto& row : reducer.Rows()) { - auto old_it = old_reducer.Find(row->deploy_name_, row->time_); - if (old_it != nullptr && row->count_ == old_it->count_) { - // don't update table only if there is no incremental data, and there is record already in table - continue; - } - - std::string time = row->GetTimeAsStr(statistics::TimeUnit::SECOND); - auto insert_sql = absl::StrCat(insert_deploy_stat, " ( '", row->deploy_name_, "', '", - time, "', ", row->count_, - ",'", row->GetTotalAsStr(statistics::TimeUnit::SECOND), "' )"); - - hybridse::sdk::Status st; - DLOG(INFO) << "sync deploy stats: executing sql: " << insert_sql; - sr->ExecuteInsert("", insert_sql, &st); - if (!st.IsOK()) { - LOG(ERROR) << "[ERROR] insert deploy stats failed: " << st.msg; - } - } - // TODO(ace): add logs for summary how many rows affected and time cost -} - -void NameServerImpl::ScheduleSyncDeployStats() { - SyncDeployStats(); - task_thread_pool_.DelayTask(FLAGS_sync_deploy_stats_timeout, - boost::bind(&NameServerImpl::ScheduleSyncDeployStats, this)); -} - /// \beirf create a SQLClusterRouter instance for use like monitoring statistics collecting /// the actual instance is stored in `sr_` member /// @@ -10568,5 +10441,26 @@ bool NameServerImpl::IsExistActiveOp(const std::string& db, const std::string& n return false; } +bool NameServerImpl::IsExistActiveOp(const std::string& db, const std::string& name) { + for (const auto& op_list : task_vec_) { + if (op_list.empty()) { + continue; + } + for (const auto& op_data : op_list) { + if (!db.empty() && op_data->op_info_.db() != db) { + continue; + } + if (!name.empty() && op_data->op_info_.name() != name) { + continue; + } + if (op_data->op_info_.task_status() == api::TaskStatus::kInited || + op_data->op_info_.task_status() == api::TaskStatus::kDoing) { + return true; + } + } + } + return false; +} + } // namespace nameserver } // namespace openmldb diff --git a/src/nameserver/name_server_impl.h b/src/nameserver/name_server_impl.h index 4bfe84ad5f4..c8f5c56b04d 100644 --- a/src/nameserver/name_server_impl.h +++ b/src/nameserver/name_server_impl.h @@ -111,7 +111,12 @@ class NameServerImpl : public NameServer { NameServerImpl(); ~NameServerImpl() override; - + void CloseThreadpool() { + running_.store(false, std::memory_order_release); + thread_pool_.Stop(true); + task_thread_pool_.Stop(true); + UpdateTableStatus(); + } bool Init(const std::string& real_endpoint); bool Init(const std::string& zk_cluster, const std::string& zk_path, const std::string& endpoint, const std::string& real_endpoint); @@ -160,6 +165,9 @@ class NameServerImpl : public NameServer { void DropTable(RpcController* controller, const DropTableRequest* request, GeneralResponse* response, Closure* done); + void TruncateTable(RpcController* controller, const TruncateTableRequest* request, + TruncateTableResponse* response, Closure* done); + void AddTableField(RpcController* controller, const AddTableFieldRequest* request, GeneralResponse* response, Closure* done); @@ -665,11 +673,6 @@ class NameServerImpl : public NameServer { uint64_t GetTerm() const; - // write deploy statistics into table - void SyncDeployStats(); - - void ScheduleSyncDeployStats(); - bool GetSdkConnection(); void FreeSdkConnection(); @@ -683,6 +686,7 @@ class NameServerImpl : public NameServer { bool IsExistDataBase(const std::string& db); bool IsExistActiveOp(const std::string& db, const std::string& name, api::OPType op_type); + bool IsExistActiveOp(const std::string& db, const std::string& name); private: std::mutex mu_; diff --git a/src/nameserver/name_server_test.cc b/src/nameserver/name_server_test.cc index f1ad0f86eab..e01f2f5c792 100644 --- a/src/nameserver/name_server_test.cc +++ b/src/nameserver/name_server_test.cc @@ -38,13 +38,14 @@ DECLARE_string(ssd_root_path); DECLARE_string(hdd_root_path); DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_int32(zk_session_timeout); DECLARE_int32(request_timeout_ms); DECLARE_int32(zk_keep_alive_check_interval); DECLARE_int32(make_snapshot_threshold_offset); DECLARE_uint32(name_server_task_max_concurrency); DECLARE_uint32(system_table_replica_num); -DECLARE_uint32(sync_deploy_stats_timeout); DECLARE_bool(auto_failover); using brpc::Server; @@ -171,7 +172,8 @@ TEST_P(NameServerImplTest, MakesnapshotTask) { sleep(5); - ZkClient zk_client(FLAGS_zk_cluster, "", 1000, FLAGS_endpoint, FLAGS_zk_root_path); + ZkClient zk_client(FLAGS_zk_cluster, "", 1000, FLAGS_endpoint, FLAGS_zk_root_path, + FLAGS_zk_auth_schema, FLAGS_zk_cert); ok = zk_client.Init(); ASSERT_TRUE(ok); std::string op_index_node = FLAGS_zk_root_path + "/op/op_index"; @@ -1294,6 +1296,5 @@ int main(int argc, char** argv) { FLAGS_ssd_root_path = tmp_path.GetTempPath("ssd"); FLAGS_hdd_root_path = tmp_path.GetTempPath("hdd"); FLAGS_system_table_replica_num = 0; - FLAGS_sync_deploy_stats_timeout = 1000000; return RUN_ALL_TESTS(); } diff --git a/src/nameserver/new_server_env_test.cc b/src/nameserver/new_server_env_test.cc index e05d1bc509c..405e3f436e0 100644 --- a/src/nameserver/new_server_env_test.cc +++ b/src/nameserver/new_server_env_test.cc @@ -34,6 +34,8 @@ DECLARE_string(endpoint); DECLARE_string(db_root_path); DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_int32(zk_session_timeout); DECLARE_int32(request_timeout_ms); DECLARE_int32(request_timeout_ms); @@ -108,7 +110,8 @@ void SetSdkEndpoint(::openmldb::RpcClient<::openmldb::nameserver::NameServer_Stu void ShowNameServer(std::map* map) { std::shared_ptr<::openmldb::zk::ZkClient> zk_client; - zk_client = std::make_shared<::openmldb::zk::ZkClient>(FLAGS_zk_cluster, "", 1000, "", FLAGS_zk_root_path); + zk_client = std::make_shared<::openmldb::zk::ZkClient>(FLAGS_zk_cluster, "", 1000, "", FLAGS_zk_root_path, + FLAGS_zk_auth_schema, FLAGS_zk_cert); if (!zk_client->Init()) { ASSERT_TRUE(false); } diff --git a/src/proto/name_server.proto b/src/proto/name_server.proto index b0eb526d8e7..219b83a0b73 100755 --- a/src/proto/name_server.proto +++ b/src/proto/name_server.proto @@ -121,6 +121,16 @@ message DropTableRequest { optional string db = 4 [default = ""]; } +message TruncateTableRequest { + optional string name = 1; + optional string db = 2; +} + +message TruncateTableResponse { + optional int32 code = 1; + optional string msg = 2; +} + message LoadTableRequest { optional string name = 1; optional string endpoint = 2; @@ -365,6 +375,8 @@ message ClusterAddress { optional string zk_endpoints = 1; optional string zk_path = 2; optional string alias = 3; + optional string zk_auth_schema = 4; + optional string zk_cert = 5; } message GeneralRequest {} @@ -529,6 +541,7 @@ message DeploySQLResponse { service NameServer { rpc CreateTable(CreateTableRequest) returns (GeneralResponse); rpc DropTable(DropTableRequest) returns (GeneralResponse); + rpc TruncateTable(TruncateTableRequest) returns (TruncateTableResponse); rpc ShowTablet(ShowTabletRequest) returns (ShowTabletResponse); rpc ShowTable(ShowTableRequest) returns (ShowTableResponse); rpc MakeSnapshotNS(MakeSnapshotNSRequest) returns (GeneralResponse); diff --git a/src/proto/tablet.proto b/src/proto/tablet.proto index 2944794b0d9..ee0ec5beae1 100755 --- a/src/proto/tablet.proto +++ b/src/proto/tablet.proto @@ -363,6 +363,16 @@ message DropTableResponse { optional string msg = 2; } +message TruncateTableRequest { + optional int32 tid = 1; + optional int32 pid = 2; +} + +message TruncateTableResponse { + optional int32 code = 1; + optional string msg = 2; +} + message GetTableSchemaRequest { optional int32 tid = 1; optional int32 pid = 2; @@ -829,7 +839,7 @@ message BulkLoadInfoResponse { required uint32 key = 1; // TODO(hw): java will use int, cpp uses uint32. Not good? required uint32 value = 2; } - repeated MapFieldEntry ts_idx_map = 2; // TODO(hw): proto3 supports map + repeated MapFieldEntry ts_idx_map = 2; // TODO(hw): proto3 can build map in proto2 syntax } repeated Segment segment = 1; } @@ -905,6 +915,7 @@ service TabletServer { rpc CreateTable(CreateTableRequest) returns (CreateTableResponse); rpc LoadTable(LoadTableRequest) returns (GeneralResponse); rpc DropTable(DropTableRequest) returns (DropTableResponse); + rpc TruncateTable(TruncateTableRequest) returns (TruncateTableResponse); rpc GetTableStatus(GetTableStatusRequest) returns (GetTableStatusResponse); rpc GetTableSchema(GetTableSchemaRequest) returns (GetTableSchemaResponse); rpc GetTableFollower(GetTableFollowerRequest) returns (GetTableFollowerResponse); diff --git a/src/replica/snapshot_replica_test.cc b/src/replica/snapshot_replica_test.cc index a9302050142..05e9a9d01da 100644 --- a/src/replica/snapshot_replica_test.cc +++ b/src/replica/snapshot_replica_test.cc @@ -93,7 +93,8 @@ TEST_P(SnapshotReplicaTest, AddReplicate) { sleep(1); ::openmldb::api::TableStatus table_status; - ASSERT_TRUE(client.GetTableStatus(tid, pid, table_status)); + auto st = client.GetTableStatus(tid, pid, table_status); + ASSERT_TRUE(st.OK()) << st.ToString(); ASSERT_EQ(::openmldb::api::kTableNormal, table_status.state()); ret = client.DelReplica(tid, pid, end_point); diff --git a/src/sdk/CMakeLists.txt b/src/sdk/CMakeLists.txt index cc959f6a23b..28c41e09c52 100644 --- a/src/sdk/CMakeLists.txt +++ b/src/sdk/CMakeLists.txt @@ -26,6 +26,9 @@ if(TESTING_ENABLE) add_executable(db_sdk_test db_sdk_test.cc) target_link_libraries(db_sdk_test ${SDK_TEST_DEPS} ${BIN_LIBS} ${THIRD_LIBS}) + add_executable(sdk_util_test sdk_util_test.cc) + target_link_libraries(sdk_util_test ${SDK_TEST_DEPS} ${BIN_LIBS} ${THIRD_LIBS}) + add_executable(result_set_sql_test result_set_sql_test.cc) target_link_libraries(result_set_sql_test ${SDK_TEST_DEPS} ${BIN_LIBS} ${THIRD_LIBS}) diff --git a/src/sdk/db_sdk.cc b/src/sdk/db_sdk.cc index c04e86d4f03..0f551853740 100644 --- a/src/sdk/db_sdk.cc +++ b/src/sdk/db_sdk.cc @@ -207,7 +207,9 @@ void ClusterSDK::CheckZk() { bool ClusterSDK::Init() { zk_client_ = new ::openmldb::zk::ZkClient(options_.zk_cluster, "", options_.zk_session_timeout, "", - options_.zk_path); + options_.zk_path, + options_.zk_auth_schema, + options_.zk_cert); bool ok = zk_client_->Init(options_.zk_log_level, options_.zk_log_file); if (!ok) { diff --git a/src/sdk/db_sdk.h b/src/sdk/db_sdk.h index 71e3e321241..c6d2cfbab76 100644 --- a/src/sdk/db_sdk.h +++ b/src/sdk/db_sdk.h @@ -43,11 +43,14 @@ struct ClusterOptions { int32_t zk_session_timeout = 2000; int32_t zk_log_level = 3; std::string zk_log_file; + std::string zk_auth_schema = "digest"; + std::string zk_cert; std::string to_string() { std::stringstream ss; ss << "zk options [cluster:" << zk_cluster << ", path:" << zk_path << ", zk_session_timeout:" << zk_session_timeout - << ", log_level:" << zk_log_level << ", log_file:" << zk_log_file << "]"; + << ", log_level:" << zk_log_level << ", log_file:" << zk_log_file + << ", zk_auth_schema:" << zk_auth_schema << ", zk_cert:" << zk_cert << "]"; return ss.str(); } }; diff --git a/src/sdk/interactive.h b/src/sdk/interactive.h new file mode 100644 index 00000000000..c4480da9bc7 --- /dev/null +++ b/src/sdk/interactive.h @@ -0,0 +1,144 @@ +/* + * Copyright 2021 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_SDK_INTERACTIVE_H_ +#define SRC_SDK_INTERACTIVE_H_ + +#include +#include + +#include "base/status.h" + +namespace openmldb { +namespace sdk { + +inline const std::string DROP_TABLE_MSG = + "DROP TABLE is a dangerous operation. Once deleted, it is very difficult to recover. \n" + "You may also note that: \n" + "- If a snapshot of a partition is being generated while dropping a table, " + "the partition will not be deleted successfully.\n" + "- By default, the deleted data is moved to the folder `recycle`.\n" + "Please refer to this link for more details: " + base::NOTICE_URL; + +inline const std::string DROP_DEPLOYMENT_MSG = + "- DROP DEPLOYMENT will not delete the index that is created automatically.\n" + "- DROP DEPLOYMENT will not delete data in the pre-aggregation table in the long window setting."; + +inline const std::string DROP_INDEX_MSG = + "DROP INDEX is a dangerous operation. Once deleted, it is very difficult to recover.\n" + "You may also note that: \n" + "- You have to wait for 2 garbage collection intervals (gc_interval) to create the same index.\n" + "- The index will not be deleted immediately, " + "it remains until after 2 garbage collection intervals.\n" + "Please refer to the doc for more details: " + base::NOTICE_URL; + +inline const std::string DROP_FUNCTION_MSG = + "This will lead to execution failure or system crash " + "if any active deployment is using the function."; + +enum class CmdType { + kDrop = 1, + kTruncate = 2, +}; + +enum class TargetType { + kTable = 1, + kDeployment = 2, + kIndex = 3, + kFunction = 4, + kProcedure = 5, +}; + +class InteractiveValidator { + public: + InteractiveValidator() = default; + explicit InteractiveValidator(bool interactive) : interactive_(interactive) {} + + bool Interactive() { return interactive_; } + void SetInteractive(bool interactive) { interactive_ = interactive; } + + bool Check(CmdType cmd_type, TargetType target, const std::string& name) { + if (!interactive_) { + return true; + } + std::string msg; + if (cmd_type == CmdType::kDrop) { + switch (target) { + case TargetType::kTable: + msg = DROP_TABLE_MSG; + break; + case TargetType::kDeployment: + msg = DROP_DEPLOYMENT_MSG; + break; + case TargetType::kIndex: + msg = DROP_INDEX_MSG; + break; + case TargetType::kFunction: + msg = DROP_FUNCTION_MSG; + break; + default: + break; + } + } + if (!msg.empty()) { + printf("%s\n", msg.c_str()); + } + std::string cmd_str = CmdType2Str(cmd_type); + std::string target_str = TargetType2Str(target); + printf("%s %s %s? yes/no\n", cmd_str.c_str(), target_str.c_str(), name.c_str()); + std::string input; + std::cin >> input; + std::transform(input.begin(), input.end(), input.begin(), ::tolower); + if (input != "yes") { + printf("'%s %s' cmd is canceled!\n", cmd_str.c_str(), name.c_str()); + return false; + } + return true; + } + + private: + std::string CmdType2Str(CmdType type) { + if (type == CmdType::kDrop) { + return "Drop"; + } else { + return "Truncate"; + } + } + + std::string TargetType2Str(TargetType type) { + switch (type) { + case TargetType::kTable: + return "table"; + case TargetType::kDeployment: + return "deployment"; + case TargetType::kIndex: + return "index"; + case TargetType::kFunction: + return "function"; + default: + return ""; + } + return ""; + } + + private: + bool interactive_ = false; +}; + +} // namespace sdk +} // namespace openmldb + +#endif // SRC_SDK_INTERACTIVE_H_ diff --git a/src/sdk/mini_cluster.h b/src/sdk/mini_cluster.h index 321df05b761..439a311f243 100644 --- a/src/sdk/mini_cluster.h +++ b/src/sdk/mini_cluster.h @@ -105,7 +105,7 @@ class MiniCluster { } } sleep(4); - ::openmldb::nameserver::NameServerImpl* nameserver = new ::openmldb::nameserver::NameServerImpl(); + nameserver = new ::openmldb::nameserver::NameServerImpl(); bool ok = nameserver->Init(zk_cluster_, zk_path_, ns_endpoint, ""); if (!ok) { return false; @@ -135,6 +135,7 @@ class MiniCluster { } void Close() { + nameserver->CloseThreadpool(); ns_.Stop(10); ns_.Join(); @@ -207,7 +208,7 @@ class MiniCluster { tb_clients_.emplace(tb_endpoint, client); return true; } - + ::openmldb::nameserver::NameServerImpl* nameserver; int32_t zk_port_; brpc::Server ns_; int32_t tablet_num_; @@ -250,7 +251,7 @@ class StandaloneEnv { FLAGS_sync_deploy_stats_timeout = 2000; ns_port_ = GenRand(); std::string ns_endpoint = "127.0.0.1:" + std::to_string(ns_port_); - ::openmldb::nameserver::NameServerImpl* nameserver = new ::openmldb::nameserver::NameServerImpl(); + nameserver = new ::openmldb::nameserver::NameServerImpl(); bool ok = nameserver->Init("", "", ns_endpoint, ""); if (!ok) { return false; @@ -278,6 +279,7 @@ class StandaloneEnv { } void Close() { + nameserver->CloseThreadpool(); ns_.Stop(10); ns_.Join(); tb_server_.Stop(10); @@ -323,7 +325,7 @@ class StandaloneEnv { tb_client_ = client; return true; } - + ::openmldb::nameserver::NameServerImpl* nameserver; brpc::Server ns_; brpc::Server tb_server_; std::string ns_endpoint_; diff --git a/src/sdk/node_adapter.cc b/src/sdk/node_adapter.cc index b148c8a4ca9..ef9de07a774 100644 --- a/src/sdk/node_adapter.cc +++ b/src/sdk/node_adapter.cc @@ -225,6 +225,7 @@ bool NodeAdapter::TransformToTableDef(::hybridse::node::CreatePlanNode* create_n hybridse::node::NodePointVector distribution_list; hybridse::node::StorageMode storage_mode = hybridse::node::kMemory; + hybridse::node::CompressType compress_type = hybridse::node::kNoCompress; // different default value for cluster and standalone mode int replica_num = 1; int partition_num = 1; @@ -253,6 +254,10 @@ bool NodeAdapter::TransformToTableDef(::hybridse::node::CreatePlanNode* create_n storage_mode = dynamic_cast(table_option)->GetStorageMode(); break; } + case hybridse::node::kCompressType: { + compress_type = dynamic_cast(table_option)->GetCompressType(); + break; + } case hybridse::node::kDistributions: { distribution_list = dynamic_cast(table_option)->GetDistributionList(); @@ -293,6 +298,7 @@ bool NodeAdapter::TransformToTableDef(::hybridse::node::CreatePlanNode* create_n table->set_replica_num(replica_num); table->set_partition_num(partition_num); table->set_storage_mode(static_cast(storage_mode)); + table->set_compress_type(static_cast(compress_type)); bool has_generate_index = false; std::set index_names; std::map column_names; diff --git a/src/sdk/sdk_util.cc b/src/sdk/sdk_util.cc new file mode 100644 index 00000000000..1df87969040 --- /dev/null +++ b/src/sdk/sdk_util.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2021 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdk/sdk_util.h" + +#include +#include "codec/schema_codec.h" + +namespace openmldb { +namespace sdk { + +std::string SDKUtil::GenCreateTableSQL(const ::openmldb::nameserver::TableInfo& table_info) { + std::stringstream ss; + ss << "CREATE TABLE `" << table_info.name() << "` (\n"; + for (const auto& column : table_info.column_desc()) { + auto it = openmldb::codec::DATA_TYPE_STR_MAP.find(column.data_type()); + if (it != openmldb::codec::DATA_TYPE_STR_MAP.end()) { + ss << "`" << column.name() << "` " << it->second; + if (column.has_default_value()) { + ss << " DEFAULT '" << column.default_value() << "'"; + } + if (column.not_null()) { + ss << " NOT NULL"; + } + ss << ",\n"; + } + } + int index_cnt = 0; + for (const auto& index : table_info.column_key()) { + if (index.flag() != 0) { + continue; + } + if (index_cnt > 0) { + ss << ",\n"; + } + ss << "INDEX ("; + if (index.col_name_size() == 1) { + ss << "KEY=`" << index.col_name(0) << "`"; + } else { + ss << "KEY=("; + for (int idx = 0; idx < index.col_name_size(); idx++) { + if (idx > 0) { + ss << ","; + } + ss << "`" << index.col_name(idx) << "`"; + } + ss << ")"; + } + if (index.has_ts_name() && !index.ts_name().empty()) { + ss << ", TS=`" << index.ts_name() << "`"; + } + if (index.has_ttl()) { + ss << ", TTL_TYPE="; + if (index.ttl().ttl_type() == openmldb::type::TTLType::kAbsoluteTime) { + ss << "ABSOLUTE, TTL=" << index.ttl().abs_ttl() << "m"; + } else if (index.ttl().ttl_type() == openmldb::type::TTLType::kLatestTime) { + ss << "LATEST, TTL=" << index.ttl().lat_ttl(); + } else if (index.ttl().ttl_type() == openmldb::type::TTLType::kAbsAndLat) { + ss << "ABSANDLAT, TTL=(" << index.ttl().abs_ttl() << "m, " << index.ttl().lat_ttl() << ")"; + } else if (index.ttl().ttl_type() == openmldb::type::TTLType::kAbsOrLat) { + ss << "ABSORLAT, TTL=(" << index.ttl().abs_ttl() << "m, " << index.ttl().lat_ttl() << ")"; + } + } + index_cnt++; + ss << ")"; + } + ss << "\n) "; + ss << "OPTIONS ("; + ss << "PARTITIONNUM=" << table_info.partition_num(); + ss << ", REPLICANUM=" << table_info.replica_num(); + if (table_info.storage_mode() == openmldb::common::StorageMode::kSSD) { + ss << ", STORAGE_MODE='SSD'"; + } else if (table_info.storage_mode() == openmldb::common::StorageMode::kHDD) { + ss << ", STORAGE_MODE='HDD'"; + } else { + ss << ", STORAGE_MODE='Memory'"; + } + if (table_info.compress_type() == type::CompressType::kSnappy) { + ss << ", COMPRESS_TYPE='Snappy'"; + } else { + ss << ", COMPRESS_TYPE='NoCompress'"; + } + ss << ");"; + return ss.str(); +} + +} // namespace sdk +} // namespace openmldb diff --git a/src/sdk/sdk_util.h b/src/sdk/sdk_util.h new file mode 100644 index 00000000000..c3c6df9e407 --- /dev/null +++ b/src/sdk/sdk_util.h @@ -0,0 +1,35 @@ +/* + * Copyright 2021 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_SDK_SDK_UTIL_H_ +#define SRC_SDK_SDK_UTIL_H_ + +#include + +#include "proto/name_server.pb.h" + +namespace openmldb { +namespace sdk { + +class SDKUtil { + public: + static std::string GenCreateTableSQL(const ::openmldb::nameserver::TableInfo& table_info); +}; + +} // namespace sdk +} // namespace openmldb + +#endif // SRC_SDK_SDK_UTIL_H_ diff --git a/src/sdk/sdk_util_test.cc b/src/sdk/sdk_util_test.cc new file mode 100644 index 00000000000..c214f592cba --- /dev/null +++ b/src/sdk/sdk_util_test.cc @@ -0,0 +1,127 @@ +/* + * Copyright 2021 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "absl/strings/str_split.h" +#include "absl/strings/numbers.h" +#include "gtest/gtest.h" +#include "codec/schema_codec.h" +#include "sdk/sdk_util.h" + +namespace openmldb { +namespace sdk { + +class SDKUtilTest : public ::testing::Test { + public: + SDKUtilTest() {} + ~SDKUtilTest() {} +}; + +void SetColumnDesc(const std::vector>& col_vec, + ::openmldb::nameserver::TableInfo* table_info) { + table_info->clear_column_desc(); + for (const auto& col : col_vec) { + auto col_desc = table_info->add_column_desc(); + col_desc->set_name(col[0]); + col_desc->set_data_type(codec::DATA_TYPE_MAP.find(col[1])->second); + if (col.size() > 2 && col[2] == "no") { + col_desc->set_not_null(true); + } + if (col.size() > 3 && !col[3].empty()) { + col_desc->set_default_value(col[3]); + } + } +} + +void SetIndex(const std::vector>& index_vec, + ::openmldb::nameserver::TableInfo* table_info) { + table_info->clear_column_key(); + for (const auto& index_val : index_vec) { + auto index = table_info->add_column_key(); + index->set_index_name(index_val[0]); + std::vector list = absl::StrSplit(index_val[1], ","); + for (const auto& name : list) { + index->add_col_name(name); + } + if (!index_val[2].empty()) { + index->set_ts_name(index_val[2]); + } + auto ttl = index->mutable_ttl(); + int abs_ttl; + int lat_ttl; + ASSERT_TRUE(absl::SimpleAtoi(index_val[4], &abs_ttl)); + ASSERT_TRUE(absl::SimpleAtoi(index_val[5], &lat_ttl)); + if (index_val[3] == "absolute") { + ttl->set_ttl_type(openmldb::type::TTLType::kAbsoluteTime); + ttl->set_abs_ttl(abs_ttl); + } else if (index_val[3] == "latest") { + ttl->set_ttl_type(openmldb::type::TTLType::kLatestTime); + ttl->set_lat_ttl(lat_ttl); + } else if (index_val[3] == "absorlat") { + ttl->set_ttl_type(openmldb::type::TTLType::kAbsAndLat); + ttl->set_abs_ttl(abs_ttl); + ttl->set_lat_ttl(lat_ttl); + } else if (index_val[3] == "absandlat") { + ttl->set_ttl_type(openmldb::type::TTLType::kAbsOrLat); + ttl->set_abs_ttl(abs_ttl); + ttl->set_lat_ttl(lat_ttl); + } + } +} + +TEST_F(SDKUtilTest, GenCreateTableSQL) { + ::openmldb::nameserver::TableInfo table_info; + std::vector> col = { {"col1", "string"}, {"col2", "int"}, {"col3", "bigint", "no"}}; + std::vector> index = { {"index1", "col1", "", "absolute", "100", "0"}}; + SetColumnDesc(col, &table_info); + SetIndex(index, &table_info); + table_info.set_replica_num(1); + table_info.set_partition_num(1); + table_info.set_name("t1"); + std::string exp_ddl = "CREATE TABLE `t1` (\n" + "`col1` string,\n" + "`col2` int,\n" + "`col3` bigInt NOT NULL,\n" + "INDEX (KEY=`col1`, TTL_TYPE=ABSOLUTE, TTL=100m)\n" + ") OPTIONS (PARTITIONNUM=1, REPLICANUM=1, STORAGE_MODE='Memory');"; + ASSERT_EQ(SDKUtil::GenCreateTableSQL(table_info), exp_ddl); + std::vector> col1 = { {"col1", "string", "no", "aa"}, + {"col2", "int"}, {"col3", "timestamp"}}; + std::vector> index1 = { {"index1", "col1", "", "absolute", "100", "0"}, + {"index2", "col1,col2", "col3", "absorlat", "100", "10"}}; + SetColumnDesc(col1, &table_info); + SetIndex(index1, &table_info); + table_info.set_replica_num(3); + table_info.set_partition_num(8); + table_info.set_storage_mode(openmldb::common::StorageMode::kHDD); + exp_ddl = "CREATE TABLE `t1` (\n" + "`col1` string DEFAULT 'aa' NOT NULL,\n" + "`col2` int,\n" + "`col3` timestamp,\n" + "INDEX (KEY=`col1`, TTL_TYPE=ABSOLUTE, TTL=100m),\n" + "INDEX (KEY=(`col1`,`col2`), TS=`col3`, TTL_TYPE=ABSANDLAT, TTL=(100m, 10))\n" + ") OPTIONS (PARTITIONNUM=8, REPLICANUM=3, STORAGE_MODE='HDD');"; + ASSERT_EQ(SDKUtil::GenCreateTableSQL(table_info), exp_ddl); +} + +} // namespace sdk +} // namespace openmldb + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/sdk/sql_cluster_router.cc b/src/sdk/sql_cluster_router.cc index 296cd3d5755..1a55e94fb2e 100644 --- a/src/sdk/sql_cluster_router.cc +++ b/src/sdk/sql_cluster_router.cc @@ -55,6 +55,7 @@ #include "sdk/job_table_helper.h" #include "sdk/node_adapter.h" #include "sdk/result_set_sql.h" +#include "sdk/sdk_util.h" #include "sdk/split.h" #include "udf/udf.h" #include "vm/catalog.h" @@ -209,7 +210,7 @@ class BatchQueryFutureImpl : public QueryFuture { SQLClusterRouter::SQLClusterRouter(const SQLRouterOptions& options) : options_(std::make_shared(options)), is_cluster_mode_(true), - interactive_(false), + interactive_validator_(), cluster_sdk_(nullptr), mu_(), rand_(::baidu::common::timer::now_time()) {} @@ -217,7 +218,7 @@ SQLClusterRouter::SQLClusterRouter(const SQLRouterOptions& options) SQLClusterRouter::SQLClusterRouter(const StandaloneOptions& options) : options_(std::make_shared(options)), is_cluster_mode_(false), - interactive_(false), + interactive_validator_(), cluster_sdk_(nullptr), mu_(), rand_(::baidu::common::timer::now_time()) {} @@ -225,7 +226,7 @@ SQLClusterRouter::SQLClusterRouter(const StandaloneOptions& options) SQLClusterRouter::SQLClusterRouter(DBSDK* sdk) : options_(), is_cluster_mode_(sdk->IsClusterMode()), - interactive_(false), + interactive_validator_(), cluster_sdk_(sdk), mu_(), rand_(::baidu::common::timer::now_time()) { @@ -257,6 +258,8 @@ bool SQLClusterRouter::Init() { coptions.zk_session_timeout = ops->zk_session_timeout; coptions.zk_log_level = ops->zk_log_level; coptions.zk_log_file = ops->zk_log_file; + coptions.zk_auth_schema = ops->zk_auth_schema; + coptions.zk_cert = ops->zk_cert; cluster_sdk_ = new ClusterSDK(coptions); // TODO(hw): no detail error info bool ok = cluster_sdk_->Init(); @@ -1432,6 +1435,68 @@ bool SQLClusterRouter::ExecuteInsert(const std::string& db, const std::string& s } } +bool SQLClusterRouter::ExecuteInsert(const std::string& db, const std::string& name, int tid, int partition_num, + hybridse::sdk::ByteArrayPtr dimension, int dimension_len, + hybridse::sdk::ByteArrayPtr value, int len, hybridse::sdk::Status* status) { + RET_FALSE_IF_NULL_AND_WARN(status, "output status is nullptr"); + if (dimension == nullptr || dimension_len <= 0 || value == nullptr || len <= 0 || partition_num <= 0) { + *status = {StatusCode::kCmdError, "invalid parameter"}; + return false; + } + std::vector> tablets; + bool ret = cluster_sdk_->GetTablet(db, name, &tablets); + if (!ret || tablets.empty()) { + status->msg = "fail to get table " + name + " tablet"; + return false; + } + std::map> dimensions_map; + int pos = 0; + while (pos < dimension_len) { + int idx = *(reinterpret_cast(dimension + pos)); + pos += sizeof(int); + int key_len = *(reinterpret_cast(dimension + pos)); + pos += sizeof(int); + base::Slice key(dimension + pos, key_len); + uint32_t pid = static_cast(::openmldb::base::hash64(key.data(), key.size()) % partition_num); + auto it = dimensions_map.find(pid); + if (it == dimensions_map.end()) { + it = dimensions_map.emplace(pid, ::google::protobuf::RepeatedPtrField<::openmldb::api::Dimension>()).first; + } + auto dim = it->second.Add(); + dim->set_idx(idx); + dim->set_key(key.data(), key.size()); + pos += key_len; + } + base::Slice row_value(value, len); + uint64_t cur_ts = ::baidu::common::timer::get_micros() / 1000; + for (auto& kv : dimensions_map) { + uint32_t pid = kv.first; + if (pid < tablets.size()) { + auto tablet = tablets[pid]; + if (tablet) { + auto client = tablet->GetClient(); + if (client) { + DLOG(INFO) << "put data to endpoint " << client->GetEndpoint() << " with dimensions size " + << kv.second.size(); + bool ret = client->Put(tid, pid, cur_ts, row_value, &kv.second); + if (!ret) { + SET_STATUS_AND_WARN(status, StatusCode::kCmdError, + "INSERT failed, tid " + std::to_string(tid) + + ". Note that data might have been partially inserted. " + "You are encouraged to perform DELETE to remove any partially " + "inserted data before trying INSERT again."); + return false; + } + continue; + } + } + } + SET_STATUS_AND_WARN(status, StatusCode::kCmdError, "fail to get tablet client. pid " + std::to_string(pid)); + return false; + } + return true; +} + bool SQLClusterRouter::GetSQLPlan(const std::string& sql, ::hybridse::node::NodeManager* nm, ::hybridse::node::PlanNodeList* plan) { if (nm == NULL || plan == NULL) return false; @@ -1625,15 +1690,37 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h return ResultSetSQL::MakeResultSet({"Tables"}, values, status); } + case hybridse::node::kCmdShowCreateTable: { + auto& args = cmd_node->GetArgs(); + std::string cur_db = db; + std::string table_name; + if (!ParseNamesFromArgs(db, args, &cur_db, &table_name).IsOK()) { + *status = {StatusCode::kCmdError, msg}; + return {}; + } + if (cur_db.empty()) { + *status = {::hybridse::common::StatusCode::kCmdError, "please enter database first"}; + return {}; + } + auto table = cluster_sdk_->GetTableInfo(cur_db, table_name); + if (table == nullptr) { + *status = {StatusCode::kCmdError, "table " + table_name + " does not exist"}; + return {}; + } + std::string sql = SDKUtil::GenCreateTableSQL(*table); + std::vector> values; + std::vector vec = {table_name, sql}; + values.push_back(std::move(vec)); + return ResultSetSQL::MakeResultSet({"Table", "Create Table"}, values, status); + } + case hybridse::node::kCmdDescTable: { std::string cur_db = db; std::string table_name; const auto& args = cmd_node->GetArgs(); - if (args.size() > 1) { - cur_db = args[0]; - table_name = args[1]; - } else { - table_name = args[0]; + if (!ParseNamesFromArgs(db, args, &cur_db, &table_name).IsOK()) { + *status = {StatusCode::kCmdError, msg}; + return {}; } if (cur_db.empty()) { *status = {::hybridse::common::StatusCode::kCmdError, "please enter database first"}; @@ -1661,9 +1748,11 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h } ss.str(""); std::unordered_map options; - options["storage_mode"] = StorageMode_Name(table->storage_mode()); + std::string storage_mode = StorageMode_Name(table->storage_mode()); // remove the prefix 'k', i.e., change kMemory to Memory - options["storage_mode"] = options["storage_mode"].substr(1, options["storage_mode"].size() - 1); + options["storage_mode"] = storage_mode.substr(1, storage_mode.size() - 1); + std::string compress_type = CompressType_Name(table->compress_type()); + options["compress_type"] = compress_type.substr(1, compress_type.size() -1); ::openmldb::cmd::PrintTableOptions(options, ss); result.emplace_back(std::vector{ss.str()}); return ResultSetSQL::MakeResultSet({FORMAT_STRING_KEY}, result, status); @@ -1729,7 +1818,7 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h } case hybridse::node::kCmdDropFunction: { std::string name = cmd_node->GetArgs()[0]; - if (!CheckAnswerIfInteractive("function", name)) { + if (!interactive_validator_.Check(CmdType::kDrop, TargetType::kFunction, name)) { return {}; } auto base_status = ns_ptr->DropFunction(name, cmd_node->IsIfExists()); @@ -1785,7 +1874,7 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h return {}; } std::string sp_name = cmd_node->GetArgs()[0]; - if (!CheckAnswerIfInteractive("procedure", sp_name)) { + if (!interactive_validator_.Check(CmdType::kDrop, TargetType::kProcedure, sp_name)) { return {}; } if (ns_ptr->DropProcedure(db, sp_name, msg)) { @@ -1855,7 +1944,7 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h *status = {StatusCode::kCmdError, sp ? "not a deployment" : "deployment not found"}; return {}; } - if (!CheckAnswerIfInteractive("deployment", deploy_name)) { + if (!interactive_validator_.Check(CmdType::kDrop, TargetType::kDeployment, deploy_name)) { return {}; } if (ns_ptr->DropProcedure(db_name, deploy_name, msg)) { @@ -1932,15 +2021,11 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h *status = {}; std::string db_name = db; std::string table_name; - if (cmd_node->GetArgs().size() == 2) { - db_name = cmd_node->GetArgs()[0]; - table_name = cmd_node->GetArgs()[1]; - } else if (cmd_node->GetArgs().size() == 1) { - table_name = cmd_node->GetArgs()[0]; - } else { - *status = {StatusCode::kCmdError, "Invalid Cmd Args size"}; + if (!ParseNamesFromArgs(db, cmd_node->GetArgs(), &db_name, &table_name).IsOK()) { + *status = {StatusCode::kCmdError, msg}; + return {}; } - if (!CheckAnswerIfInteractive("table", table_name)) { + if (!interactive_validator_.Check(CmdType::kDrop, TargetType::kTable, table_name)) { return {}; } if (DropTable(db_name, table_name, cmd_node->IsIfExists(), status)) { @@ -1948,6 +2033,23 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h } return {}; } + case hybridse::node::kCmdTruncate: { + *status = {}; + std::string db_name; + std::string table_name; + if (!ParseNamesFromArgs(db, cmd_node->GetArgs(), &db_name, &table_name).IsOK()) { + *status = {StatusCode::kCmdError, msg}; + return {}; + } + if (!interactive_validator_.Check(CmdType::kTruncate, TargetType::kTable, table_name)) { + return {}; + } + auto base_status = ns_ptr->TruncateTable(db_name, table_name); + if (!base_status.OK()) { + *status = {StatusCode::kCmdError, base_status.GetMsg()}; + } + return {}; + } case hybridse::node::kCmdDropIndex: { std::string db_name = db; std::string table_name; @@ -1963,7 +2065,7 @@ std::shared_ptr SQLClusterRouter::HandleSQLCmd(const h *status = {StatusCode::kCmdError, "Invalid Cmd Args size"}; return {}; } - if (!CheckAnswerIfInteractive("index", index_name + " on " + table_name)) { + if (!interactive_validator_.Check(CmdType::kDrop, TargetType::kIndex, index_name + " on " + table_name)) { return {}; } ret = ns_ptr->DeleteIndex(db_name, table_name, index_name, msg); @@ -2025,7 +2127,7 @@ base::Status SQLClusterRouter::HandleSQLCreateTable(hybridse::node::CreatePlanNo if (!ns_ptr->CreateTable(table_info, create_node->GetIfNotExist(), msg)) { return base::Status(base::ReturnCode::kSQLCmdRunError, msg); } - if (interactive_ && table_info.column_key_size() == 0) { + if (interactive_validator_.Interactive() && table_info.column_key_size() == 0) { return base::Status{base::ReturnCode::kOk, "As there is no index specified, a default index type `absolute 0` will be created. " "The data attached to the index will never expire to be deleted. " @@ -2989,62 +3091,22 @@ ::hybridse::sdk::Status SQLClusterRouter::SetVariable(hybridse::node::SetPlanNod } ::hybridse::sdk::Status SQLClusterRouter::ParseNamesFromArgs(const std::string& db, - const std::vector& args, std::string* db_name, - std::string* sp_name) { + const std::vector& args, std::string* db_name, std::string* name) { if (args.size() == 1) { - // only sp name, no db_name if (db.empty()) { return {StatusCode::kCmdError, "Please enter database first"}; } *db_name = db; - *sp_name = args[0]; + *name = args[0]; } else if (args.size() == 2) { *db_name = args[0]; - *sp_name = args[1]; + *name = args[1]; } else { return {StatusCode::kCmdError, "Invalid args"}; } return {}; } -bool SQLClusterRouter::CheckAnswerIfInteractive(const std::string& drop_type, const std::string& name) { - if (interactive_) { - std::string msg; - if (drop_type == "table") { - msg = "DROP TABLE is a dangerous operation. Once deleted, it is very difficult to recover. \n" - "You may also note that: \n" - "- If a snapshot of a partition is being generated while dropping a table, " - "the partition will not be deleted successfully.\n" - "- By default, the deleted data is moved to the folder `recycle`.\n" - "Please refer to this link for more details: " + base::NOTICE_URL; - } else if (drop_type == "deployment") { - msg = "- DROP DEPLOYMENT will not delete the index that is created automatically.\n" - "- DROP DEPLOYMENT will not delete data in the pre-aggregation table in the long window setting."; - } else if (drop_type == "index") { - msg = "DROP INDEX is a dangerous operation. Once deleted, it is very difficult to recover.\n" - "You may also note that: \n" - "- You have to wait for 2 garbage collection intervals (gc_interval) to create the same index.\n" - "- The index will not be deleted immediately, " - "it remains until after 2 garbage collection intervals.\n" - "Please refer to the doc for more details: " + base::NOTICE_URL; - } else if (drop_type == "function") { - msg = "This will lead to execution failure or system crash if any active deployment is using the function."; - } - if (!msg.empty()) { - printf("%s\n", msg.c_str()); - } - printf("Drop %s %s? yes/no\n", drop_type.c_str(), name.c_str()); - std::string input; - std::cin >> input; - std::transform(input.begin(), input.end(), input.begin(), ::tolower); - if (input != "yes") { - printf("'Drop %s' cmd is canceled!\n", name.c_str()); - return false; - } - } - return true; -} - std::string SQLClusterRouter::GetDatabase() { std::lock_guard<::openmldb::base::SpinMutex> lock(mu_); return db_; @@ -3055,7 +3117,7 @@ void SQLClusterRouter::SetDatabase(const std::string& db) { db_ = db; } -void SQLClusterRouter::SetInteractive(bool value) { interactive_ = value; } +void SQLClusterRouter::SetInteractive(bool value) { interactive_validator_.SetInteractive(value); } ::openmldb::base::Status SQLClusterRouter::SaveResultSet(const std::string& file_path, const std::shared_ptr& options_map, @@ -3775,8 +3837,8 @@ hybridse::sdk::Status SQLClusterRouter::GetNewIndex(const TableInfoMap& table_ma // update ttl auto ns_ptr = cluster_sdk_->GetNsClient(); std::string err; - if (!ns_ptr->UpdateTTL(table_name, result.ttl_type(), result.abs_ttl(), result.lat_ttl(), - old_column_key.index_name(), err)) { + if (!ns_ptr->UpdateTTL(db_name, table_name, result.ttl_type(), + result.abs_ttl(), result.lat_ttl(), old_column_key.index_name(), err)) { return {StatusCode::kCmdError, "update ttl failed"}; } } @@ -4279,7 +4341,7 @@ static const std::initializer_list GetTableStatusSchema() { // 1. memory: binlog + snapshot // 2. SSD/HDD: binlog + rocksdb data (sst files), wal files and checkpoints are not included // - Partition: partition number -// - partition_unalive: partition number that is unalive +// - Partition_unalive: partition number that is unalive // - Replica: replica number // - Offline_path: data path for offline data // - Offline_format: format for offline data @@ -4308,10 +4370,12 @@ std::shared_ptr SQLClusterRouter::ExecuteShowTableStat std::shared_ptr tablet_client; if (tablet_accessor && (tablet_client = tablet_accessor->GetClient())) { ::openmldb::api::GetTableStatusResponse response; - if (tablet_client->GetTableStatus(response)) { + if (auto st = tablet_client->GetTableStatus(response); st.OK()) { for (const auto& table_status : response.all_table_status()) { table_statuses[table_status.tid()][table_status.pid()][tablet_client->GetEndpoint()] = table_status; } + } else { + LOG(WARNING) << "get table status from tablet failed: " << st.GetMsg(); } } } @@ -4441,14 +4505,18 @@ bool SQLClusterRouter::CheckTableStatus(const std::string& db, const std::string if (tablet_accessor && (tablet_client = tablet_accessor->GetClient())) { uint64_t offset = 0; std::map info_map; - std::string msg; - tablet_client->GetTableFollower(tid, pid, offset, info_map, msg); - for (auto& meta : partition_info.partition_meta()) { - if (meta.is_leader()) continue; + auto st = tablet_client->GetTableFollower(tid, pid, offset, info_map); + // no followers is fine if replicanum == 1 + if (st.OK() || st.GetCode() == ::openmldb::base::ReturnCode::kNoFollower) { + for (auto& meta : partition_info.partition_meta()) { + if (meta.is_leader()) continue; - if (info_map.count(meta.endpoint()) == 0) { - append_error_msg(error_msg, pid, false, meta.endpoint(), "not connected to leader"); + if (info_map.count(meta.endpoint()) == 0) { + append_error_msg(error_msg, pid, false, meta.endpoint(), "not connected to leader"); + } } + } else { + append_error_msg(error_msg, pid, -1, "", absl::StrCat("fail to get from tablet: ", st.GetMsg())); } } diff --git a/src/sdk/sql_cluster_router.h b/src/sdk/sql_cluster_router.h index 033bda8d090..f5661c9a1bb 100644 --- a/src/sdk/sql_cluster_router.h +++ b/src/sdk/sql_cluster_router.h @@ -34,6 +34,7 @@ #include "nameserver/system_table.h" #include "sdk/db_sdk.h" #include "sdk/file_option_parser.h" +#include "sdk/interactive.h" #include "sdk/sql_cache.h" #include "sdk/sql_router.h" #include "sdk/table_reader_impl.h" @@ -84,6 +85,10 @@ class SQLClusterRouter : public SQLRouter { bool ExecuteInsert(const std::string& db, const std::string& sql, std::shared_ptr rows, hybridse::sdk::Status* status) override; + bool ExecuteInsert(const std::string& db, const std::string& name, int tid, int partition_num, + hybridse::sdk::ByteArrayPtr dimension, int dimension_len, + hybridse::sdk::ByteArrayPtr value, int len, hybridse::sdk::Status* status) override; + bool ExecuteDelete(std::shared_ptr row, hybridse::sdk::Status* status) override; std::shared_ptr GetTableReader() override; @@ -417,7 +422,7 @@ class SQLClusterRouter : public SQLRouter { std::string db_; std::map session_variables_; bool is_cluster_mode_; - bool interactive_; + InteractiveValidator interactive_validator_; DBSDK* cluster_sdk_; std::map>>> input_lru_cache_; diff --git a/src/sdk/sql_cluster_test.cc b/src/sdk/sql_cluster_test.cc index 359ac431573..9374841d71e 100644 --- a/src/sdk/sql_cluster_test.cc +++ b/src/sdk/sql_cluster_test.cc @@ -257,6 +257,24 @@ TEST_F(SQLClusterDDLTest, CreateTableWithDatabase) { ASSERT_TRUE(router->DropDB(db2, &status)); } +TEST_F(SQLClusterDDLTest, ShowCreateTable) { + ::hybridse::sdk::Status status; + ASSERT_TRUE(router->ExecuteDDL(db, "drop table if exists t1;", &status)); + std::string ddl = "CREATE TABLE `t1` (\n" + "`col1` varchar,\n" + "`col2` int,\n" + "`col3` bigInt NOT NULL,\n" + "INDEX (KEY=`col1`, TTL_TYPE=ABSOLUTE, TTL=100m)\n" + ") OPTIONS (PARTITIONNUM=1, REPLICANUM=1, STORAGE_MODE='Memory', COMPRESS_TYPE='NoCompress');"; + ASSERT_TRUE(router->ExecuteDDL(db, ddl, &status)) << "ddl: " << ddl; + ASSERT_TRUE(router->RefreshCatalog()); + auto rs = router->ExecuteSQL(db, "show create table t1;", &status); + ASSERT_TRUE(status.IsOK()) << status.msg; + ASSERT_TRUE(rs->Next()); + ASSERT_EQ(ddl, rs->GetStringUnsafe(1)); + ASSERT_TRUE(router->ExecuteDDL(db, "drop table t1;", &status)); +} + TEST_F(SQLClusterDDLTest, CreateTableWithDatabaseWrongDDL) { std::string name = "test" + GenRand(); ::hybridse::sdk::Status status; @@ -628,6 +646,40 @@ TEST_F(SQLSDKQueryTest, GetTabletClient) { ASSERT_TRUE(router->DropDB(db, &status)); } +TEST_F(SQLClusterTest, DeployWithMultiDB) { + SQLRouterOptions sql_opt; + sql_opt.zk_cluster = mc_->GetZkCluster(); + sql_opt.zk_path = mc_->GetZkPath(); + auto router = NewClusterSQLRouter(sql_opt); + SetOnlineMode(router); + ASSERT_TRUE(router != nullptr); + std::string base_table = "test" + GenRand(); + std::string db1 = "db1"; + std::string db2 = "db2"; + ::hybridse::sdk::Status status; + ASSERT_TRUE(router->ExecuteDDL(db1, "drop table if exists db1.t1;", &status)); + ASSERT_TRUE(router->ExecuteDDL(db2, "drop table if exists db2.t1;", &status)); + ASSERT_TRUE(router->ExecuteDDL(db1, "drop database if exists db1;", &status)); + ASSERT_TRUE(router->ExecuteDDL(db2, "drop database if exists db2;", &status)); + ASSERT_TRUE(router->CreateDB(db1, &status)); + ASSERT_TRUE(router->CreateDB(db2, &status)); + std::string sql1 = "create table db1.t1 (c1 string, c2 int, c3 bigint, c4 timestamp, index(key=c1, ts=c4));"; + std::string sql2 = "create table db2.t1 (c1 string, c2 int, c3 bigint, c4 timestamp, index(key=c1, ts=c3));"; + ASSERT_TRUE(router->ExecuteDDL(db1, sql1, &status)); + ASSERT_TRUE(router->ExecuteDDL(db2, sql2, &status)); + ASSERT_TRUE(router->ExecuteDDL(db1, "use " + db1 + ";", &status)); + std::string sql = "deploy demo select db1.t1.c1,db1.t1.c2,db2.t1.c3,db2.t1.c4 from db1.t1 " + "last join db2.t1 ORDER BY db2.t1.c3 on db1.t1.c1=db2.t1.c1;"; + ASSERT_TRUE(router->RefreshCatalog()); + router->ExecuteSQL(sql, &status); + ASSERT_TRUE(status.IsOK()); + ASSERT_TRUE(router->ExecuteDDL(db1, "drop deployment demo;", &status)); + ASSERT_TRUE(router->ExecuteDDL(db1, "drop table t1;", &status)); + ASSERT_TRUE(router->ExecuteDDL(db2, "drop table t1;", &status)); + ASSERT_TRUE(router->DropDB(db1, &status)); + ASSERT_TRUE(router->DropDB(db2, &status)); +} + TEST_F(SQLClusterTest, CreatePreAggrTable) { SQLRouterOptions sql_opt; sql_opt.zk_cluster = mc_->GetZkCluster(); diff --git a/src/sdk/sql_insert_row.h b/src/sdk/sql_insert_row.h index bee50291b3c..ded1c824e19 100644 --- a/src/sdk/sql_insert_row.h +++ b/src/sdk/sql_insert_row.h @@ -29,12 +29,78 @@ #include "codec/fe_row_codec.h" #include "node/sql_node.h" #include "proto/name_server.pb.h" +#include "schema/schema_adapter.h" #include "sdk/base.h" namespace openmldb::sdk { typedef std::shared_ptr>> DefaultValueMap; +// used in java to build InsertPreparedStatementCache +class DefaultValueContainer { + public: + explicit DefaultValueContainer(const DefaultValueMap& default_map) : default_map_(default_map) {} + + std::vector GetAllPosition() { + std::vector vec; + for (const auto& kv : *default_map_) { + vec.push_back(kv.first); + } + return vec; + } + + bool IsValid(int idx) { + return idx >= 0 && idx < Size(); + } + + int Size() { + return default_map_->size(); + } + + bool IsNull(int idx) { + return default_map_->at(idx)->IsNull(); + } + + bool GetBool(int idx) { + return default_map_->at(idx)->GetBool(); + } + + int16_t GetSmallInt(int idx) { + return default_map_->at(idx)->GetSmallInt(); + } + + int32_t GetInt(int idx) { + return default_map_->at(idx)->GetInt(); + } + + int64_t GetBigInt(int idx) { + return default_map_->at(idx)->GetLong(); + } + + float GetFloat(int idx) { + return default_map_->at(idx)->GetFloat(); + } + + double GetDouble(int idx) { + return default_map_->at(idx)->GetDouble(); + } + + int32_t GetDate(int idx) { + return default_map_->at(idx)->GetInt(); + } + + int64_t GetTimeStamp(int idx) { + return default_map_->at(idx)->GetLong(); + } + + std::string GetString(int idx) { + return default_map_->at(idx)->GetStr(); + } + + private: + DefaultValueMap default_map_; +}; + class SQLInsertRow { public: SQLInsertRow(std::shared_ptr<::openmldb::nameserver::TableInfo> table_info, @@ -81,6 +147,14 @@ class SQLInsertRow { const std::vector& stmt_column_idx_in_table, const std::shared_ptr<::hybridse::sdk::Schema>& schema); + std::shared_ptr GetDefaultValue() { + return std::make_shared(default_map_); + } + + ::openmldb::nameserver::TableInfo GetTableInfo() { + return *table_info_; + } + private: bool MakeDefault(); void PackDimension(const std::string& val); diff --git a/src/sdk/sql_router.h b/src/sdk/sql_router.h index aa12b6dff56..68186a83b00 100644 --- a/src/sdk/sql_router.h +++ b/src/sdk/sql_router.h @@ -58,6 +58,8 @@ struct SQLRouterOptions : BasicRouterOptions { std::string spark_conf_path; uint32_t zk_log_level = 3; // PY/JAVA SDK default info log std::string zk_log_file; + std::string zk_auth_schema = "digest"; + std::string zk_cert; }; struct StandaloneOptions : BasicRouterOptions { @@ -110,6 +112,10 @@ class SQLRouter { virtual bool ExecuteInsert(const std::string& db, const std::string& sql, std::shared_ptr row, hybridse::sdk::Status* status) = 0; + virtual bool ExecuteInsert(const std::string& db, const std::string& name, int tid, int partition_num, + hybridse::sdk::ByteArrayPtr dimension, int dimension_len, + hybridse::sdk::ByteArrayPtr value, int len, hybridse::sdk::Status* status) = 0; + virtual bool ExecuteDelete(std::shared_ptr row, hybridse::sdk::Status* status) = 0; virtual std::shared_ptr GetTableReader() = 0; diff --git a/src/sdk/sql_router_sdk.i b/src/sdk/sql_router_sdk.i index 1146aeba42e..22ee63b3e6d 100644 --- a/src/sdk/sql_router_sdk.i +++ b/src/sdk/sql_router_sdk.i @@ -65,6 +65,7 @@ %shared_ptr(openmldb::sdk::QueryFuture); %shared_ptr(openmldb::sdk::TableReader); %shared_ptr(hybridse::node::CreateTableLikeClause); +%shared_ptr(openmldb::sdk::DefaultValueContainer); %template(VectorUint32) std::vector; %template(VectorString) std::vector; @@ -93,6 +94,7 @@ using openmldb::sdk::ExplainInfo; using hybridse::sdk::ProcedureInfo; using openmldb::sdk::QueryFuture; using openmldb::sdk::TableReader; +using openmldb::sdk::DefaultValueContainer; %} %include "sdk/sql_router.h" diff --git a/src/sdk/sql_sdk_test.h b/src/sdk/sql_sdk_test.h index 58d72cf458a..5a020d144cb 100644 --- a/src/sdk/sql_sdk_test.h +++ b/src/sdk/sql_sdk_test.h @@ -48,8 +48,12 @@ INSTANTIATE_TEST_SUITE_P(SQLSDKHavingQuery, SQLSDKQueryTest, testing::ValuesIn(SQLSDKQueryTest::InitCases("cases/query/having_query.yaml"))); INSTANTIATE_TEST_SUITE_P(SQLSDKLastJoinQuery, SQLSDKQueryTest, testing::ValuesIn(SQLSDKQueryTest::InitCases("cases/query/last_join_query.yaml"))); +INSTANTIATE_TEST_SUITE_P(SQLSDKLeftJoin, SQLSDKQueryTest, + testing::ValuesIn(SQLSDKQueryTest::InitCases("cases/query/left_join.yml"))); INSTANTIATE_TEST_SUITE_P(SQLSDKLastJoinWindowQuery, SQLSDKQueryTest, testing::ValuesIn(SQLSDKQueryTest::InitCases("cases/query/last_join_window_query.yaml"))); +INSTANTIATE_TEST_SUITE_P(SQLSDKLastJoinSubqueryWindow, SQLSDKQueryTest, + testing::ValuesIn(SQLSDKQueryTest::InitCases("cases/query/last_join_subquery_window.yml"))); INSTANTIATE_TEST_SUITE_P(SQLSDKLastJoinWhere, SQLSDKQueryTest, testing::ValuesIn(SQLSDKQueryTest::InitCases("cases/query/last_join_where.yaml"))); INSTANTIATE_TEST_SUITE_P(SQLSDKParameterizedQuery, SQLSDKQueryTest, diff --git a/src/statistics/query_response_time/CMakeLists.txt b/src/statistics/query_response_time/CMakeLists.txt index e309934f318..b03aeef65c5 100644 --- a/src/statistics/query_response_time/CMakeLists.txt +++ b/src/statistics/query_response_time/CMakeLists.txt @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -set(LINK_LIBS absl::time absl::random_random absl::strings absl::status absl::statusor absl::synchronization ${GTEST_LIBRARIES} ${GLOG_LIBRARY} ${GFLAGS_LIBRARY}) +set(LINK_LIBS absl::time absl::random_random absl::strings absl::status absl::statusor absl::synchronization ${BRPC_LIBS} ${GTEST_LIBRARIES} ${GLOG_LIBRARY} ${GFLAGS_LIBRARY}) link_libraries(${LINK_LIBS}) if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang)|(Clang)") add_definitions(-Wthread-safety) endif() -add_library(query_response_time STATIC ${CMAKE_CURRENT_SOURCE_DIR}/deploy_query_response_time.cc ${CMAKE_CURRENT_SOURCE_DIR}/query_response_time.cc) +add_library(query_response_time STATIC ${CMAKE_CURRENT_SOURCE_DIR}/deployment_metric_collector.cc) function(add_test_file TARGET_NAME SOURCE_NAME) add_executable(${TARGET_NAME} ${SOURCE_NAME}) @@ -40,8 +40,7 @@ function(add_test_file TARGET_NAME SOURCE_NAME) endfunction(add_test_file) if(TESTING_ENABLE) - add_test_file(query_response_time_test ${CMAKE_CURRENT_SOURCE_DIR}/query_response_time_test.cc) - add_test_file(deploy_query_response_time_test ${CMAKE_CURRENT_SOURCE_DIR}/deploy_query_response_time_test.cc) + add_test_file(deployment_metric_collector_test ${CMAKE_CURRENT_SOURCE_DIR}/deployment_metric_collector_test.cc) if(CMAKE_PROJECT_NAME STREQUAL "openmldb") set(test_list ${test_list} PARENT_SCOPE) diff --git a/src/statistics/query_response_time/deployment_metric_collector.cc b/src/statistics/query_response_time/deployment_metric_collector.cc new file mode 100644 index 00000000000..252821254b8 --- /dev/null +++ b/src/statistics/query_response_time/deployment_metric_collector.cc @@ -0,0 +1,43 @@ +/* + * Copyright 2022 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "statistics/query_response_time/deployment_metric_collector.h" + +namespace openmldb::statistics { + +absl::Status DeploymentMetricCollector::Collect(const std::string& db, const std::string& deploy_name, + absl::Duration time) { + absl::ReaderMutexLock lock(&mutex_); + auto it = md_recorder_->get_stats({db, deploy_name}); + if (it == nullptr) { + LOG(WARNING) << "reach limit size, collect failed"; + return absl::OutOfRangeError("multi-dimensional recorder reaches limit size, please delete old deploy"); + } + *it << absl::ToInt64Microseconds(time); + return absl::OkStatus(); +} + +absl::Status DeploymentMetricCollector::DeleteDeploy(const std::string& db, const std::string& deploy_name) { + absl::ReaderMutexLock lock(&mutex_); + md_recorder_->delete_stats({db, deploy_name}); + return absl::OkStatus(); +} + +void DeploymentMetricCollector::Reset() { + absl::WriterMutexLock lock(&mutex_); + md_recorder_ = make_shared(prefix_); +} +} // namespace openmldb::statistics diff --git a/src/statistics/query_response_time/deployment_metric_collector.h b/src/statistics/query_response_time/deployment_metric_collector.h new file mode 100644 index 00000000000..9296b5a5fde --- /dev/null +++ b/src/statistics/query_response_time/deployment_metric_collector.h @@ -0,0 +1,81 @@ +/* + * Copyright 2022 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef SRC_STATISTICS_QUERY_RESPONSE_TIME_DEPLOYMENT_METRIC_COLLECTOR_H_ +#define SRC_STATISTICS_QUERY_RESPONSE_TIME_DEPLOYMENT_METRIC_COLLECTOR_H_ + +#include +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include "bvar/bvar.h" +#include "bvar/multi_dimension.h" +#include "gflags/gflags.h" + +namespace openmldb::statistics { +class DeploymentMetricCollector { + public: + typedef typename bvar::MultiDimension MDRecorder; + explicit DeploymentMetricCollector(const std::string& prefix) : prefix_(prefix), md_recorder_(make_shared(prefix)) { + // already expose_as when MultiDimension ctor + } + // collector is not copyable + DeploymentMetricCollector(const DeploymentMetricCollector& c) = delete; + + ~DeploymentMetricCollector() {} + // . + absl::Status Collect(const std::string& db, const std::string& deploy_name, absl::Duration time) + LOCKS_EXCLUDED(mutex_); + absl::Status DeleteDeploy(const std::string& db, const std::string& deploy_name) LOCKS_EXCLUDED(mutex_); + void Reset() LOCKS_EXCLUDED(mutex_); + + // usually used for debug + std::string Desc(const std::list& key) LOCKS_EXCLUDED(mutex_) { + absl::ReaderMutexLock lock(&mutex_); + std::stringstream ss; + if (key.empty()) { + md_recorder_->describe(ss); + } else if (md_recorder_->has_stats(key)) { + auto rd = md_recorder_->get_stats(key); + ss << "count:" << rd->count() << ", qps:" << rd->qps() << ", latency:[" << rd->latency() << "," + << rd->latency_percentile(0.8) << "," << rd->latency_percentile(0.9) << "," + << rd->latency_percentile(0.99) << "," << rd->latency_percentile(0.999) << "," + << rd->latency_percentile(0.9999) << "]"; + } else { + ss << "no stats for key"; + } + + return ss.str(); + } + + static std::shared_ptr make_shared(const std::string& prefix) { + MDRecorder::key_type labels = {"db", "deployment"}; + return std::make_shared(prefix, "deployment", labels); + } + + private: + std::string prefix_; // for reset + // not copyable and can't clear, so use ptr + // MultiDimension can't define recorder window size by yourself, bvar_dump_interval is the only way + std::shared_ptr md_recorder_ GUARDED_BY(mutex_); + mutable absl::Mutex mutex_; // protects collectors_ +}; +} // namespace openmldb::statistics +#endif // SRC_STATISTICS_QUERY_RESPONSE_TIME_DEPLOYMENT_METRIC_COLLECTOR_H_ diff --git a/src/statistics/query_response_time/deployment_metric_collector_test.cc b/src/statistics/query_response_time/deployment_metric_collector_test.cc new file mode 100644 index 00000000000..06ae0e0ddd4 --- /dev/null +++ b/src/statistics/query_response_time/deployment_metric_collector_test.cc @@ -0,0 +1,134 @@ +/* + * Copyright 2022 4Paradigm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "statistics/query_response_time/deployment_metric_collector.h" + +#include +#include +#include +#include +#include + +#include "absl/random/random.h" +#include "absl/strings/str_cat.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gflags/gflags.h" +#include "glog/logging.h" +#include "gtest/gtest.h" + +namespace bvar { +DECLARE_int32(bvar_dump_interval); +} + +namespace openmldb { +namespace statistics { + +class CollectorTest : public ::testing::Test { + public: + CollectorTest() = default; + ~CollectorTest() override = default; +}; + +using std::ifstream; +using std::string; +using std::ios_base; + +void mem_usage() { + double vm_usage = 0.0; + double resident_set = 0.0; + ifstream stat_stream("/proc/self/stat", ios_base::in); // get info from proc directory + // create some variables to get info + string pid, comm, state, ppid, pgrp, session, tty_nr; + string tpgid, flags, minflt, cminflt, majflt, cmajflt; + string utime, stime, cutime, cstime, priority, nice; + string O, itrealvalue, starttime; + uint64_t vsize; + int64_t rss; + stat_stream >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr >> tpgid >> flags >> minflt >> cminflt >> + majflt >> cmajflt >> utime >> stime >> cutime >> cstime >> priority >> nice >> O >> itrealvalue >> starttime >> + vsize >> rss; // don't care about the rest + stat_stream.close(); + int64_t page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // for x86-64 is configured to use 2MB pages + vm_usage = vsize / 1024.0; + resident_set = rss * page_size_kb; + LOG(INFO) << "VM: " << vm_usage << "KB; RSS: " << resident_set << "KB"; +} + +TEST_F(CollectorTest, MemoryTest) { + // let's see how much memory will be used + auto test = [](int db_size, int dpl_size, int request_cnt, bool will_fail = false) { + DeploymentMetricCollector collector("test"); + for (int db_idx = 0; db_idx < db_size; db_idx++) { + auto db = "db" + std::to_string(db_idx); + for (int i = 0; i < dpl_size; i++) { + for (int t = 0; t < request_cnt; t++) { + auto st = collector.Collect(db, "d" + std::to_string(i), absl::Microseconds(10)); + } + } + } + if (will_fail) { + return; + } + auto desc = collector.Desc({}); + LOG(INFO) << desc; + ASSERT_TRUE(desc == + absl::StrCat(R"({"name" : "test_deployment", "labels" : ["db", "deployment"], "stats_count" : )", + db_size * dpl_size, "}")) + << desc; + // peek one + auto dstat = collector.Desc({"db0", "d0"}); + LOG(INFO) << dstat; + // can't check qps, and latency is calc from window, it'll == 0 if get too fase, so we just check count + ASSERT_TRUE(dstat.find("count:" + std::to_string(request_cnt)) != std::string::npos) << dstat; + mem_usage(); + }; + // empty mem VM: 104948KB; RSS: 5752KB + mem_usage(); + // recorder mem for one label is stable, VM: ~105576KB; RSS: ~6512KB, even collect 10M requests + // but VM&RSS containes other memory, should use diff to get recorder mem + test(1, 1, 1000); // + ~0.5M + test(1, 1, 10000); + test(1, 1, 100000); + // disable for speed + // test(1, 1, 1000000); + // test(1, 1, 10000000); + + // VM: 105568KB; RSS: 6628KB + // VM: 105964KB; RSS: 6984KB + // VM: 110056KB; RSS: 11208KB + // VM: 341356KB; RSS: 126256KB + // VM: 344076KB; RSS: 129936KB + // test(1, 10, 1000); + // test(1, 100, 1000); + // test(1, 1000, 1000); + // test(1, 10000, 1000); + // test(10, 1000, 1000); + + // MAX_MULTI_DIMENSION_STATS_COUNT = 20000, so total alive deployment count <= 20001 + // If we need more, need a repetitive task to reset (K*bvar_bump_interval?) or add LabelLatencyRecorder + test(1, 20002, 1, true); +} + +} // namespace statistics +} // namespace openmldb + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + ::google::ParseCommandLineFlags(&argc, &argv, true); + bvar::FLAGS_bvar_dump_interval = 75; + return RUN_ALL_TESTS(); +} diff --git a/src/storage/aggregator.cc b/src/storage/aggregator.cc index c57ff5103cb..7814c687be5 100644 --- a/src/storage/aggregator.cc +++ b/src/storage/aggregator.cc @@ -54,11 +54,13 @@ std::string AggrStatToString(AggrStat type) { return output; } -Aggregator::Aggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size) +Aggregator::Aggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size) : base_table_schema_(base_meta.column_desc()), + base_table_(base_table), aggr_table_schema_(aggr_meta.column_desc()), aggr_table_(aggr_table), aggr_replicator_(aggr_replicator), @@ -104,19 +106,11 @@ bool Aggregator::Update(const std::string& key, const std::string& row, uint64_t } auto row_ptr = reinterpret_cast(row.c_str()); int64_t cur_ts = 0; - switch (ts_col_type_) { - case DataType::kBigInt: { - base_row_view_.GetValue(row_ptr, ts_col_idx_, DataType::kBigInt, &cur_ts); - break; - } - case DataType::kTimestamp: { - base_row_view_.GetValue(row_ptr, ts_col_idx_, DataType::kTimestamp, &cur_ts); - break; - } - default: { - PDLOG(ERROR, "Unsupported timestamp data type"); - return false; - } + if (ts_col_type_ == DataType::kBigInt || ts_col_type_ == DataType::kTimestamp) { + base_row_view_.GetValue(row_ptr, ts_col_idx_, ts_col_type_, &cur_ts); + } else { + PDLOG(ERROR, "Unsupported timestamp data type"); + return false; } std::string filter_key = ""; if (filter_col_idx_ != -1) { @@ -213,8 +207,9 @@ bool Aggregator::Update(const std::string& key, const std::string& row, uint64_t return true; } -bool Aggregator::Delete(const std::string& key) { - { +bool Aggregator::DeleteData(const std::string& key, const std::optional& start_ts, + const std::optional& end_ts) { + if (!start_ts.has_value() && !end_ts.has_value()) { std::lock_guard lock(mu_); // erase from the aggr_buffer_map_ aggr_buffer_map_.erase(key); @@ -225,23 +220,181 @@ bool Aggregator::Delete(const std::string& key) { auto dimension = entry.add_dimensions(); dimension->set_key(key); dimension->set_idx(aggr_index_pos_); - + if (start_ts.has_value()) { + entry.set_ts(start_ts.value()); + } + if (end_ts.has_value()) { + entry.set_end_ts(end_ts.value()); + } // delete the entries from the pre-aggr table - bool ok = aggr_table_->Delete(entry); - if (!ok) { - PDLOG(ERROR, "Delete key %s from aggr table %s failed", key, aggr_table_->GetName()); + if (!aggr_table_->Delete(entry)) { + PDLOG(ERROR, "Delete key %s from aggr table %s failed", key.c_str(), aggr_table_->GetName().c_str()); return false; } - - ok = aggr_replicator_->AppendEntry(entry); - if (!ok) { - PDLOG(ERROR, "Add Delete entry to binlog failed: key %s, aggr table %s", key, aggr_table_->GetName()); + if (!aggr_replicator_->AppendEntry(entry)) { + PDLOG(ERROR, "Add Delete entry to binlog failed: key %s, aggr table %s", + key.c_str(), aggr_table_->GetName().c_str()); return false; } if (FLAGS_binlog_notify_on_put) { aggr_replicator_->Notify(); } + return true; +} +bool Aggregator::Delete(const std::string& key, const std::optional& start_ts, + const std::optional& end_ts) { + if (!start_ts.has_value() && !end_ts.has_value()) { + return DeleteData(key, start_ts, end_ts); + } + uint64_t real_start_ts = start_ts.has_value() ? start_ts.value() : UINT64_MAX; + std::vector aggr_buffer_lock_vec; + { + std::lock_guard lock(mu_); + if (auto it = aggr_buffer_map_.find(key); it != aggr_buffer_map_.end()) { + for (auto& kv : it->second) { + auto& buffer = kv.second.buffer_; + if (buffer.IsInited() && real_start_ts >= static_cast(buffer.ts_begin_) && + (!end_ts.has_value() || end_ts.value() < static_cast(buffer.ts_end_))) { + aggr_buffer_lock_vec.push_back(&kv.second); + } + } + } + } + for (auto agg_buffer_lock : aggr_buffer_lock_vec) { + RebuildAggrBuffer(key, &agg_buffer_lock->buffer_); + } + ::openmldb::storage::Ticket ticket; + std::unique_ptr it(aggr_table_->NewIterator(0, key, ticket)); + if (it == nullptr) { + return false; + } + if (window_type_ == WindowType::kRowsRange && UINT64_MAX - window_size_ > real_start_ts) { + it->Seek(real_start_ts + window_size_); + } else { + it->SeekToFirst(); + } + std::optional delete_start_ts = std::nullopt; + std::optional delete_end_ts = std::nullopt; + bool is_first_block = true; + while (it->Valid()) { + uint64_t buffer_start_ts = it->GetKey(); + uint64_t buffer_end_ts = 0; + auto aggr_row_ptr = reinterpret_cast(it->GetValue().data()); + aggr_row_view_.GetValue(aggr_row_ptr, 2, DataType::kTimestamp, &buffer_end_ts); + if (is_first_block) { + is_first_block = false; + if (!end_ts.has_value() || end_ts.value() < buffer_end_ts) { + real_start_ts = std::min(buffer_end_ts, real_start_ts); + } + } + if (real_start_ts <= buffer_end_ts) { + if (end_ts.has_value()) { + delete_end_ts = buffer_start_ts; + } + if (real_start_ts >= buffer_start_ts) { + RebuildFlushedAggrBuffer(key, aggr_row_ptr); + // start delete from next block + delete_start_ts = buffer_start_ts > 0 ? buffer_start_ts - 1 : 0; + if (end_ts.has_value()) { + if (end_ts.value() >= buffer_start_ts) { + // range data in one aggregate buffer + return true; + } + } else { + break; + } + it->Next(); + continue; + } + } + if (end_ts.has_value()) { + if (end_ts.value() >= buffer_end_ts) { + break; + } else { + delete_end_ts = buffer_start_ts > 0 ? buffer_start_ts - 1 : 0; + if (end_ts.value() >= buffer_start_ts) { + // end delete with last block + delete_end_ts = buffer_end_ts; + if (delete_start_ts.has_value() && delete_start_ts.value() <= buffer_end_ts) { + // two adjacent blocks, no delete + delete_start_ts.reset(); + delete_end_ts.reset(); + } + RebuildFlushedAggrBuffer(key, aggr_row_ptr); + break; + } + } + } + it->Next(); + } + if (delete_start_ts.has_value() || delete_end_ts.has_value()) { + if (delete_start_ts.has_value() && delete_end_ts.has_value() && + delete_start_ts.value() <= delete_end_ts.value()) { + return true; + } + return DeleteData(key, delete_start_ts, delete_end_ts); + } + return true; +} + +bool Aggregator::RebuildFlushedAggrBuffer(const std::string& key, const int8_t* row_ptr) { + DLOG(INFO) << "RebuildFlushedAggrBuffer. key is " << key; + AggrBuffer buffer; + if (!GetAggrBufferFromRowView(aggr_row_view_, row_ptr, &buffer)) { + PDLOG(WARNING, "GetAggrBufferFromRowView failed"); + return false; + } + if (!RebuildAggrBuffer(key, &buffer)) { + PDLOG(WARNING, "RebuildAggrBuffer failed. key is %s", key.c_str()); + return false; + } + std::string filter_key; + if (!aggr_row_view_.IsNULL(row_ptr, 6)) { + char* ch = nullptr; + uint32_t len = 0; + aggr_row_view_.GetValue(row_ptr, 6, &ch, &len); + filter_key.assign(ch, len); + } + if (!FlushAggrBuffer(key, filter_key, buffer)) { + PDLOG(WARNING, "FlushAggrBuffer failed. key is %s", key.c_str()); + return false; + } + return true; +} + +bool Aggregator::RebuildAggrBuffer(const std::string& key, AggrBuffer* aggr_buffer) { + if (base_table_ == nullptr) { + PDLOG(WARNING, "base table is nullptr, cannot update MinAggr table"); + return false; + } + storage::Ticket ticket; + std::unique_ptr it(base_table_->NewIterator(GetIndexPos(), key, ticket)); + if (it == nullptr) { + return false; + } + int64_t ts_begin = aggr_buffer->ts_begin_; + int64_t ts_end = aggr_buffer->ts_end_; + uint64_t binlog_offset = aggr_buffer->binlog_offset_; + auto data_type = aggr_buffer->data_type_; + aggr_buffer->Clear(); + aggr_buffer->ts_begin_ = ts_begin; + aggr_buffer->ts_end_ = ts_end; + aggr_buffer->binlog_offset_ = binlog_offset; + aggr_buffer->data_type_ = data_type; + it->Seek(ts_end); + while (it->Valid()) { + if (it->GetKey() < static_cast(ts_begin)) { + break; + } + auto base_row_ptr = reinterpret_cast(it->GetValue().data()); + if (!UpdateAggrVal(base_row_view_, base_row_ptr, aggr_buffer)) { + PDLOG(WARNING, "Failed to update aggr Val during rebuilding Extermum aggr buffer"); + return false; + } + aggr_buffer->aggr_cnt_++; + it->Next(); + } return true; } @@ -270,12 +423,10 @@ bool Aggregator::FlushAll() { } bool Aggregator::Init(std::shared_ptr base_replicator) { - std::unique_lock lock(mu_); if (GetStat() != AggrStat::kUnInit) { - PDLOG(INFO, "aggregator status is %s", AggrStatToString(GetStat())); + PDLOG(INFO, "aggregator status is %s", AggrStatToString(GetStat()).c_str()); return true; } - lock.unlock(); if (!base_replicator) { return false; } @@ -372,7 +523,11 @@ bool Aggregator::Init(std::shared_ptr base_replicator) { for (const auto& dimension : entry.dimensions()) { if (dimension.idx() == index_pos_) { if (entry.has_method_type() && entry.method_type() == ::openmldb::api::MethodType::kDelete) { - Delete(dimension.key()); + std::optional start_ts = entry.has_ts() ? + std::optional(entry.ts()) : std::nullopt; + std::optional end_ts = entry.has_end_ts() ? + std::optional(entry.end_ts()) : std::nullopt; + Delete(dimension.key(), start_ts, end_ts); } else { Update(dimension.key(), entry.value(), entry.log_index(), true); } @@ -586,12 +741,13 @@ bool Aggregator::CheckBufferFilled(int64_t cur_ts, int64_t buffer_end, int32_t b return false; } -SumAggregator::SumAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size) - : Aggregator(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, ts_col, window_tpye, - window_size) {} +SumAggregator::SumAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size) + : Aggregator(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, index_pos, + aggr_col, aggr_type, ts_col, window_tpye, window_size) {} bool SumAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t* row_ptr, AggrBuffer* aggr_buffer) { if (row_view.IsNULL(row_ptr, aggr_col_idx_)) { @@ -700,13 +856,14 @@ bool SumAggregator::DecodeAggrVal(const int8_t* row_ptr, AggrBuffer* buffer) { } MinMaxBaseAggregator::MinMaxBaseAggregator(const ::openmldb::api::TableMeta& base_meta, + std::shared_ptr
base_table, const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, - std::shared_ptr aggr_replicator, const uint32_t& index_pos, + std::shared_ptr aggr_replicator, uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, const std::string& ts_col, WindowType window_tpye, uint32_t window_size) - : Aggregator(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, ts_col, window_tpye, - window_size) {} + : Aggregator(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, + ts_col, window_tpye, window_size) {} bool MinMaxBaseAggregator::EncodeAggrVal(const AggrBuffer& buffer, std::string* aggr_val) { switch (aggr_col_type_) { @@ -806,12 +963,13 @@ bool MinMaxBaseAggregator::DecodeAggrVal(const int8_t* row_ptr, AggrBuffer* buff return true; } -MinAggregator::MinAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size) - : MinMaxBaseAggregator(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, ts_col, - window_tpye, window_size) {} +MinAggregator::MinAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size) + : MinMaxBaseAggregator(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, index_pos, + aggr_col, aggr_type, ts_col, window_tpye, window_size) {} bool MinAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t* row_ptr, AggrBuffer* aggr_buffer) { if (row_view.IsNULL(row_ptr, aggr_col_idx_)) { @@ -888,12 +1046,13 @@ bool MinAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t* return true; } -MaxAggregator::MaxAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size) - : MinMaxBaseAggregator(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, ts_col, - window_tpye, window_size) {} +MaxAggregator::MaxAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size) + : MinMaxBaseAggregator(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, index_pos, + aggr_col, aggr_type, ts_col, window_tpye, window_size) {} bool MaxAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t* row_ptr, AggrBuffer* aggr_buffer) { if (row_view.IsNULL(row_ptr, aggr_col_idx_)) { @@ -970,13 +1129,13 @@ bool MaxAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t* return true; } -CountAggregator::CountAggregator(const ::openmldb::api::TableMeta& base_meta, +CountAggregator::CountAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, - std::shared_ptr aggr_replicator, const uint32_t& index_pos, + std::shared_ptr aggr_replicator, uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, const std::string& ts_col, WindowType window_tpye, uint32_t window_size) - : Aggregator(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, ts_col, window_tpye, - window_size) { + : Aggregator(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, + ts_col, window_tpye, window_size) { if (aggr_col == "*") { count_all = true; } @@ -1005,12 +1164,13 @@ bool CountAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t return true; } -AvgAggregator::AvgAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size) - : Aggregator(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, aggr_type, ts_col, window_tpye, - window_size) {} +AvgAggregator::AvgAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size) + : Aggregator(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, index_pos, + aggr_col, aggr_type, ts_col, window_tpye, window_size) {} bool AvgAggregator::UpdateAggrVal(const codec::RowView& row_view, const int8_t* row_ptr, AggrBuffer* aggr_buffer) { if (row_view.IsNULL(row_ptr, aggr_col_idx_)) { @@ -1076,6 +1236,7 @@ bool AvgAggregator::DecodeAggrVal(const int8_t* row_ptr, AggrBuffer* buffer) { } std::shared_ptr CreateAggregator(const ::openmldb::api::TableMeta& base_meta, + std::shared_ptr
base_table, const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, uint32_t index_pos, @@ -1123,20 +1284,20 @@ std::shared_ptr CreateAggregator(const ::openmldb::api::TableMeta& b std::shared_ptr agg; if (aggr_type == "sum" || aggr_type == "sum_where") { - agg = std::make_shared(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, - AggrType::kSum, ts_col, window_type, window_size); + agg = std::make_shared(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, + index_pos, aggr_col, AggrType::kSum, ts_col, window_type, window_size); } else if (aggr_type == "min" || aggr_type == "min_where") { - agg = std::make_shared(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, - AggrType::kMin, ts_col, window_type, window_size); + agg = std::make_shared(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, + index_pos, aggr_col, AggrType::kMin, ts_col, window_type, window_size); } else if (aggr_type == "max" || aggr_type == "max_where") { - agg = std::make_shared(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, - AggrType::kMax, ts_col, window_type, window_size); + agg = std::make_shared(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, + index_pos, aggr_col, AggrType::kMax, ts_col, window_type, window_size); } else if (aggr_type == "count" || aggr_type == "count_where") { - agg = std::make_shared(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, - AggrType::kCount, ts_col, window_type, window_size); + agg = std::make_shared(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, + index_pos, aggr_col, AggrType::kCount, ts_col, window_type, window_size); } else if (aggr_type == "avg" || aggr_type == "avg_where") { - agg = std::make_shared(base_meta, aggr_meta, aggr_table, aggr_replicator, index_pos, aggr_col, - AggrType::kAvg, ts_col, window_type, window_size); + agg = std::make_shared(base_meta, base_table, aggr_meta, aggr_table, aggr_replicator, + index_pos, aggr_col, AggrType::kAvg, ts_col, window_type, window_size); } else { PDLOG(ERROR, "Unsupported aggregate function type"); return {}; @@ -1149,11 +1310,11 @@ std::shared_ptr CreateAggregator(const ::openmldb::api::TableMeta& b // _where variant if (filter_col.empty()) { - PDLOG(ERROR, "no filter column specified for %s", aggr_type); + PDLOG(ERROR, "no filter column specified for %s", aggr_type.c_str()); return {}; } if (!agg->SetFilter(filter_col)) { - PDLOG(ERROR, "can not find filter column '%s' for %s", filter_col, aggr_type); + PDLOG(ERROR, "can not find filter column '%s' for %s", filter_col.c_str(), aggr_type.c_str()); return {}; } return agg; diff --git a/src/storage/aggregator.h b/src/storage/aggregator.h index f007ffc18e4..373e23633cd 100644 --- a/src/storage/aggregator.h +++ b/src/storage/aggregator.h @@ -120,16 +120,17 @@ struct AggrBufferLocked { class Aggregator { public: - Aggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + Aggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~Aggregator(); bool Update(const std::string& key, const std::string& row, uint64_t offset, bool recover = false); - bool Delete(const std::string& key); + bool Delete(const std::string& key, const std::optional& start_ts, const std::optional& end_ts); bool FlushAll(); @@ -156,15 +157,18 @@ class Aggregator { // set the filter column info that not initialized in constructor bool SetFilter(absl::string_view filter_col); + std::shared_ptr
GetAggTable() { return aggr_table_; } + protected: codec::Schema base_table_schema_; - codec::Schema aggr_table_schema_; using FilterMap = absl::flat_hash_map; // filter_column -> aggregator buffer absl::flat_hash_map aggr_buffer_map_; // key -> filter_map std::mutex mu_; DataType aggr_col_type_; DataType ts_col_type_; + std::shared_ptr
base_table_; + codec::Schema aggr_table_schema_; std::shared_ptr
aggr_table_; std::shared_ptr aggr_replicator_; std::atomic status_; @@ -176,11 +180,16 @@ class Aggregator { bool CheckBufferFilled(int64_t cur_ts, int64_t buffer_end, int32_t buffer_cnt); private: + bool DeleteData(const std::string& key, const std::optional& start_ts, + const std::optional& end_ts); + virtual bool UpdateAggrVal(const codec::RowView& row_view, const int8_t* row_ptr, AggrBuffer* aggr_buffer) = 0; virtual bool EncodeAggrVal(const AggrBuffer& buffer, std::string* aggr_val) = 0; virtual bool DecodeAggrVal(const int8_t* row_ptr, AggrBuffer* buffer) = 0; bool EncodeAggrBuffer(const std::string& key, const std::string& filter_key, const AggrBuffer& buffer, const std::string& aggr_val, std::string* encoded_row); + bool RebuildAggrBuffer(const std::string& key, AggrBuffer* aggr_buffer); + bool RebuildFlushedAggrBuffer(const std::string& key, const int8_t* row_ptr); int64_t AlignedStart(int64_t ts) { if (window_type_ == WindowType::kRowsRange) { return ts / window_size_ * window_size_; @@ -213,10 +222,11 @@ class Aggregator { class SumAggregator : public Aggregator { public: - SumAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + SumAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~SumAggregator() = default; @@ -230,10 +240,11 @@ class SumAggregator : public Aggregator { class MinMaxBaseAggregator : public Aggregator { public: - MinMaxBaseAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + MinMaxBaseAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~MinMaxBaseAggregator() = default; @@ -244,10 +255,11 @@ class MinMaxBaseAggregator : public Aggregator { }; class MinAggregator : public MinMaxBaseAggregator { public: - MinAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + MinAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~MinAggregator() = default; @@ -257,10 +269,11 @@ class MinAggregator : public MinMaxBaseAggregator { class MaxAggregator : public MinMaxBaseAggregator { public: - MaxAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + MaxAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~MaxAggregator() = default; @@ -270,10 +283,11 @@ class MaxAggregator : public MinMaxBaseAggregator { class CountAggregator : public Aggregator { public: - CountAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + CountAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~CountAggregator() = default; @@ -289,10 +303,11 @@ class CountAggregator : public Aggregator { class AvgAggregator : public Aggregator { public: - AvgAggregator(const ::openmldb::api::TableMeta& base_meta, const ::openmldb::api::TableMeta& aggr_meta, - std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, - const uint32_t& index_pos, const std::string& aggr_col, const AggrType& aggr_type, - const std::string& ts_col, WindowType window_tpye, uint32_t window_size); + AvgAggregator(const ::openmldb::api::TableMeta& base_meta, std::shared_ptr
base_table, + const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, + std::shared_ptr aggr_replicator, + uint32_t index_pos, const std::string& aggr_col, const AggrType& aggr_type, + const std::string& ts_col, WindowType window_tpye, uint32_t window_size); ~AvgAggregator() = default; @@ -305,6 +320,7 @@ class AvgAggregator : public Aggregator { }; std::shared_ptr CreateAggregator(const ::openmldb::api::TableMeta& base_meta, + std::shared_ptr
base_table, const ::openmldb::api::TableMeta& aggr_meta, std::shared_ptr
aggr_table, std::shared_ptr aggr_replicator, uint32_t index_pos, diff --git a/src/storage/aggregator_test.cc b/src/storage/aggregator_test.cc index c64f70b9269..2fa9299c6f2 100644 --- a/src/storage/aggregator_test.cc +++ b/src/storage/aggregator_test.cc @@ -123,8 +123,8 @@ bool GetUpdatedResult(const uint32_t& id, const std::string& aggr_col, const std std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, aggr_col, aggr_type, - "ts_col", bucket_size, "low_card"); + auto aggr = CreateAggregator(base_table_meta, table, aggr_table_meta, aggr_table, replicator, 0, + aggr_col, aggr_type, "ts_col", bucket_size, "low_card"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -319,7 +319,8 @@ void CheckCountWhereAggrResult(std::shared_ptr
aggr_table, std::shared_pt TEST_F(AggregatorTest, CreateAggregator) { // rows_num window type std::map map; - std::string folder = "/tmp/" + GenRand() + "/"; + ::openmldb::test::TempPath tmp_path; + std::string folder = tmp_path.GetTempPath(); { uint32_t id = counter++; ::openmldb::api::TableMeta base_table_meta; @@ -334,8 +335,8 @@ TEST_F(AggregatorTest, CreateAggregator) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", - "ts_col", "1000"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "1000"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -360,8 +361,8 @@ TEST_F(AggregatorTest, CreateAggregator) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", - "ts_col", "1d"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "1d"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -385,8 +386,8 @@ TEST_F(AggregatorTest, CreateAggregator) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", - "ts_col", "2s"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "2s"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -410,8 +411,8 @@ TEST_F(AggregatorTest, CreateAggregator) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", - "ts_col", "3m"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "3m"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -435,8 +436,8 @@ TEST_F(AggregatorTest, CreateAggregator) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", - "ts_col", "100h"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "100h"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -471,7 +472,8 @@ TEST_F(AggregatorTest, SumAggregatorUpdate) { aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); auto aggr = - CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", "ts_col", "2"); + CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "2"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -739,7 +741,8 @@ TEST_F(AggregatorTest, OutOfOrder) { aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); auto aggr = - CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "sum", "ts_col", "1s"); + CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "sum", "ts_col", "1s"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -808,7 +811,8 @@ TEST_F(AggregatorTest, OutOfOrder) { TEST_F(AggregatorTest, OutOfOrderCountWhere) { std::map map; - std::string folder = "/tmp/" + GenRand() + "/"; + ::openmldb::test::TempPath tmp_path; + std::string folder = tmp_path.GetTempPath(); uint32_t id = counter++; ::openmldb::api::TableMeta base_table_meta; base_table_meta.set_tid(id); @@ -822,8 +826,8 @@ TEST_F(AggregatorTest, OutOfOrderCountWhere) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "count_where", - "ts_col", "1s", "low_card"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "count_where", "ts_col", "1s", "low_card"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); @@ -914,7 +918,8 @@ TEST_F(AggregatorTest, OutOfOrderCountWhere) { TEST_F(AggregatorTest, AlignedCountWhere) { std::map map; - std::string folder = "/tmp/" + GenRand() + "/"; + ::openmldb::test::TempPath tmp_path; + std::string folder = tmp_path.GetTempPath(); uint32_t id = counter++; ::openmldb::api::TableMeta base_table_meta; base_table_meta.set_tid(id); @@ -928,8 +933,8 @@ TEST_F(AggregatorTest, AlignedCountWhere) { std::shared_ptr replicator = std::make_shared( aggr_table->GetId(), aggr_table->GetPid(), folder, map, ::openmldb::replica::kLeaderNode); replicator->Init(); - auto aggr = CreateAggregator(base_table_meta, aggr_table_meta, aggr_table, replicator, 0, "col3", "count_where", - "ts_col", "1s", "low_card"); + auto aggr = CreateAggregator(base_table_meta, nullptr, aggr_table_meta, aggr_table, replicator, 0, + "col3", "count_where", "ts_col", "1s", "low_card"); std::shared_ptr base_replicator = std::make_shared( base_table_meta.tid(), base_table_meta.pid(), folder, map, ::openmldb::replica::kLeaderNode); base_replicator->Init(); diff --git a/src/storage/disk_table.cc b/src/storage/disk_table.cc index 8f508bac6c5..b41c9f8fd3c 100644 --- a/src/storage/disk_table.cc +++ b/src/storage/disk_table.cc @@ -283,17 +283,14 @@ bool DiskTable::Put(uint64_t time, const std::string& value, const Dimensions& d } bool DiskTable::Delete(const ::openmldb::api::LogEntry& entry) { - uint64_t start_ts = entry.has_ts() ? entry.ts() : UINT64_MAX; + std::optional start_ts = entry.has_ts() ? std::optional(entry.ts()) : std::nullopt; std::optional end_ts = entry.has_end_ts() ? std::optional(entry.end_ts()) : std::nullopt; if (entry.dimensions_size() > 0) { for (const auto& dimension : entry.dimensions()) { - auto s = Delete(dimension.idx(), dimension.key(), start_ts, end_ts); - if (!s.OK()) { - DEBUGLOG("Delete failed. tid %u pid %u msg %s", id_, pid_, s.GetMsg().c_str()); + if (!Delete(dimension.idx(), dimension.key(), start_ts, end_ts)) { return false; } } - offset_.fetch_add(1, std::memory_order_relaxed); return true; } else { for (const auto& index : table_index_.GetAllIndex()) { @@ -316,12 +313,13 @@ bool DiskTable::Delete(const ::openmldb::api::LogEntry& entry) { return true; } -base::Status DiskTable::Delete(uint32_t idx, const std::string& pk, - uint64_t start_ts, const std::optional& end_ts) { +bool DiskTable::Delete(uint32_t idx, const std::string& pk, + const std::optional& start_ts, const std::optional& end_ts) { auto index_def = table_index_.GetIndex(idx); if (!index_def || !index_def->IsReady()) { - return {-1, "index not found"}; + return false; } + uint64_t real_start_ts = start_ts.has_value() ? start_ts.value() : UINT64_MAX; uint64_t real_end_ts = end_ts.has_value() ? end_ts.value() : 0; std::string combine_key1; std::string combine_key2; @@ -330,21 +328,23 @@ base::Status DiskTable::Delete(uint32_t idx, const std::string& pk, if (inner_index && inner_index->GetIndex().size() > 1) { auto ts_col = index_def->GetTsColumn(); if (!ts_col) { - return {-1, "ts column not found"}; + return false; } - combine_key1 = CombineKeyTs(pk, start_ts, ts_col->GetId()); + combine_key1 = CombineKeyTs(pk, real_start_ts, ts_col->GetId()); combine_key2 = CombineKeyTs(pk, real_end_ts, ts_col->GetId()); } else { - combine_key1 = CombineKeyTs(pk, start_ts); + combine_key1 = CombineKeyTs(pk, real_start_ts); combine_key2 = CombineKeyTs(pk, real_end_ts); } rocksdb::WriteBatch batch; batch.DeleteRange(cf_hs_[inner_pos + 1], rocksdb::Slice(combine_key1), rocksdb::Slice(combine_key2)); rocksdb::Status s = db_->Write(write_opts_, &batch); if (!s.ok()) { - return {-1, s.ToString()}; + DEBUGLOG("Delete failed. tid %u pid %u msg %s", id_, pid_, s.ToString().c_str()); + return false; } - return {}; + offset_.fetch_add(1, std::memory_order_relaxed); + return true; } bool DiskTable::Get(uint32_t idx, const std::string& pk, uint64_t ts, std::string& value) { @@ -363,6 +363,48 @@ bool DiskTable::Get(uint32_t idx, const std::string& pk, uint64_t ts, std::strin bool DiskTable::Get(const std::string& pk, uint64_t ts, std::string& value) { return Get(0, pk, ts, value); } +base::Status DiskTable::Truncate() { + const rocksdb::Snapshot* snapshot = db_->GetSnapshot(); + absl::Cleanup release_snapshot = [this, snapshot] { this->db_->ReleaseSnapshot(snapshot); }; + rocksdb::ReadOptions ro = rocksdb::ReadOptions(); + ro.snapshot = snapshot; + ro.prefix_same_as_start = true; + ro.pin_data = true; + rocksdb::WriteBatch batch; + for (const auto& inner_index : *(table_index_.GetAllInnerIndex())) { + uint32_t idx = inner_index->GetId(); + std::unique_ptr it(db_->NewIterator(ro, cf_hs_[idx + 1])); + it->SeekToFirst(); + if (it->Valid()) { + std::string start_key(it->key().data(), it->key().size()); + it->SeekToLast(); + if (it->Valid()) { + rocksdb::Slice cur_pk; + uint64_t ts = 0; + uint32_t ts_idx = 0; + const auto& indexs = inner_index->GetIndex(); + std::string end_key; + if (indexs.size() > 1) { + ParseKeyAndTs(true, it->key(), &cur_pk, &ts, &ts_idx); + end_key = CombineKeyTs(cur_pk, 0, ts_idx); + } else { + ParseKeyAndTs(false, it->key(), &cur_pk, &ts, &ts_idx); + end_key = CombineKeyTs(cur_pk, 0); + } + PDLOG(INFO, "delete range. start key %s end key %s inner idx %u tid %u pid %u", + start_key.c_str(), end_key.c_str(), idx, id_, pid_); + batch.DeleteRange(cf_hs_[idx + 1], rocksdb::Slice(start_key), rocksdb::Slice(end_key)); + } + } + } + rocksdb::Status s = db_->Write(write_opts_, &batch); + if (!s.ok()) { + PDLOG(WARNING, "delete failed, tid %u pid %u msg %s", id_, pid_, s.ToString().c_str()); + return {-1, s.ToString()}; + } + return {}; +} + void DiskTable::SchedGc() { GcHead(); UpdateTTL(); @@ -543,10 +585,10 @@ TableIterator* DiskTable::NewIterator(uint32_t idx, const std::string& pk, Ticke if (inner_index && inner_index->GetIndex().size() > 1) { auto ts_col = index_def->GetTsColumn(); if (ts_col) { - return new DiskTableIterator(db_, it, snapshot, pk, ts_col->GetId()); + return new DiskTableIterator(db_, it, snapshot, pk, ts_col->GetId(), GetCompressType()); } } - return new DiskTableIterator(db_, it, snapshot, pk); + return new DiskTableIterator(db_, it, snapshot, pk, GetCompressType()); } TraverseIterator* DiskTable::NewTraverseIterator(uint32_t index) { @@ -569,10 +611,10 @@ TraverseIterator* DiskTable::NewTraverseIterator(uint32_t index) { auto ts_col = index_def->GetTsColumn(); if (ts_col) { return new DiskTableTraverseIterator(db_, it, snapshot, ttl->ttl_type, expire_time, expire_cnt, - ts_col->GetId()); + ts_col->GetId(), GetCompressType()); } } - return new DiskTableTraverseIterator(db_, it, snapshot, ttl->ttl_type, expire_time, expire_cnt); + return new DiskTableTraverseIterator(db_, it, snapshot, ttl->ttl_type, expire_time, expire_cnt, GetCompressType()); } ::hybridse::vm::WindowIterator* DiskTable::NewWindowIterator(uint32_t idx) { @@ -595,10 +637,11 @@ ::hybridse::vm::WindowIterator* DiskTable::NewWindowIterator(uint32_t idx) { auto ts_col = index_def->GetTsColumn(); if (ts_col) { return new DiskTableKeyIterator(db_, it, snapshot, ttl->ttl_type, expire_time, expire_cnt, - ts_col->GetId(), cf_hs_[inner_pos + 1]); + ts_col->GetId(), cf_hs_[inner_pos + 1], GetCompressType()); } } - return new DiskTableKeyIterator(db_, it, snapshot, ttl->ttl_type, expire_time, expire_cnt, cf_hs_[inner_pos + 1]); + return new DiskTableKeyIterator(db_, it, snapshot, ttl->ttl_type, expire_time, expire_cnt, + cf_hs_[inner_pos + 1], GetCompressType()); } bool DiskTable::DeleteIndex(const std::string& idx_name) { diff --git a/src/storage/disk_table.h b/src/storage/disk_table.h index 20f25f9a7ae..be549d0c2cd 100644 --- a/src/storage/disk_table.h +++ b/src/storage/disk_table.h @@ -181,6 +181,11 @@ class DiskTable : public Table { bool Delete(const ::openmldb::api::LogEntry& entry) override; + base::Status Truncate(); + + bool Delete(uint32_t idx, const std::string& pk, + const std::optional& start_ts, const std::optional& end_ts) override; + uint64_t GetExpireTime(const TTLSt& ttl_st) override; uint64_t GetRecordCnt() override { diff --git a/src/storage/disk_table_iterator.cc b/src/storage/disk_table_iterator.cc index 7b78bec4f3e..d934715e880 100644 --- a/src/storage/disk_table_iterator.cc +++ b/src/storage/disk_table_iterator.cc @@ -15,7 +15,7 @@ */ #include "storage/disk_table_iterator.h" - +#include #include #include "gflags/gflags.h" #include "storage/key_transform.h" @@ -26,12 +26,12 @@ namespace openmldb { namespace storage { DiskTableIterator::DiskTableIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, - const std::string& pk) - : db_(db), it_(it), snapshot_(snapshot), pk_(pk), ts_(0) {} + const std::string& pk, type::CompressType compress_type) + : db_(db), it_(it), snapshot_(snapshot), pk_(pk), ts_(0), compress_type_(compress_type) {} DiskTableIterator::DiskTableIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, - const std::string& pk, uint32_t ts_idx) - : db_(db), it_(it), snapshot_(snapshot), pk_(pk), ts_(0), ts_idx_(ts_idx) { + const std::string& pk, uint32_t ts_idx, type::CompressType compress_type) + : db_(db), it_(it), snapshot_(snapshot), pk_(pk), ts_(0), ts_idx_(ts_idx), compress_type_(compress_type) { has_ts_idx_ = true; } @@ -55,7 +55,13 @@ void DiskTableIterator::Next() { return it_->Next(); } openmldb::base::Slice DiskTableIterator::GetValue() const { rocksdb::Slice value = it_->value(); - return openmldb::base::Slice(value.data(), value.size()); + if (compress_type_ == type::CompressType::kSnappy) { + tmp_buf_.clear(); + snappy::Uncompress(value.data(), value.size(), &tmp_buf_); + return openmldb::base::Slice(tmp_buf_); + } else { + return openmldb::base::Slice(value.data(), value.size()); + } } std::string DiskTableIterator::GetPK() const { return pk_; } @@ -85,7 +91,8 @@ void DiskTableIterator::Seek(const uint64_t ts) { DiskTableTraverseIterator::DiskTableTraverseIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, - const uint64_t& expire_cnt) + const uint64_t& expire_cnt, + type::CompressType compress_type) : db_(db), it_(it), snapshot_(snapshot), @@ -93,12 +100,14 @@ DiskTableTraverseIterator::DiskTableTraverseIterator(rocksdb::DB* db, rocksdb::I expire_value_(expire_time, expire_cnt, ttl_type), has_ts_idx_(false), ts_idx_(0), - traverse_cnt_(0) {} + traverse_cnt_(0), + compress_type_(compress_type) {} DiskTableTraverseIterator::DiskTableTraverseIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, - const uint64_t& expire_cnt, int32_t ts_idx) + const uint64_t& expire_cnt, int32_t ts_idx, + type::CompressType compress_type) : db_(db), it_(it), snapshot_(snapshot), @@ -106,7 +115,8 @@ DiskTableTraverseIterator::DiskTableTraverseIterator(rocksdb::DB* db, rocksdb::I expire_value_(expire_time, expire_cnt, ttl_type), has_ts_idx_(true), ts_idx_(ts_idx), - traverse_cnt_(0) {} + traverse_cnt_(0), + compress_type_(compress_type) {} DiskTableTraverseIterator::~DiskTableTraverseIterator() { delete it_; @@ -154,6 +164,11 @@ void DiskTableTraverseIterator::Next() { openmldb::base::Slice DiskTableTraverseIterator::GetValue() const { rocksdb::Slice value = it_->value(); + if (compress_type_ == type::CompressType::kSnappy) { + tmp_buf_.clear(); + snappy::Uncompress(value.data(), value.size(), &tmp_buf_); + return openmldb::base::Slice(tmp_buf_); + } return openmldb::base::Slice(value.data(), value.size()); } @@ -297,7 +312,8 @@ void DiskTableTraverseIterator::NextPK() { DiskTableKeyIterator::DiskTableKeyIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, const uint64_t& expire_cnt, - rocksdb::ColumnFamilyHandle* column_handle) + rocksdb::ColumnFamilyHandle* column_handle, + type::CompressType compress_type) : db_(db), it_(it), snapshot_(snapshot), @@ -306,12 +322,14 @@ DiskTableKeyIterator::DiskTableKeyIterator(rocksdb::DB* db, rocksdb::Iterator* i expire_cnt_(expire_cnt), has_ts_idx_(false), ts_idx_(0), - column_handle_(column_handle) {} + column_handle_(column_handle), + compress_type_(compress_type) {} DiskTableKeyIterator::DiskTableKeyIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, const uint64_t& expire_cnt, int32_t ts_idx, - rocksdb::ColumnFamilyHandle* column_handle) + rocksdb::ColumnFamilyHandle* column_handle, + type::CompressType compress_type) : db_(db), it_(it), snapshot_(snapshot), @@ -320,7 +338,8 @@ DiskTableKeyIterator::DiskTableKeyIterator(rocksdb::DB* db, rocksdb::Iterator* i expire_cnt_(expire_cnt), has_ts_idx_(true), ts_idx_(ts_idx), - column_handle_(column_handle) {} + column_handle_(column_handle), + compress_type_(compress_type) {} DiskTableKeyIterator::~DiskTableKeyIterator() { delete it_; @@ -398,7 +417,7 @@ std::unique_ptr<::hybridse::vm::RowIterator> DiskTableKeyIterator::GetValue() { ro.pin_data = true; rocksdb::Iterator* it = db_->NewIterator(ro, column_handle_); return std::make_unique(db_, it, snapshot, ttl_type_, expire_time_, - expire_cnt_, pk_, ts_, has_ts_idx_, ts_idx_); + expire_cnt_, pk_, ts_, has_ts_idx_, ts_idx_, compress_type_); } ::hybridse::vm::RowIterator* DiskTableKeyIterator::GetRawValue() { @@ -408,14 +427,14 @@ ::hybridse::vm::RowIterator* DiskTableKeyIterator::GetRawValue() { // ro.prefix_same_as_start = true; ro.pin_data = true; rocksdb::Iterator* it = db_->NewIterator(ro, column_handle_); - return new DiskTableRowIterator(db_, it, snapshot, ttl_type_, expire_time_, expire_cnt_, pk_, ts_, has_ts_idx_, - ts_idx_); + return new DiskTableRowIterator(db_, it, snapshot, ttl_type_, expire_time_, + expire_cnt_, pk_, ts_, has_ts_idx_, ts_idx_, compress_type_); } DiskTableRowIterator::DiskTableRowIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, uint64_t expire_time, uint64_t expire_cnt, std::string pk, uint64_t ts, bool has_ts_idx, - uint32_t ts_idx) + uint32_t ts_idx, type::CompressType compress_type) : db_(db), it_(it), snapshot_(snapshot), @@ -426,7 +445,8 @@ DiskTableRowIterator::DiskTableRowIterator(rocksdb::DB* db, rocksdb::Iterator* i ts_(ts), has_ts_idx_(has_ts_idx), ts_idx_(ts_idx), - row_() {} + row_(), + compress_type_(compress_type) {} DiskTableRowIterator::~DiskTableRowIterator() { delete it_; @@ -470,9 +490,17 @@ const ::hybridse::codec::Row& DiskTableRowIterator::GetValue() { } valid_value_ = true; size_t size = it_->value().size(); - int8_t* copyed_row_data = reinterpret_cast(malloc(size)); - memcpy(copyed_row_data, it_->value().data(), size); - row_.Reset(::hybridse::base::RefCountedSlice::CreateManaged(copyed_row_data, size)); + if (compress_type_ == type::CompressType::kSnappy) { + tmp_buf_.clear(); + snappy::Uncompress(it_->value().data(), size, &tmp_buf_); + int8_t* copyed_row_data = reinterpret_cast(malloc(tmp_buf_.size())); + memcpy(copyed_row_data, tmp_buf_.data(), tmp_buf_.size()); + row_.Reset(::hybridse::base::RefCountedSlice::CreateManaged(copyed_row_data, tmp_buf_.size())); + } else { + int8_t* copyed_row_data = reinterpret_cast(malloc(size)); + memcpy(copyed_row_data, it_->value().data(), size); + row_.Reset(::hybridse::base::RefCountedSlice::CreateManaged(copyed_row_data, size)); + } return row_; } diff --git a/src/storage/disk_table_iterator.h b/src/storage/disk_table_iterator.h index 88f7225c5a9..df9b98fca9c 100644 --- a/src/storage/disk_table_iterator.h +++ b/src/storage/disk_table_iterator.h @@ -29,9 +29,10 @@ namespace storage { class DiskTableIterator : public TableIterator { public: - DiskTableIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, const std::string& pk); - DiskTableIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, const std::string& pk, - uint32_t ts_idx); + DiskTableIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, + const std::string& pk, type::CompressType compress_type); + DiskTableIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, + const std::string& pk, uint32_t ts_idx, type::CompressType compress_type); virtual ~DiskTableIterator(); bool Valid() override; void Next() override; @@ -49,16 +50,18 @@ class DiskTableIterator : public TableIterator { uint64_t ts_; uint32_t ts_idx_; bool has_ts_idx_ = false; + type::CompressType compress_type_; + mutable std::string tmp_buf_; }; class DiskTableTraverseIterator : public TraverseIterator { public: DiskTableTraverseIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, - const uint64_t& expire_cnt); + const uint64_t& expire_cnt, type::CompressType compress_type); DiskTableTraverseIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, - const uint64_t& expire_cnt, int32_t ts_idx); + const uint64_t& expire_cnt, int32_t ts_idx, type::CompressType compress_type); virtual ~DiskTableTraverseIterator(); bool Valid() override; void Next() override; @@ -84,13 +87,16 @@ class DiskTableTraverseIterator : public TraverseIterator { bool has_ts_idx_; uint32_t ts_idx_; uint64_t traverse_cnt_; + type::CompressType compress_type_; + mutable std::string tmp_buf_; }; class DiskTableRowIterator : public ::hybridse::vm::RowIterator { public: DiskTableRowIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, uint64_t expire_time, uint64_t expire_cnt, - std::string pk, uint64_t ts, bool has_ts_idx, uint32_t ts_idx); + std::string pk, uint64_t ts, bool has_ts_idx, uint32_t ts_idx, + type::CompressType compress_type); ~DiskTableRowIterator(); @@ -129,17 +135,21 @@ class DiskTableRowIterator : public ::hybridse::vm::RowIterator { ::hybridse::codec::Row row_; bool pk_valid_; bool valid_value_ = false; + type::CompressType compress_type_; + std::string tmp_buf_; }; class DiskTableKeyIterator : public ::hybridse::vm::WindowIterator { public: DiskTableKeyIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, const uint64_t& expire_cnt, - int32_t ts_idx, rocksdb::ColumnFamilyHandle* column_handle); + int32_t ts_idx, rocksdb::ColumnFamilyHandle* column_handle, + type::CompressType compress_type); DiskTableKeyIterator(rocksdb::DB* db, rocksdb::Iterator* it, const rocksdb::Snapshot* snapshot, ::openmldb::storage::TTLType ttl_type, const uint64_t& expire_time, const uint64_t& expire_cnt, - rocksdb::ColumnFamilyHandle* column_handle); + rocksdb::ColumnFamilyHandle* column_handle, + type::CompressType compress_type); ~DiskTableKeyIterator() override; @@ -171,6 +181,7 @@ class DiskTableKeyIterator : public ::hybridse::vm::WindowIterator { uint64_t ts_; uint32_t ts_idx_; rocksdb::ColumnFamilyHandle* column_handle_; + type::CompressType compress_type_; }; } // namespace storage diff --git a/src/storage/mem_table.cc b/src/storage/mem_table.cc index 8cbb145e323..a50e3c6dc82 100644 --- a/src/storage/mem_table.cc +++ b/src/storage/mem_table.cc @@ -170,14 +170,18 @@ bool MemTable::Put(uint64_t time, const std::string& value, const Dimensions& di PDLOG(WARNING, "invalid schema version %u, tid %u pid %u", version, id_, pid_); return false; } - std::map ts_map; + std::map> ts_value_map; for (const auto& kv : inner_index_key_map) { auto inner_index = table_index_.GetInnerIndex(kv.first); if (!inner_index) { PDLOG(WARNING, "invalid inner index pos %d. tid %u pid %u", kv.first, id_, pid_); return false; } + std::map ts_map; for (const auto& index_def : inner_index->GetIndex()) { + if (!index_def->IsReady()) { + continue; + } auto ts_col = index_def->GetTsColumn(); if (ts_col) { int64_t ts = 0; @@ -192,66 +196,43 @@ bool MemTable::Put(uint64_t time, const std::string& value, const Dimensions& di return false; } ts_map.emplace(ts_col->GetId(), ts); - } - if (index_def->IsReady()) { real_ref_cnt++; } } + if (!ts_map.empty()) { + ts_value_map.emplace(kv.first, std::move(ts_map)); + } } - if (ts_map.empty()) { + if (ts_value_map.empty()) { return false; } auto* block = new DataBlock(real_ref_cnt, value.c_str(), value.length()); for (const auto& kv : inner_index_key_map) { - auto inner_index = table_index_.GetInnerIndex(kv.first); - bool need_put = false; - for (const auto& index_def : inner_index->GetIndex()) { - if (index_def->IsReady()) { - // TODO(hw): if we don't find this ts(has_found_ts==false), but it's ready, will put too? - need_put = true; - break; - } + auto iter = ts_value_map.find(kv.first); + if (iter == ts_value_map.end()) { + continue; } - if (need_put) { - uint32_t seg_idx = 0; - if (seg_cnt_ > 1) { - seg_idx = ::openmldb::base::hash(kv.second.data(), kv.second.size(), SEED) % seg_cnt_; - } - Segment* segment = segments_[kv.first][seg_idx]; - segment->Put(::openmldb::base::Slice(kv.second), ts_map, block); + uint32_t seg_idx = 0; + if (seg_cnt_ > 1) { + seg_idx = ::openmldb::base::hash(kv.second.data(), kv.second.size(), SEED) % seg_cnt_; } + Segment* segment = segments_[kv.first][seg_idx]; + segment->Put(::openmldb::base::Slice(kv.second), iter->second, block); } record_byte_size_.fetch_add(GetRecordSize(value.length())); return true; } bool MemTable::Delete(const ::openmldb::api::LogEntry& entry) { + std::optional start_ts = entry.has_ts() ? std::optional{entry.ts()} + : std::nullopt; + std::optional end_ts = entry.has_end_ts() ? std::optional{entry.end_ts()} + : std::nullopt; if (entry.dimensions_size() > 0) { for (const auto& dimension : entry.dimensions()) { - auto index_def = GetIndex(dimension.idx()); - if (!index_def || !index_def->IsReady()) { + if (!Delete(dimension.idx(), dimension.key(), start_ts, end_ts)) { return false; } - auto ts_col = index_def->GetTsColumn(); - std::optional ts_idx = ts_col ? std::optional{ts_col->GetId()} : std::nullopt; - Slice spk(dimension.key()); - uint32_t seg_idx = 0; - if (seg_cnt_ > 1) { - seg_idx = base::hash(spk.data(), spk.size(), SEED) % seg_cnt_; - } - uint32_t real_idx = index_def->GetInnerPos(); - if (entry.has_ts() || entry.has_end_ts()) { - uint64_t start_ts = entry.has_ts() ? entry.ts() : UINT64_MAX; - std::optional end_ts = entry.has_end_ts() ? std::optional{entry.end_ts()} - : std::nullopt; - if (!segments_[real_idx][seg_idx]->Delete(ts_idx, spk, start_ts, end_ts)) { - return false; - } - } else { - if (!segments_[real_idx][seg_idx]->Delete(ts_idx, spk)) { - return false; - } - } } return true; } else { @@ -259,37 +240,46 @@ bool MemTable::Delete(const ::openmldb::api::LogEntry& entry) { if (!index_def || !index_def->IsReady()) { continue; } - uint32_t real_idx = index_def->GetInnerPos(); auto ts_col = index_def->GetTsColumn(); if (!ts_col->IsAutoGenTs() && ts_col->GetName() != entry.ts_name()) { continue; } - std::optional ts_idx = ts_col ? std::optional{ts_col->GetId()} : std::nullopt; uint32_t idx = index_def->GetId(); std::unique_ptr iter(NewTraverseIterator(idx)); iter->SeekToFirst(); while (iter->Valid()) { auto pk = iter->GetPK(); iter->NextPK(); - Slice spk(pk); - uint32_t seg_idx = 0; - if (seg_cnt_ > 1) { - seg_idx = base::hash(spk.data(), spk.size(), SEED) % seg_cnt_; - } - if (entry.has_ts() || entry.has_end_ts()) { - uint64_t start_ts = entry.has_ts() ? entry.ts() : UINT64_MAX; - std::optional end_ts = entry.has_end_ts() ? std::optional{entry.end_ts()} - : std::nullopt; - segments_[real_idx][seg_idx]->Delete(ts_idx, spk, start_ts, end_ts); - } else { - segments_[real_idx][seg_idx]->Delete(ts_idx, spk); - } + Delete(idx, pk, start_ts, end_ts); } } } return true; } +bool MemTable::Delete(uint32_t idx, const std::string& key, + const std::optional& start_ts, const std::optional& end_ts) { + auto index_def = GetIndex(idx); + if (!index_def || !index_def->IsReady()) { + return false; + } + uint32_t real_idx = index_def->GetInnerPos(); + auto ts_col = index_def->GetTsColumn(); + std::optional ts_idx = ts_col ? std::optional{ts_col->GetId()} : std::nullopt; + Slice spk(key); + uint32_t seg_idx = 0; + if (seg_cnt_ > 1) { + seg_idx = base::hash(spk.data(), spk.size(), SEED) % seg_cnt_; + } + if (!start_ts.has_value() && !end_ts.has_value()) { + return segments_[real_idx][seg_idx]->Delete(ts_idx, spk); + } else { + uint64_t real_start_ts = start_ts.has_value() ? start_ts.value() : UINT64_MAX; + return segments_[real_idx][seg_idx]->Delete(ts_idx, spk, real_start_ts, end_ts); + } + return true; +} + uint64_t MemTable::Release() { if (segment_released_) { return 0; @@ -433,6 +423,11 @@ bool MemTable::IsExpire(const LogEntry& entry) { } } const int8_t* data = reinterpret_cast(entry.value().data()); + std::string uncompress_data; + if (GetCompressType() == openmldb::type::kSnappy) { + snappy::Uncompress(entry.value().data(), entry.value().size(), &uncompress_data); + data = reinterpret_cast(uncompress_data.data()); + } uint8_t version = codec::RowView::GetSchemaVersion(data); auto decoder = GetVersionDecoder(version); if (decoder == nullptr) { @@ -523,9 +518,9 @@ TableIterator* MemTable::NewIterator(uint32_t index, const std::string& pk, Tick Segment* segment = segments_[real_idx][seg_idx]; auto ts_col = index_def->GetTsColumn(); if (ts_col) { - return segment->NewIterator(spk, ts_col->GetId(), ticket); + return segment->NewIterator(spk, ts_col->GetId(), ticket, GetCompressType()); } - return segment->NewIterator(spk, ticket); + return segment->NewIterator(spk, ticket, GetCompressType()); } uint64_t MemTable::GetRecordIdxByteSize() { @@ -749,7 +744,8 @@ ::hybridse::vm::WindowIterator* MemTable::NewWindowIterator(uint32_t index) { if (ts_col) { ts_idx = ts_col->GetId(); } - return new MemTableKeyIterator(segments_[real_idx], seg_cnt_, ttl->ttl_type, expire_time, expire_cnt, ts_idx); + return new MemTableKeyIterator(segments_[real_idx], seg_cnt_, ttl->ttl_type, + expire_time, expire_cnt, ts_idx, GetCompressType()); } TraverseIterator* MemTable::NewTraverseIterator(uint32_t index) { @@ -768,10 +764,11 @@ TraverseIterator* MemTable::NewTraverseIterator(uint32_t index) { uint32_t real_idx = index_def->GetInnerPos(); auto ts_col = index_def->GetTsColumn(); if (ts_col) { - return new MemTableTraverseIterator(segments_[real_idx], seg_cnt_, ttl->ttl_type, expire_time, expire_cnt, - ts_col->GetId()); + return new MemTableTraverseIterator(segments_[real_idx], seg_cnt_, ttl->ttl_type, + expire_time, expire_cnt, ts_col->GetId(), GetCompressType()); } - return new MemTableTraverseIterator(segments_[real_idx], seg_cnt_, ttl->ttl_type, expire_time, expire_cnt, 0); + return new MemTableTraverseIterator(segments_[real_idx], seg_cnt_, ttl->ttl_type, + expire_time, expire_cnt, 0, GetCompressType()); } bool MemTable::GetBulkLoadInfo(::openmldb::api::BulkLoadInfoResponse* response) { diff --git a/src/storage/mem_table.h b/src/storage/mem_table.h index 48e313b3eec..8ae1964e0ef 100644 --- a/src/storage/mem_table.h +++ b/src/storage/mem_table.h @@ -59,6 +59,8 @@ class MemTable : public Table { const ::google::protobuf::RepeatedPtrField<::openmldb::api::BulkLoadIndex>& indexes); bool Delete(const ::openmldb::api::LogEntry& entry) override; + bool Delete(uint32_t idx, const std::string& key, + const std::optional& start_ts, const std::optional& end_ts); // use the first demission TableIterator* NewIterator(const std::string& pk, Ticket& ticket) override; diff --git a/src/storage/mem_table_iterator.cc b/src/storage/mem_table_iterator.cc index 8b0f074427a..22cd7964640 100644 --- a/src/storage/mem_table_iterator.cc +++ b/src/storage/mem_table_iterator.cc @@ -15,7 +15,7 @@ */ #include "storage/mem_table_iterator.h" - +#include #include #include "base/hash.h" #include "gflags/gflags.h" @@ -48,7 +48,13 @@ const uint64_t& MemTableWindowIterator::GetKey() const { } const ::hybridse::codec::Row& MemTableWindowIterator::GetValue() { - row_.Reset(reinterpret_cast(it_->GetValue()->data), it_->GetValue()->size); + if (compress_type_ == type::CompressType::kSnappy) { + tmp_buf_.clear(); + snappy::Uncompress(it_->GetValue()->data, it_->GetValue()->size, &tmp_buf_); + row_.Reset(reinterpret_cast(tmp_buf_.data()), tmp_buf_.size()); + } else { + row_.Reset(reinterpret_cast(it_->GetValue()->data), it_->GetValue()->size); + } return row_; } @@ -69,7 +75,8 @@ void MemTableWindowIterator::SeekToFirst() { } MemTableKeyIterator::MemTableKeyIterator(Segment** segments, uint32_t seg_cnt, ::openmldb::storage::TTLType ttl_type, - uint64_t expire_time, uint64_t expire_cnt, uint32_t ts_index) + uint64_t expire_time, uint64_t expire_cnt, uint32_t ts_index, + type::CompressType compress_type) : segments_(segments), seg_cnt_(seg_cnt), seg_idx_(0), @@ -79,7 +86,8 @@ MemTableKeyIterator::MemTableKeyIterator(Segment** segments, uint32_t seg_cnt, : expire_time_(expire_time), expire_cnt_(expire_cnt), ticket_(), - ts_idx_(0) { + ts_idx_(0), + compress_type_(compress_type) { uint32_t idx = 0; if (segments_[0]->GetTsIdx(ts_index, idx) == 0) { ts_idx_ = idx; @@ -142,7 +150,7 @@ ::hybridse::vm::RowIterator* MemTableKeyIterator::GetRawValue() { ticket_.Push((KeyEntry*)pk_it_->GetValue()); // NOLINT } it->SeekToFirst(); - return new MemTableWindowIterator(it, ttl_type_, expire_time_, expire_cnt_); + return new MemTableWindowIterator(it, ttl_type_, expire_time_, expire_cnt_, compress_type_); } std::unique_ptr<::hybridse::vm::RowIterator> MemTableKeyIterator::GetValue() { @@ -177,8 +185,9 @@ void MemTableKeyIterator::NextPK() { } MemTableTraverseIterator::MemTableTraverseIterator(Segment** segments, uint32_t seg_cnt, - ::openmldb::storage::TTLType ttl_type, uint64_t expire_time, - uint64_t expire_cnt, uint32_t ts_index) + ::openmldb::storage::TTLType ttl_type, uint64_t expire_time, + uint64_t expire_cnt, uint32_t ts_index, + type::CompressType compress_type) : segments_(segments), seg_cnt_(seg_cnt), seg_idx_(0), @@ -188,7 +197,8 @@ MemTableTraverseIterator::MemTableTraverseIterator(Segment** segments, uint32_t ts_idx_(0), expire_value_(expire_time, expire_cnt, ttl_type), ticket_(), - traverse_cnt_(0) { + traverse_cnt_(0), + compress_type_(compress_type) { uint32_t idx = 0; if (segments_[0]->GetTsIdx(ts_index, idx) == 0) { ts_idx_ = idx; @@ -320,7 +330,13 @@ void MemTableTraverseIterator::Seek(const std::string& key, uint64_t ts) { } openmldb::base::Slice MemTableTraverseIterator::GetValue() const { - return openmldb::base::Slice(it_->GetValue()->data, it_->GetValue()->size); + if (compress_type_ == type::CompressType::kSnappy) { + tmp_buf_.clear(); + snappy::Uncompress(it_->GetValue()->data, it_->GetValue()->size, &tmp_buf_); + return openmldb::base::Slice(tmp_buf_); + } else { + return openmldb::base::Slice(it_->GetValue()->data, it_->GetValue()->size); + } } uint64_t MemTableTraverseIterator::GetKey() const { diff --git a/src/storage/mem_table_iterator.h b/src/storage/mem_table_iterator.h index 967345fc2a9..5e5ba461181 100644 --- a/src/storage/mem_table_iterator.h +++ b/src/storage/mem_table_iterator.h @@ -27,8 +27,9 @@ namespace storage { class MemTableWindowIterator : public ::hybridse::vm::RowIterator { public: MemTableWindowIterator(TimeEntries::Iterator* it, ::openmldb::storage::TTLType ttl_type, uint64_t expire_time, - uint64_t expire_cnt) - : it_(it), record_idx_(1), expire_value_(expire_time, expire_cnt, ttl_type), row_() {} + uint64_t expire_cnt, type::CompressType compress_type) + : it_(it), record_idx_(1), expire_value_(expire_time, expire_cnt, ttl_type), + row_(), compress_type_(compress_type) {} ~MemTableWindowIterator(); @@ -51,12 +52,15 @@ class MemTableWindowIterator : public ::hybridse::vm::RowIterator { uint32_t record_idx_; TTLSt expire_value_; ::hybridse::codec::Row row_; + type::CompressType compress_type_; + std::string tmp_buf_; }; class MemTableKeyIterator : public ::hybridse::vm::WindowIterator { public: MemTableKeyIterator(Segment** segments, uint32_t seg_cnt, ::openmldb::storage::TTLType ttl_type, - uint64_t expire_time, uint64_t expire_cnt, uint32_t ts_index); + uint64_t expire_time, uint64_t expire_cnt, uint32_t ts_index, + type::CompressType compress_type); ~MemTableKeyIterator() override; @@ -87,12 +91,14 @@ class MemTableKeyIterator : public ::hybridse::vm::WindowIterator { uint64_t expire_cnt_; Ticket ticket_; uint32_t ts_idx_; + type::CompressType compress_type_; }; class MemTableTraverseIterator : public TraverseIterator { public: MemTableTraverseIterator(Segment** segments, uint32_t seg_cnt, ::openmldb::storage::TTLType ttl_type, - uint64_t expire_time, uint64_t expire_cnt, uint32_t ts_index); + uint64_t expire_time, uint64_t expire_cnt, uint32_t ts_index, + type::CompressType compress_type); ~MemTableTraverseIterator() override; inline bool Valid() override; void Next() override; @@ -115,6 +121,8 @@ class MemTableTraverseIterator : public TraverseIterator { TTLSt expire_value_; Ticket ticket_; uint64_t traverse_cnt_; + type::CompressType compress_type_; + mutable std::string tmp_buf_; }; } // namespace storage diff --git a/src/storage/mem_table_snapshot.cc b/src/storage/mem_table_snapshot.cc index 3eaacd3e2ac..2b085df59be 100644 --- a/src/storage/mem_table_snapshot.cc +++ b/src/storage/mem_table_snapshot.cc @@ -1041,5 +1041,39 @@ ::openmldb::base::Status MemTableSnapshot::ExtractIndexData(const std::shared_pt return status; } +int MemTableSnapshot::Truncate(uint64_t offset, uint64_t term) { + if (making_snapshot_.load(std::memory_order_acquire)) { + PDLOG(INFO, "snapshot is doing now!"); + return -1; + } + if (offset < offset_) { + PDLOG(WARNING, "end_offset %lu less than offset_ %lu, do nothing", offset, offset_); + return -1; + } + making_snapshot_.store(true, std::memory_order_release); + absl::Cleanup clean = [this] { + this->making_snapshot_.store(false, std::memory_order_release); + this->delete_collector_.Clear(); + }; + MemSnapshotMeta snapshot_meta(GenSnapshotName(), snapshot_path_, FLAGS_snapshot_compression); + snapshot_meta.term = term; + snapshot_meta.count = 0; + snapshot_meta.offset = offset; + auto wh = ::openmldb::log::CreateWriteHandle(FLAGS_snapshot_compression, + snapshot_meta.snapshot_name, snapshot_meta.tmp_file_path); + if (!wh) { + PDLOG(WARNING, "fail to create file %s", snapshot_meta.tmp_file_path.c_str()); + return -1; + } + wh->EndLog(); + wh.reset(); + auto status = WriteSnapshot(snapshot_meta); + if (!status.OK()) { + PDLOG(WARNING, "write snapshot failed. tid %u pid %u msg is %s ", tid_, pid_, status.GetMsg().c_str()); + return -1; + } + return 0; +} + } // namespace storage } // namespace openmldb diff --git a/src/storage/mem_table_snapshot.h b/src/storage/mem_table_snapshot.h index caad8fd182c..54da3a8c8c4 100644 --- a/src/storage/mem_table_snapshot.h +++ b/src/storage/mem_table_snapshot.h @@ -192,6 +192,8 @@ class MemTableSnapshot : public Snapshot { int CheckDeleteAndUpdate(std::shared_ptr
table, ::openmldb::api::LogEntry* new_entry); + int Truncate(uint64_t offset, uint64_t term); + private: // load single snapshot to table void RecoverSingleSnapshot(const std::string& path, std::shared_ptr
table, std::atomic* g_succ_cnt, diff --git a/src/storage/segment.cc b/src/storage/segment.cc index aec7f083b36..d79b6e85681 100644 --- a/src/storage/segment.cc +++ b/src/storage/segment.cc @@ -15,7 +15,7 @@ */ #include "storage/segment.h" - +#include #include #include "base/glog_wrapper.h" @@ -742,36 +742,38 @@ int Segment::GetCount(const Slice& key, uint32_t idx, uint64_t& count) { return 0; } -MemTableIterator* Segment::NewIterator(const Slice& key, Ticket& ticket) { +MemTableIterator* Segment::NewIterator(const Slice& key, Ticket& ticket, type::CompressType compress_type) { if (entries_ == nullptr || ts_cnt_ > 1) { - return new MemTableIterator(nullptr); + return new MemTableIterator(nullptr, compress_type); } void* entry = nullptr; if (entries_->Get(key, entry) < 0 || entry == nullptr) { - return new MemTableIterator(nullptr); + return new MemTableIterator(nullptr, compress_type); } ticket.Push(reinterpret_cast(entry)); - return new MemTableIterator(reinterpret_cast(entry)->entries.NewIterator()); + return new MemTableIterator(reinterpret_cast(entry)->entries.NewIterator(), compress_type); } -MemTableIterator* Segment::NewIterator(const Slice& key, uint32_t idx, Ticket& ticket) { +MemTableIterator* Segment::NewIterator(const Slice& key, uint32_t idx, + Ticket& ticket, type::CompressType compress_type) { auto pos = ts_idx_map_.find(idx); if (pos == ts_idx_map_.end()) { - return new MemTableIterator(nullptr); + return new MemTableIterator(nullptr, compress_type); } if (ts_cnt_ == 1) { - return NewIterator(key, ticket); + return NewIterator(key, ticket, compress_type); } void* entry_arr = nullptr; if (entries_->Get(key, entry_arr) < 0 || entry_arr == nullptr) { - return new MemTableIterator(nullptr); + return new MemTableIterator(nullptr, compress_type); } auto entry = reinterpret_cast(entry_arr)[pos->second]; ticket.Push(entry); - return new MemTableIterator(entry->entries.NewIterator()); + return new MemTableIterator(entry->entries.NewIterator(), compress_type); } -MemTableIterator::MemTableIterator(TimeEntries::Iterator* it) : it_(it) {} +MemTableIterator::MemTableIterator(TimeEntries::Iterator* it, type::CompressType compress_type) + : it_(it), compress_type_(compress_type) {} MemTableIterator::~MemTableIterator() { if (it_ != nullptr) { @@ -797,6 +799,11 @@ void MemTableIterator::Next() { } ::openmldb::base::Slice MemTableIterator::GetValue() const { + if (compress_type_ == type::CompressType::kSnappy) { + tmp_buf_.clear(); + snappy::Uncompress(it_->GetValue()->data, it_->GetValue()->size, &tmp_buf_); + return openmldb::base::Slice(tmp_buf_); + } return ::openmldb::base::Slice(it_->GetValue()->data, it_->GetValue()->size); } diff --git a/src/storage/segment.h b/src/storage/segment.h index 8e320400e39..fe58dd893a0 100644 --- a/src/storage/segment.h +++ b/src/storage/segment.h @@ -22,6 +22,7 @@ #include #include // NOLINT #include +#include #include #include "base/skiplist.h" @@ -40,7 +41,7 @@ using ::openmldb::base::Slice; class MemTableIterator : public TableIterator { public: - explicit MemTableIterator(TimeEntries::Iterator* it); + explicit MemTableIterator(TimeEntries::Iterator* it, type::CompressType compress_type); virtual ~MemTableIterator(); void Seek(const uint64_t time) override; bool Valid() override; @@ -52,6 +53,8 @@ class MemTableIterator : public TableIterator { private: TimeEntries::Iterator* it_; + type::CompressType compress_type_; + mutable std::string tmp_buf_; }; struct SliceComparator { @@ -93,9 +96,9 @@ class Segment { void Gc4TTLOrHead(const uint64_t time, const uint64_t keep_cnt, StatisticsInfo* statistics_info); void GcAllType(const std::map& ttl_st_map, StatisticsInfo* statistics_info); - MemTableIterator* NewIterator(const Slice& key, Ticket& ticket); // NOLINT + MemTableIterator* NewIterator(const Slice& key, Ticket& ticket, type::CompressType compress_type); // NOLINT MemTableIterator* NewIterator(const Slice& key, uint32_t idx, - Ticket& ticket); // NOLINT + Ticket& ticket, type::CompressType compress_type); // NOLINT uint64_t GetIdxCnt() const { return idx_cnt_vec_[0]->load(std::memory_order_relaxed); diff --git a/src/storage/segment_test.cc b/src/storage/segment_test.cc index 8b4728a9150..c51c0984473 100644 --- a/src/storage/segment_test.cc +++ b/src/storage/segment_test.cc @@ -61,7 +61,7 @@ TEST_F(SegmentTest, PutAndScan) { segment.Put(pk, 9529, value.c_str(), value.size()); ASSERT_EQ(1, (int64_t)segment.GetPkCnt()); Ticket ticket; - std::unique_ptr it(segment.NewIterator("test1", ticket)); + std::unique_ptr it(segment.NewIterator("test1", ticket, type::CompressType::kNoCompress)); it->Seek(9530); ASSERT_TRUE(it->Valid()); ASSERT_EQ(9529, (int64_t)it->GetKey()); @@ -103,7 +103,7 @@ TEST_F(SegmentTest, Delete) { segment.Put(pk, 9529, value.c_str(), value.size()); ASSERT_EQ(1, (int64_t)segment.GetPkCnt()); Ticket ticket; - std::unique_ptr it(segment.NewIterator("test1", ticket)); + std::unique_ptr it(segment.NewIterator("test1", ticket, type::CompressType::kNoCompress)); int size = 0; it->SeekToFirst(); while (it->Valid()) { @@ -112,7 +112,7 @@ TEST_F(SegmentTest, Delete) { } ASSERT_EQ(4, size); ASSERT_TRUE(segment.Delete(std::nullopt, pk)); - it.reset(segment.NewIterator("test1", ticket)); + it.reset(segment.NewIterator("test1", ticket, type::CompressType::kNoCompress)); ASSERT_FALSE(it->Valid()); segment.IncrGcVersion(); segment.IncrGcVersion(); @@ -178,7 +178,7 @@ TEST_F(SegmentTest, Iterator) { segment.Put(pk, 9769, "test2", 5); ASSERT_EQ(1, (int64_t)segment.GetPkCnt()); Ticket ticket; - std::unique_ptr it(segment.NewIterator("test1", ticket)); + std::unique_ptr it(segment.NewIterator("test1", ticket, type::CompressType::kNoCompress)); it->SeekToFirst(); int size = 0; while (it->Valid()) { @@ -208,7 +208,7 @@ TEST_F(SegmentTest, TestGc4Head) { segment.Gc4Head(1, &gc_info); CheckStatisticsInfo(CreateStatisticsInfo(1, 0, GetRecordSize(5)), gc_info); Ticket ticket; - std::unique_ptr it(segment.NewIterator(pk, ticket)); + std::unique_ptr it(segment.NewIterator(pk, ticket, type::CompressType::kNoCompress)); it->Seek(9769); ASSERT_TRUE(it->Valid()); ASSERT_EQ(9769, (int64_t)it->GetKey()); @@ -401,7 +401,7 @@ TEST_F(SegmentTest, TestDeleteRange) { ASSERT_EQ(100, GetCount(&segment, 0)); std::string pk = "key2"; Ticket ticket; - std::unique_ptr it(segment.NewIterator(pk, ticket)); + std::unique_ptr it(segment.NewIterator(pk, ticket, type::CompressType::kNoCompress)); it->Seek(1005); ASSERT_TRUE(it->Valid() && it->GetKey() == 1005); ASSERT_TRUE(segment.Delete(std::nullopt, pk, 1005, 1004)); diff --git a/src/storage/snapshot_test.cc b/src/storage/snapshot_test.cc index bd1be720e8a..910a8bc7724 100644 --- a/src/storage/snapshot_test.cc +++ b/src/storage/snapshot_test.cc @@ -718,6 +718,79 @@ TEST_F(SnapshotTest, Recover_only_snapshot) { ASSERT_FALSE(it->Valid()); } +TEST_F(SnapshotTest, RecoverWithDeleteIndex) { + uint32_t tid = 12; + uint32_t pid = 0; + ::openmldb::api::TableMeta meta; + meta.set_tid(tid); + meta.set_pid(pid); + SchemaCodec::SetColumnDesc(meta.add_column_desc(), "userid", ::openmldb::type::kString); + SchemaCodec::SetColumnDesc(meta.add_column_desc(), "ts1", ::openmldb::type::kBigInt); + SchemaCodec::SetColumnDesc(meta.add_column_desc(), "ts2", ::openmldb::type::kBigInt); + SchemaCodec::SetColumnDesc(meta.add_column_desc(), "val", ::openmldb::type::kString); + SchemaCodec::SetIndex(meta.add_column_key(), "index1", "userid", "ts1", ::openmldb::type::kLatestTime, 0, 1); + SchemaCodec::SetIndex(meta.add_column_key(), "index2", "userid", "ts2", ::openmldb::type::kLatestTime, 0, 1); + + std::string snapshot_dir = absl::StrCat(FLAGS_db_root_path, "/", tid, "_", pid, "/snapshot"); + + ::openmldb::base::MkdirRecur(snapshot_dir); + std::string snapshot1 = "20231018.sdb"; + uint64_t offset = 0; + { + if (FLAGS_snapshot_compression != "off") { + snapshot1.append("."); + snapshot1.append(FLAGS_snapshot_compression); + } + std::string full_path = snapshot_dir + "/" + snapshot1; + FILE* fd_w = fopen(full_path.c_str(), "ab+"); + ASSERT_TRUE(fd_w != NULL); + ::openmldb::log::WritableFile* wf = ::openmldb::log::NewWritableFile(snapshot1, fd_w); + ::openmldb::log::Writer writer(FLAGS_snapshot_compression, wf); + ::openmldb::codec::SDKCodec sdk_codec(meta); + for (int i = 0; i < 5; i++) { + uint32_t ts = 100 + i; + for (int key_num = 0; key_num < 10; key_num++) { + std::string userid = absl::StrCat("userid", key_num); + std::string ts_str = std::to_string(ts); + std::vector row = {userid, ts_str, ts_str, "aa"}; + std::string result; + sdk_codec.EncodeRow(row, &result); + ::openmldb::api::LogEntry entry; + entry.set_log_index(offset++); + entry.set_value(result); + for (int k = 0; k < meta.column_key_size(); k++) { + auto dimension = entry.add_dimensions(); + dimension->set_key(userid); + dimension->set_idx(k); + } + entry.set_ts(ts); + entry.set_term(1); + std::string val; + bool ok = entry.SerializeToString(&val); + ASSERT_TRUE(ok); + Slice sval(val.c_str(), val.size()); + ::openmldb::log::Status status = writer.AddRecord(sval); + ASSERT_TRUE(status.ok()); + } + } + writer.EndLog(); + } + + auto index1 = meta.mutable_column_key(1); + index1->set_flag(1); + std::shared_ptr table = std::make_shared(meta); + table->Init(); + LogParts* log_part = new LogParts(12, 4, scmp); + MemTableSnapshot snapshot(tid, pid, log_part, FLAGS_db_root_path); + ASSERT_TRUE(snapshot.Init()); + int ret = snapshot.GenManifest(snapshot1, 50, offset, 1); + ASSERT_EQ(0, ret); + uint64_t r_offset = 0; + ASSERT_TRUE(snapshot.Recover(table, r_offset)); + ASSERT_EQ(r_offset, offset); + table->SchedGc(); +} + TEST_F(SnapshotTest, MakeSnapshot) { LogParts* log_part = new LogParts(12, 4, scmp); MemTableSnapshot snapshot(1, 2, log_part, FLAGS_db_root_path); diff --git a/src/storage/table.h b/src/storage/table.h index 55c89d7674a..32a957c9db7 100644 --- a/src/storage/table.h +++ b/src/storage/table.h @@ -59,6 +59,9 @@ class Table { virtual bool Delete(const ::openmldb::api::LogEntry& entry) = 0; + virtual bool Delete(uint32_t idx, const std::string& key, + const std::optional& start_ts, const std::optional& end_ts) = 0; + virtual TableIterator* NewIterator(const std::string& pk, Ticket& ticket) = 0; // NOLINT diff --git a/src/tablet/combine_iterator.h b/src/tablet/combine_iterator.h index 1250cb83ca2..d7b97ddbb03 100644 --- a/src/tablet/combine_iterator.h +++ b/src/tablet/combine_iterator.h @@ -27,7 +27,7 @@ namespace tablet { __attribute__((unused)) static bool SeekWithCount(::openmldb::storage::TableIterator* it, const uint64_t time, const ::openmldb::api::GetType& type, uint32_t max_cnt, uint32_t* cnt) { - if (it == NULL) { + if (it == nullptr) { return false; } it->SeekToFirst(); @@ -63,7 +63,7 @@ __attribute__((unused)) static bool SeekWithCount(::openmldb::storage::TableIter __attribute__((unused)) static bool Seek(::openmldb::storage::TableIterator* it, const uint64_t time, const ::openmldb::api::GetType& type) { - if (it == NULL) { + if (it == nullptr) { return false; } switch (type) { @@ -91,15 +91,15 @@ __attribute__((unused)) static bool Seek(::openmldb::storage::TableIterator* it, __attribute__((unused)) static int GetIterator(std::shared_ptr<::openmldb::storage::Table> table, const std::string& pk, int index, std::shared_ptr<::openmldb::storage::TableIterator>* it, std::shared_ptr<::openmldb::storage::Ticket>* ticket) { - if (it == NULL || ticket == NULL) { + if (it == nullptr || ticket == nullptr) { return -1; } if (!(*ticket)) { *ticket = std::make_shared<::openmldb::storage::Ticket>(); } - ::openmldb::storage::TableIterator* cur_it = NULL; + ::openmldb::storage::TableIterator* cur_it = nullptr; cur_it = table->NewIterator(index, pk, *(ticket->get())); - if (cur_it == NULL) { + if (cur_it == nullptr) { return -1; } it->reset(cur_it); diff --git a/src/tablet/tablet_impl.cc b/src/tablet/tablet_impl.cc index f30f1f8b74b..2c506be510f 100644 --- a/src/tablet/tablet_impl.cc +++ b/src/tablet/tablet_impl.cc @@ -20,8 +20,6 @@ #include #include #include -#include "absl/time/clock.h" -#include "absl/time/time.h" #ifdef DISALLOW_COPY_AND_ASSIGN #undef DISALLOW_COPY_AND_ASSIGN #endif @@ -34,12 +32,10 @@ #include #include "absl/cleanup/cleanup.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "boost/bind.hpp" #include "boost/container/deque.hpp" -#include "config.h" // NOLINT -#ifdef TCMALLOC_ENABLE -#include "gperftools/malloc_extension.h" -#endif #include "base/file_util.h" #include "base/glog_wrapper.h" #include "base/hash.h" @@ -53,8 +49,12 @@ #include "codec/row_codec.h" #include "codec/sql_rpc_row_codec.h" #include "common/timer.h" +#include "config.h" // NOLINT #include "gflags/gflags.h" #include "glog/logging.h" +#ifdef TCMALLOC_ENABLE +#include "gperftools/malloc_extension.h" +#endif #include "google/protobuf/io/zero_copy_stream_impl.h" #include "google/protobuf/text_format.h" #include "nameserver/task.h" @@ -66,11 +66,8 @@ #include "tablet/file_sender.h" using ::openmldb::base::ReturnCode; -using ::openmldb::codec::SchemaCodec; -using ::openmldb::storage::DataBlock; using ::openmldb::storage::DiskTable; using ::openmldb::storage::Table; -using google::protobuf::RepeatedPtrField; DECLARE_int32(gc_interval); DECLARE_int32(gc_pool_size); @@ -110,6 +107,8 @@ DECLARE_string(zk_cluster); DECLARE_string(zk_root_path); DECLARE_int32(zk_session_timeout); DECLARE_int32(zk_keep_alive_check_interval); +DECLARE_string(zk_auth_schema); +DECLARE_string(zk_cert); DECLARE_int32(binlog_sync_to_disk_interval); DECLARE_int32(binlog_delete_interval); @@ -125,7 +124,6 @@ DECLARE_int32(snapshot_pool_size); namespace openmldb { namespace tablet { -static const std::string SERVER_CONCURRENCY_KEY = "server"; // NOLINT static const uint32_t SEED = 0xe17a1465; static constexpr const char DEPLOY_STATS[] = "deploy_stats"; @@ -191,10 +189,14 @@ bool TabletImpl::Init(const std::string& zk_cluster, const std::string& zk_path, mode_recycle_root_paths_[::openmldb::common::kSSD]); ::openmldb::base::SplitString(FLAGS_recycle_bin_hdd_root_path, ",", mode_recycle_root_paths_[::openmldb::common::kHDD]); - deploy_collector_ = std::make_unique<::openmldb::statistics::DeployQueryTimeCollector>(); + // if want /brpc_metrics, prefix should be g_server_info_prefix+(when no server_info_name), means + // rpc_server_ if standalone, diy + deploy_collector_ = std::make_unique<::openmldb::statistics::DeploymentMetricCollector>( + "rpc_server_" + endpoint.substr(endpoint.find(":") + 1)); if (!zk_cluster.empty()) { - zk_client_ = new ZkClient(zk_cluster, real_endpoint, FLAGS_zk_session_timeout, endpoint, zk_path); + zk_client_ = new ZkClient(zk_cluster, real_endpoint, FLAGS_zk_session_timeout, endpoint, zk_path, + FLAGS_zk_auth_schema, FLAGS_zk_cert); bool ok = zk_client_->Init(); if (!ok) { PDLOG(ERROR, "fail to init zookeeper with cluster %s", zk_cluster.c_str()); @@ -212,9 +214,8 @@ bool TabletImpl::Init(const std::string& zk_cluster, const std::string& zk_path, } else { options.SetClusterOptimized(false); } - engine_ = std::unique_ptr<::hybridse::vm::Engine>(new ::hybridse::vm::Engine(catalog_, options)); - catalog_->SetLocalTablet( - std::shared_ptr<::hybridse::vm::Tablet>(new ::hybridse::vm::LocalTablet(engine_.get(), sp_cache_))); + engine_ = std::make_unique<::hybridse::vm::Engine>(catalog_, options); + catalog_->SetLocalTablet(std::make_shared<::hybridse::vm::LocalTablet>(engine_.get(), sp_cache_)); std::set snapshot_compression_set{"off", "zlib", "snappy"}; if (snapshot_compression_set.find(FLAGS_snapshot_compression) == snapshot_compression_set.end()) { LOG(ERROR) << "wrong snapshot_compression: " << FLAGS_snapshot_compression; @@ -463,9 +464,6 @@ int32_t TabletImpl::GetIndex(const ::openmldb::api::GetRequest* request, const : bool enable_project = false; openmldb::codec::RowProject row_project(vers_schema, request->projection()); if (request->projection().size() > 0) { - if (meta.compress_type() == ::openmldb::type::kSnappy) { - return -1; - } bool ok = row_project.Init(); if (!ok) { PDLOG(WARNING, "invalid project list"); @@ -724,6 +722,22 @@ void TabletImpl::Put(RpcController* controller, const ::openmldb::api::PutReques response->set_msg("exceed max memory"); return; } + ::openmldb::api::LogEntry entry; + entry.set_pk(request->pk()); + entry.set_ts(request->time()); + if (table->GetCompressType() == openmldb::type::CompressType::kSnappy) { + const auto& raw_val = request->value(); + std::string* val = entry.mutable_value(); + ::snappy::Compress(raw_val.c_str(), raw_val.length(), val); + } else { + entry.set_value(request->value()); + } + if (request->dimensions_size() > 0) { + entry.mutable_dimensions()->CopyFrom(request->dimensions()); + } + if (request->ts_dimensions_size() > 0) { + entry.mutable_ts_dimensions()->CopyFrom(request->ts_dimensions()); + } bool ok = false; if (request->dimensions_size() > 0) { int32_t ret_code = CheckDimessionPut(request, table->GetIdxCnt()); @@ -733,7 +747,7 @@ void TabletImpl::Put(RpcController* controller, const ::openmldb::api::PutReques return; } DLOG(INFO) << "put data to tid " << tid << " pid " << pid << " with key " << request->dimensions(0).key(); - ok = table->Put(request->time(), request->value(), request->dimensions()); + ok = table->Put(entry.ts(), entry.value(), entry.dimensions()); } if (!ok) { response->set_code(::openmldb::base::ReturnCode::kPutFailed); @@ -743,23 +757,13 @@ void TabletImpl::Put(RpcController* controller, const ::openmldb::api::PutReques response->set_code(::openmldb::base::ReturnCode::kOk); std::shared_ptr replicator; - ::openmldb::api::LogEntry entry; do { replicator = GetReplicator(request->tid(), request->pid()); if (!replicator) { PDLOG(WARNING, "fail to find table tid %u pid %u leader's log replicator", tid, pid); break; } - entry.set_pk(request->pk()); - entry.set_ts(request->time()); - entry.set_value(request->value()); entry.set_term(replicator->GetLeaderTerm()); - if (request->dimensions_size() > 0) { - entry.mutable_dimensions()->CopyFrom(request->dimensions()); - } - if (request->ts_dimensions_size() > 0) { - entry.mutable_ts_dimensions()->CopyFrom(request->ts_dimensions()); - } // Aggregator update assumes that binlog_offset is strictly increasing // so the update should be protected within the replicator lock @@ -885,7 +889,7 @@ int TabletImpl::CheckTableMeta(const openmldb::api::TableMeta* table_meta, std:: } int32_t TabletImpl::ScanIndex(const ::openmldb::api::ScanRequest* request, const ::openmldb::api::TableMeta& meta, - const std::map>& vers_schema, + const std::map>& vers_schema, bool use_attachment, CombineIterator* combine_it, butil::IOBuf* io_buf, uint32_t* count, bool* is_finish) { uint32_t limit = request->limit(); if (combine_it == nullptr || io_buf == nullptr || count == nullptr || is_finish == nullptr) { @@ -909,12 +913,7 @@ int32_t TabletImpl::ScanIndex(const ::openmldb::api::ScanRequest* request, const bool enable_project = false; ::openmldb::codec::RowProject row_project(vers_schema, request->projection()); if (request->projection().size() > 0) { - if (meta.compress_type() == ::openmldb::type::kSnappy) { - LOG(WARNING) << "project on compress row data do not eing supported"; - return -1; - } - bool ok = row_project.Init(); - if (!ok) { + if (!row_project.Init()) { PDLOG(WARNING, "invalid project list"); return -1; } @@ -955,11 +954,19 @@ int32_t TabletImpl::ScanIndex(const ::openmldb::api::ScanRequest* request, const PDLOG(WARNING, "fail to make a projection"); return -4; } - io_buf->append(reinterpret_cast(ptr), size); + if (use_attachment) { + io_buf->append(reinterpret_cast(ptr), size); + } else { + ::openmldb::codec::Encode(ts, reinterpret_cast(ptr), size, io_buf); + } total_block_size += size; } else { openmldb::base::Slice data = combine_it->GetValue(); - io_buf->append(reinterpret_cast(data.data()), data.size()); + if (use_attachment) { + io_buf->append(reinterpret_cast(data.data()), data.size()); + } else { + ::openmldb::codec::Encode(ts, data.data(), data.size(), io_buf); + } total_block_size += data.size(); } record_count++; @@ -972,98 +979,6 @@ int32_t TabletImpl::ScanIndex(const ::openmldb::api::ScanRequest* request, const *count = record_count; return 0; } -int32_t TabletImpl::ScanIndex(const ::openmldb::api::ScanRequest* request, const ::openmldb::api::TableMeta& meta, - const std::map>& vers_schema, - CombineIterator* combine_it, std::string* pairs, uint32_t* count, bool* is_finish) { - uint32_t limit = request->limit(); - if (combine_it == nullptr || pairs == nullptr || count == nullptr || is_finish == nullptr) { - PDLOG(WARNING, "invalid args"); - return -1; - } - uint64_t st = request->st(); - uint64_t et = request->et(); - uint64_t expire_time = combine_it->GetExpireTime(); - ::openmldb::storage::TTLType ttl_type = combine_it->GetTTLType(); - if (ttl_type == ::openmldb::storage::TTLType::kAbsoluteTime || - ttl_type == ::openmldb::storage::TTLType::kAbsOrLat) { - et = std::max(et, expire_time); - } - if (st > 0 && st < et) { - PDLOG(WARNING, "invalid args for st %lu less than et %lu or expire time %lu", st, et, expire_time); - return -1; - } - - bool enable_project = false; - ::openmldb::codec::RowProject row_project(vers_schema, request->projection()); - if (!request->projection().empty()) { - if (meta.compress_type() == ::openmldb::type::kSnappy) { - LOG(WARNING) << "project on compress row data, not supported"; - return -1; - } - bool ok = row_project.Init(); - if (!ok) { - PDLOG(WARNING, "invalid project list"); - return -1; - } - enable_project = true; - } - bool remove_duplicated_record = request->enable_remove_duplicated_record(); - uint64_t last_time = 0; - boost::container::deque> tmp; - uint32_t total_block_size = 0; - combine_it->SeekToFirst(); - uint32_t skip_record_num = request->skip_record_num(); - while (combine_it->Valid()) { - if (limit > 0 && tmp.size() >= limit) { - *is_finish = false; - break; - } - if (remove_duplicated_record && !tmp.empty() && last_time == combine_it->GetTs()) { - combine_it->Next(); - continue; - } - if (combine_it->GetTs() == st && skip_record_num > 0) { - skip_record_num--; - combine_it->Next(); - continue; - } - uint64_t ts = combine_it->GetTs(); - if (ts <= et) { - break; - } - last_time = ts; - if (enable_project) { - int8_t* ptr = nullptr; - uint32_t size = 0; - openmldb::base::Slice data = combine_it->GetValue(); - const auto* row_ptr = reinterpret_cast(data.data()); - bool ok = row_project.Project(row_ptr, data.size(), &ptr, &size); - if (!ok) { - PDLOG(WARNING, "fail to make a projection"); - return -4; - } - tmp.emplace_back(ts, Slice(reinterpret_cast(ptr), size, true)); - total_block_size += size; - } else { - openmldb::base::Slice data = combine_it->GetValue(); - total_block_size += data.size(); - tmp.emplace_back(ts, data); - } - if (total_block_size > FLAGS_scan_max_bytes_size) { - LOG(WARNING) << "reach the max byte size " << FLAGS_scan_max_bytes_size << " cur is " << total_block_size; - *is_finish = false; - break; - } - combine_it->Next(); - } - int32_t ok = ::openmldb::codec::EncodeRows(tmp, total_block_size, pairs); - if (ok == -1) { - PDLOG(WARNING, "fail to encode rows"); - return -4; - } - *count = tmp.size(); - return 0; -} int32_t TabletImpl::CountIndex(uint64_t expire_time, uint64_t expire_cnt, ::openmldb::storage::TTLType ttl_type, ::openmldb::storage::TableIterator* it, const ::openmldb::api::CountRequest* request, @@ -1252,12 +1167,13 @@ void TabletImpl::Scan(RpcController* controller, const ::openmldb::api::ScanRequ int32_t code = 0; bool is_finish = true; if (!request->has_use_attachment() || !request->use_attachment()) { - std::string* pairs = response->mutable_pairs(); - code = ScanIndex(request, *table_meta, vers_schema, &combine_it, pairs, &count, &is_finish); + butil::IOBuf buf; + code = ScanIndex(request, *table_meta, vers_schema, false, &combine_it, &buf, &count, &is_finish); + buf.copy_to(response->mutable_pairs()); } else { auto* cntl = dynamic_cast(controller); butil::IOBuf& buf = cntl->response_attachment(); - code = ScanIndex(request, *table_meta, vers_schema, &combine_it, &buf, &count, &is_finish); + code = ScanIndex(request, *table_meta, vers_schema, true, &combine_it, &buf, &count, &is_finish); response->set_buf_size(buf.size()); DLOG(INFO) << " scan " << request->pk() << " with buf size " << buf.size(); } @@ -1440,14 +1356,12 @@ void TabletImpl::Traverse(RpcController* controller, const ::openmldb::api::Trav DEBUGLOG("tid %u, pid %u seek to first", tid, pid); it->SeekToFirst(); } - std::map>> value_map; - std::vector key_seq; - uint32_t total_block_size = 0; bool remove_duplicated_record = false; if (request->has_enable_remove_duplicated_record()) { remove_duplicated_record = request->enable_remove_duplicated_record(); } uint32_t scount = 0; + butil::IOBuf buf; for (; it->Valid(); it->Next()) { if (request->limit() > 0 && scount > request->limit() - 1) { DEBUGLOG("reache the limit %u ", request->limit()); @@ -1469,16 +1383,9 @@ void TabletImpl::Traverse(RpcController* controller, const ::openmldb::api::Trav continue; } } - auto map_it = value_map.find(last_pk); - if (map_it == value_map.end()) { - auto pair = value_map.emplace(last_pk, std::vector>()); - map_it = pair.first; - map_it->second.reserve(request->limit()); - key_seq.emplace_back(map_it->first); - } openmldb::base::Slice value = it->GetValue(); - map_it->second.emplace_back(it->GetKey(), value); - total_block_size += last_pk.length() + value.size(); + DLOG(INFO) << "encode pk " << it->GetPK() << " ts " << it->GetKey() << " size " << value.size(); + ::openmldb::codec::EncodeFull(it->GetPK(), it->GetKey(), value.data(), value.size(), &buf); scount++; if (FLAGS_max_traverse_cnt > 0 && it->GetCount() >= FLAGS_max_traverse_cnt) { DEBUGLOG("traverse cnt %lu max %lu, key %s ts %lu", it->GetCount(), FLAGS_max_traverse_cnt, last_pk.c_str(), @@ -1498,26 +1405,7 @@ void TabletImpl::Traverse(RpcController* controller, const ::openmldb::api::Trav } else if (scount < request->limit()) { is_finish = true; } - uint32_t total_size = scount * (8 + 4 + 4) + total_block_size; - std::string* pairs = response->mutable_pairs(); - if (scount <= 0) { - pairs->resize(0); - } else { - pairs->resize(total_size); - } - char* rbuffer = reinterpret_cast(&((*pairs)[0])); - uint32_t offset = 0; - for (const auto& key : key_seq) { - auto iter = value_map.find(key); - if (iter == value_map.end()) { - continue; - } - for (const auto& pair : iter->second) { - DLOG(INFO) << "encode pk " << key << " ts " << pair.first << " size " << pair.second.size(); - ::openmldb::codec::EncodeFull(key, pair.first, pair.second.data(), pair.second.size(), rbuffer, offset); - offset += (4 + 4 + 8 + key.length() + pair.second.size()); - } - } + buf.copy_to(response->mutable_pairs()); delete it; DLOG(INFO) << "tid " << tid << " pid " << pid << " traverse count " << scount << " last_pk " << last_pk << " last_time " << last_time << " ts_pos " << ts_pos; @@ -1602,34 +1490,80 @@ void TabletImpl::Delete(RpcController* controller, const ::openmldb::api::Delete PDLOG(WARNING, "invalid args. tid %u, pid %u", tid, pid); return; } - if (table->Delete(entry)) { - response->set_code(::openmldb::base::ReturnCode::kOk); - response->set_msg("ok"); - DEBUGLOG("delete ok. tid %u, pid %u, key %s", tid, pid, request->key().c_str()); - } else { - response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); - response->set_msg("delete failed"); - return; - } - - // delete the entries from pre-aggr table auto aggrs = GetAggregators(tid, pid); - if (aggrs) { - for (const auto& aggr : *aggrs) { - if (aggr->GetIndexPos() != idx) { - continue; + if (!aggrs) { + if (table->Delete(entry)) { + DEBUGLOG("delete ok. tid %u, pid %u, key %s", tid, pid, request->key().c_str()); + } else { + response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); + response->set_msg("delete failed"); + return; + } + } else { + auto get_aggregator = [this](std::shared_ptr aggrs, uint32_t idx) -> std::shared_ptr { + if (aggrs) { + for (const auto& aggr : *aggrs) { + if (aggr->GetIndexPos() == idx) { + return aggr; + } + } } - auto ok = aggr->Delete(request->key()); - if (!ok) { - PDLOG(WARNING, - "delete from aggr failed. base table: tid[%u] pid[%u] index[%u] key[%s]. aggr table: tid[%u]", - tid, pid, idx, request->key().c_str(), aggr->GetAggrTid()); - response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); - response->set_msg("delete from associated pre-aggr table failed"); - return; + return {}; + }; + std::optional start_ts = entry.has_ts() ? std::optional{entry.ts()} : std::nullopt; + std::optional end_ts = entry.has_end_ts() ? std::optional{entry.end_ts()} : std::nullopt; + if (entry.dimensions_size() > 0) { + for (const auto& dimension : entry.dimensions()) { + if (!table->Delete(dimension.idx(), dimension.key(), start_ts, end_ts)) { + response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); + response->set_msg("delete failed"); + return; + } + auto aggr = get_aggregator(aggrs, dimension.idx()); + if (aggr) { + if (!aggr->Delete(dimension.key(), start_ts, end_ts)) { + PDLOG(WARNING, "delete from aggr failed. base table: tid[%u] pid[%u] index[%u] key[%s]. " + "aggr table: tid[%u]", + tid, pid, idx, dimension.key().c_str(), aggr->GetAggrTid()); + response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); + response->set_msg("delete from associated pre-aggr table failed"); + return; + } + } + DEBUGLOG("delete ok. tid %u, pid %u, key %s", tid, pid, dimension.key().c_str()); + } + } else { + for (const auto& index_def : table->GetAllIndex()) { + if (!index_def || !index_def->IsReady()) { + continue; + } + uint32_t idx = index_def->GetId(); + std::unique_ptr iter(table->NewTraverseIterator(idx)); + iter->SeekToFirst(); + while (iter->Valid()) { + auto pk = iter->GetPK(); + iter->NextPK(); + if (!table->Delete(idx, pk, start_ts, end_ts)) { + response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); + response->set_msg("delete failed"); + return; + } + auto aggr = get_aggregator(aggrs, idx); + if (aggr) { + if (!aggr->Delete(pk, start_ts, end_ts)) { + PDLOG(WARNING, "delete from aggr failed. base table: tid[%u] pid[%u] index[%u] key[%s]. " + "aggr table: tid[%u]", tid, pid, idx, pk.c_str(), aggr->GetAggrTid()); + response->set_code(::openmldb::base::ReturnCode::kDeleteFailed); + response->set_msg("delete from associated pre-aggr table failed"); + return; + } + } + } } } } + response->set_code(::openmldb::base::ReturnCode::kOk); + response->set_msg("ok"); replicator->AppendEntry(entry); if (FLAGS_binlog_notify_on_put) { @@ -1643,15 +1577,15 @@ void TabletImpl::Query(RpcController* ctrl, const openmldb::api::QueryRequest* r brpc::ClosureGuard done_guard(done); brpc::Controller* cntl = static_cast(ctrl); butil::IOBuf& buf = cntl->response_attachment(); - ProcessQuery(ctrl, request, response, &buf); + ProcessQuery(true, ctrl, request, response, &buf); } -void TabletImpl::ProcessQuery(RpcController* ctrl, const openmldb::api::QueryRequest* request, +void TabletImpl::ProcessQuery(bool is_sub, RpcController* ctrl, const openmldb::api::QueryRequest* request, ::openmldb::api::QueryResponse* response, butil::IOBuf* buf) { auto start = absl::Now(); - absl::Cleanup deploy_collect_task = [this, request, start]() { + absl::Cleanup deploy_collect_task = [this, is_sub, request, start]() { if (this->IsCollectDeployStatsEnabled()) { - if (request->is_procedure() && request->has_db() && request->has_sp_name()) { + if (!is_sub && request->is_procedure() && request->has_db() && request->has_sp_name()) { this->TryCollectDeployStats(request->db(), request->sp_name(), start); } } @@ -1773,7 +1707,8 @@ void TabletImpl::SubQuery(RpcController* ctrl, const openmldb::api::QueryRequest brpc::ClosureGuard done_guard(done); brpc::Controller* cntl = static_cast(ctrl); butil::IOBuf& buf = cntl->response_attachment(); - ProcessQuery(ctrl, request, response, &buf); + // subquery don't need to collect deploy stats + ProcessQuery(true, ctrl, request, response, &buf); } void TabletImpl::SQLBatchRequestQuery(RpcController* ctrl, const openmldb::api::SQLBatchRequestQueryRequest* request, @@ -1782,15 +1717,15 @@ void TabletImpl::SQLBatchRequestQuery(RpcController* ctrl, const openmldb::api:: brpc::ClosureGuard done_guard(done); brpc::Controller* cntl = static_cast(ctrl); butil::IOBuf& buf = cntl->response_attachment(); - return ProcessBatchRequestQuery(ctrl, request, response, buf); + return ProcessBatchRequestQuery(false, ctrl, request, response, buf); } -void TabletImpl::ProcessBatchRequestQuery(RpcController* ctrl, +void TabletImpl::ProcessBatchRequestQuery(bool is_sub, RpcController* ctrl, const openmldb::api::SQLBatchRequestQueryRequest* request, openmldb::api::SQLBatchRequestQueryResponse* response, butil::IOBuf& buf) { absl::Time start = absl::Now(); - absl::Cleanup deploy_collect_task = [this, request, start]() { + absl::Cleanup deploy_collect_task = [this, is_sub, request, start]() { if (this->IsCollectDeployStatsEnabled()) { - if (request->is_procedure() && request->has_db() && request->has_sp_name()) { + if (!is_sub && request->is_procedure() && request->has_db() && request->has_sp_name()) { this->TryCollectDeployStats(request->db(), request->sp_name(), start); } } @@ -1962,7 +1897,7 @@ void TabletImpl::SubBatchRequestQuery(RpcController* ctrl, const openmldb::api:: brpc::ClosureGuard done_guard(done); brpc::Controller* cntl = static_cast(ctrl); butil::IOBuf& buf = cntl->response_attachment(); - return ProcessBatchRequestQuery(ctrl, request, response, buf); + return ProcessBatchRequestQuery(true, ctrl, request, response, buf); } void TabletImpl::ChangeRole(RpcController* controller, const ::openmldb::api::ChangeRoleRequest* request, @@ -2481,7 +2416,7 @@ void TabletImpl::SetExpire(RpcController* controller, const ::openmldb::api::Set } void TabletImpl::MakeSnapshotInternal(uint32_t tid, uint32_t pid, uint64_t end_offset, - std::shared_ptr<::openmldb::api::TaskInfo> task) { + std::shared_ptr<::openmldb::api::TaskInfo> task, bool is_force) { PDLOG(INFO, "MakeSnapshotInternal begin, tid[%u] pid[%u]", tid, pid); std::shared_ptr
table; std::shared_ptr snapshot; @@ -2524,7 +2459,7 @@ void TabletImpl::MakeSnapshotInternal(uint32_t tid, uint32_t pid, uint64_t end_o uint64_t cur_offset = replicator->GetOffset(); uint64_t snapshot_offset = snapshot->GetOffset(); int ret = 0; - if (cur_offset < snapshot_offset + FLAGS_make_snapshot_threshold_offset && end_offset == 0) { + if (!is_force && cur_offset < snapshot_offset + FLAGS_make_snapshot_threshold_offset && end_offset == 0) { PDLOG(INFO, "offset can't reach the threshold. tid[%u] pid[%u] " "cur_offset[%lu], snapshot_offset[%lu] end_offset[%lu]", @@ -2597,7 +2532,7 @@ void TabletImpl::MakeSnapshot(RpcController* controller, const ::openmldb::api:: break; } } - snapshot_pool_.AddTask(boost::bind(&TabletImpl::MakeSnapshotInternal, this, tid, pid, offset, task_ptr)); + snapshot_pool_.AddTask(boost::bind(&TabletImpl::MakeSnapshotInternal, this, tid, pid, offset, task_ptr, false)); response->set_code(::openmldb::base::ReturnCode::kOk); response->set_msg("ok"); return; @@ -2631,7 +2566,7 @@ void TabletImpl::SchedMakeSnapshot() { } for (auto iter = table_set.begin(); iter != table_set.end(); ++iter) { PDLOG(INFO, "start make snapshot tid[%u] pid[%u]", iter->first, iter->second); - MakeSnapshotInternal(iter->first, iter->second, 0, std::shared_ptr<::openmldb::api::TaskInfo>()); + MakeSnapshotInternal(iter->first, iter->second, 0, std::shared_ptr<::openmldb::api::TaskInfo>(), false); } // delay task one hour later avoid execute more than one time snapshot_pool_.DelayTask(FLAGS_make_snapshot_check_interval + 60 * 60 * 1000, @@ -3033,10 +2968,7 @@ void TabletImpl::LoadTable(RpcController* controller, const ::openmldb::api::Loa std::string db_path = GetDBPath(root_path, tid, pid); if (!::openmldb::base::IsExists(db_path)) { - PDLOG(WARNING, "table db path does not exist. tid %u, pid %u, path %s", tid, pid, db_path.c_str()); - response->set_code(::openmldb::base::ReturnCode::kTableDbPathIsNotExist); - response->set_msg("table db path does not exist"); - break; + PDLOG(WARNING, "table db path does not exist, but still load. tid %u, pid %u, path %s", tid, pid, db_path.c_str()); } std::shared_ptr
table = GetTable(tid, pid); @@ -3458,6 +3390,110 @@ void TabletImpl::CreateTable(RpcController* controller, const ::openmldb::api::C response->set_msg("ok"); } +void TabletImpl::TruncateTable(RpcController* controller, const ::openmldb::api::TruncateTableRequest* request, + ::openmldb::api::TruncateTableResponse* response, Closure* done) { + brpc::ClosureGuard done_guard(done); + uint32_t tid = request->tid(); + uint32_t pid = request->pid(); + if (auto status = TruncateTableInternal(tid, pid); !status.OK()) { + base::SetResponseStatus(status, response); + return; + } + auto aggrs = GetAggregators(tid, pid); + if (aggrs) { + for (const auto& aggr : *aggrs) { + auto agg_table = aggr->GetAggTable(); + if (!agg_table) { + PDLOG(WARNING, "aggrate table does not exist. tid[%u] pid[%u] index pos[%u]", + tid, pid, aggr->GetIndexPos()); + response->set_code(::openmldb::base::ReturnCode::kTableIsNotExist); + response->set_msg("aggrate table does not exist"); + return; + } + uint32_t agg_tid = agg_table->GetId(); + uint32_t agg_pid = agg_table->GetPid(); + if (auto status = TruncateTableInternal(agg_tid, agg_pid); !status.OK()) { + PDLOG(WARNING, "truncate aggrate table failed. tid[%u] pid[%u] index pos[%u]", + agg_tid, agg_pid, aggr->GetIndexPos()); + base::SetResponseStatus(status, response); + return; + } + PDLOG(INFO, "truncate aggrate table success. tid[%u] pid[%u] index pos[%u]", + agg_tid, agg_pid, aggr->GetIndexPos()); + } + } + response->set_code(::openmldb::base::ReturnCode::kOk); + response->set_msg("ok"); +} + +base::Status TabletImpl::TruncateTableInternal(uint32_t tid, uint32_t pid) { + std::shared_ptr
table; + std::shared_ptr snapshot; + std::shared_ptr replicator; + { + std::lock_guard spin_lock(spin_mutex_); + table = GetTableUnLock(tid, pid); + if (!table) { + DEBUGLOG("table does not exist. tid %u pid %u", tid, pid); + return {::openmldb::base::ReturnCode::kTableIsNotExist, "table not found"}; + } + snapshot = GetSnapshotUnLock(tid, pid); + if (!snapshot) { + PDLOG(WARNING, "snapshot does not exist. tid[%u] pid[%u]", tid, pid); + return {::openmldb::base::ReturnCode::kSnapshotIsNotExist, "snapshot not found"}; + } + replicator = GetReplicatorUnLock(tid, pid); + if (!replicator) { + PDLOG(WARNING, "replicator does not exist. tid[%u] pid[%u]", tid, pid); + return {::openmldb::base::ReturnCode::kReplicatorIsNotExist, "replicator not found"}; + } + } + if (replicator->GetOffset() == 0) { + PDLOG(INFO, "table is empty, truncate success. tid[%u] pid[%u]", tid, pid); + return {}; + } + if (table->GetTableStat() == ::openmldb::storage::kMakingSnapshot) { + PDLOG(WARNING, "making snapshot task is running now. tid[%u] pid[%u]", tid, pid); + return {::openmldb::base::ReturnCode::kTableStatusIsKmakingsnapshot, "table status is kMakingSnapshot"}; + } else if (table->GetTableStat() == ::openmldb::storage::kLoading) { + PDLOG(WARNING, "table is loading now. tid[%u] pid[%u]", tid, pid); + return {::openmldb::base::ReturnCode::kTableIsLoading, "table is loading data"}; + } + if (table->GetStorageMode() == openmldb::common::kMemory) { + auto table_meta = table->GetTableMeta(); + std::shared_ptr
new_table; + new_table = std::make_shared(*table_meta); + if (!new_table->Init()) { + PDLOG(WARNING, "fail to init table. tid %u, pid %u", tid, pid); + return {::openmldb::base::ReturnCode::kTableMetaIsIllegal, "fail to init table"}; + } + new_table->SetTableStat(::openmldb::storage::kNormal); + { + std::lock_guard spin_lock(spin_mutex_); + tables_[tid].insert_or_assign(pid, new_table); + } + auto mem_snapshot = std::dynamic_pointer_cast(snapshot); + mem_snapshot->Truncate(replicator->GetOffset(), replicator->GetLeaderTerm()); + if (table_meta->mode() == ::openmldb::api::TableMode::kTableLeader) { + if (catalog_->AddTable(*table_meta, new_table)) { + LOG(INFO) << "add table " << table_meta->name() << " to catalog with db " << table_meta->db(); + } else { + LOG(WARNING) << "fail to add table " << table_meta->name() + << " to catalog with db " << table_meta->db(); + return {::openmldb::base::ReturnCode::kCatalogUpdateFailed, "fail to update catalog"}; + } + } + } else { + auto disk_table = std::dynamic_pointer_cast(table); + if (auto status = disk_table->Truncate(); !status.OK()) { + return {::openmldb::base::ReturnCode::kTruncateTableFailed, status.GetMsg()}; + } + snapshot_pool_.AddTask(boost::bind(&TabletImpl::MakeSnapshotInternal, this, tid, pid, 0, nullptr, true)); + } + PDLOG(INFO, "truncate table success. tid[%u] pid[%u]", tid, pid); + return {}; +} + void TabletImpl::ExecuteGc(RpcController* controller, const ::openmldb::api::ExecuteGcRequest* request, ::openmldb::api::GeneralResponse* response, Closure* done) { brpc::ClosureGuard done_guard(done); @@ -3867,13 +3903,11 @@ int TabletImpl::CreateTableInternal(const ::openmldb::api::TableMeta* table_meta return -1; } std::string table_db_path = GetDBPath(db_root_path, tid, pid); - Table* table_ptr; if (table_meta->storage_mode() == openmldb::common::kMemory) { - table_ptr = new MemTable(*table_meta); + table = std::make_shared(*table_meta); } else { - table_ptr = new DiskTable(*table_meta, table_db_path); + table = std::make_shared(*table_meta, table_db_path); } - table.reset(table_ptr); if (!table->Init()) { PDLOG(WARNING, "fail to init table. tid %u, pid %u", table_meta->tid(), table_meta->pid()); @@ -4286,6 +4320,12 @@ void TabletImpl::UpdateGlobalVarTable() { it->Next(); } std::atomic_store_explicit(&global_variables_, new_global_var, std::memory_order_relaxed); + + // no DEPLOY_STATS when init, so we can get the first DEPLOY_STATS value here, and all changes will be handled in + // and we assume that global vars change is low frequency, so we reset here instead of in the repeated task + if (!IsCollectDeployStatsEnabled()) { + deploy_collector_->Reset(); + } return; } @@ -5311,7 +5351,7 @@ void TabletImpl::DropProcedure(RpcController* controller, const ::openmldb::api: if (is_deployment_procedure) { auto collector_key = absl::StrCat(db_name, ".", sp_name); - auto s = deploy_collector_->DeleteDeploy(collector_key); + auto s = deploy_collector_->DeleteDeploy(db_name, sp_name); if (!s.ok()) { LOG(ERROR) << "[ERROR] delete deploy collector: " << s; } else { @@ -5464,31 +5504,12 @@ bool TabletImpl::IsCollectDeployStatsEnabled() const { } // try collect the cost time for a deployment procedure into collector -// if the procedure found in collector, it is colelcted directly -// if not, the function will try find the procedure info from procedure cache, and if turns out is a deployment -// procedure, retry collecting by firstly adding the missing deployment procedure into collector +// if failed, log inside, no extra handle void TabletImpl::TryCollectDeployStats(const std::string& db, const std::string& name, absl::Time start_time) { absl::Time now = absl::Now(); absl::Duration time = now - start_time; - const std::string deploy_name = absl::StrCat(db, ".", name); - auto s = deploy_collector_->Collect(deploy_name, time); - if (absl::IsNotFound(s)) { - // deploy collector is regarded as non-update-to-date cache for sp_info (sp_cache_ should be up-to-date) - // so when Not Found error happens, retry once again by AddDeploy first, with the help of sp_cache_ - auto sp_info = sp_cache_->FindSpProcedureInfo(db, name); - if (sp_info.ok() && sp_info.value()->GetType() == hybridse::sdk::kReqDeployment) { - s = deploy_collector_->AddDeploy(deploy_name); - if (!s.ok()) { - LOG(ERROR) << "[ERROR] add deploy collector: " << s; - return; - } - s = deploy_collector_->Collect(deploy_name, time); - } - } - if (!s.ok()) { - LOG(ERROR) << "[ERROR] collect deploy stat: " << s; - } - DLOG(INFO) << "collected " << deploy_name << " for " << time; + auto st = deploy_collector_->Collect(db, name, time); + DLOG(INFO) << "collect " << db << "." << name << " latency " << time; } void TabletImpl::BulkLoad(RpcController* controller, const ::openmldb::api::BulkLoadRequest* request, @@ -5715,9 +5736,14 @@ bool TabletImpl::CreateAggregatorInternal(const ::openmldb::api::CreateAggregato return false; } auto aggr_replicator = GetReplicator(request->aggr_table_tid(), request->aggr_table_pid()); - auto aggregator = ::openmldb::storage::CreateAggregator( - base_meta, *aggr_table->GetTableMeta(), aggr_table, aggr_replicator, request->index_pos(), request->aggr_col(), - request->aggr_func(), request->order_by_col(), request->bucket_size(), request->filter_col()); + auto base_table = GetTable(base_meta.tid(), base_meta.pid()); + if (!base_table) { + PDLOG(WARNING, "base table does not exist. tid %u, pid %u", base_meta.tid(), base_meta.pid()); + return false; + } + auto aggregator = ::openmldb::storage::CreateAggregator(base_meta, base_table, + *aggr_table->GetTableMeta(), aggr_table, aggr_replicator, request->index_pos(), request->aggr_col(), + request->aggr_func(), request->order_by_col(), request->bucket_size(), request->filter_col()); if (!aggregator) { msg.assign("create aggregator failed"); return false; @@ -5745,14 +5771,7 @@ void TabletImpl::GetAndFlushDeployStats(::google::protobuf::RpcController* contr ::google::protobuf::Closure* done) { brpc::ClosureGuard done_guard(done); - auto rs = deploy_collector_->Flush(); - for (auto& r : rs) { - auto new_row = response->add_rows(); - new_row->set_deploy_name(r.deploy_name_); - new_row->set_time(r.GetTimeAsStr(statistics::TimeUnit::MICRO_SECOND)); - new_row->set_count(r.count_); - new_row->set_total(r.GetTotalAsStr(statistics::TimeUnit::MICRO_SECOND)); - } + // TODO(hw): delete rpc? response->set_code(ReturnCode::kOk); } diff --git a/src/tablet/tablet_impl.h b/src/tablet/tablet_impl.h index d48f192ae26..c24a253c9e9 100644 --- a/src/tablet/tablet_impl.h +++ b/src/tablet/tablet_impl.h @@ -33,9 +33,9 @@ #include "nameserver/system_table.h" #include "proto/tablet.pb.h" #include "replica/log_replicator.h" -#include "storage/aggregator.h" #include "sdk/sql_cluster_router.h" -#include "statistics/query_response_time/deploy_query_response_time.h" +#include "statistics/query_response_time/deployment_metric_collector.h" +#include "storage/aggregator.h" #include "storage/mem_table.h" #include "storage/mem_table_snapshot.h" #include "tablet/bulk_load_mgr.h" @@ -109,6 +109,9 @@ class TabletImpl : public ::openmldb::api::TabletServer { void DropTable(RpcController* controller, const ::openmldb::api::DropTableRequest* request, ::openmldb::api::DropTableResponse* response, Closure* done); + void TruncateTable(RpcController* controller, const ::openmldb::api::TruncateTableRequest* request, + ::openmldb::api::TruncateTableResponse* response, Closure* done); + void Refresh(RpcController* controller, const ::openmldb::api::RefreshRequest* request, ::openmldb::api::GeneralResponse* response, Closure* done); @@ -216,10 +219,10 @@ class TabletImpl : public ::openmldb::api::TabletServer { openmldb::api::QueryResponse* response, Closure* done); void CreateFunction(RpcController* controller, const openmldb::api::CreateFunctionRequest* request, - openmldb::api::CreateFunctionResponse* response, Closure* done); + openmldb::api::CreateFunctionResponse* response, Closure* done); void DropFunction(RpcController* controller, const openmldb::api::DropFunctionRequest* request, - openmldb::api::DropFunctionResponse* response, Closure* done); + openmldb::api::DropFunctionResponse* response, Closure* done); void SubQuery(RpcController* controller, const openmldb::api::QueryRequest* request, openmldb::api::QueryResponse* response, Closure* done); @@ -239,14 +242,9 @@ class TabletImpl : public ::openmldb::api::TabletServer { const std::map>& vers_schema, CombineIterator* combine_it, std::string* value, uint64_t* ts); - // scan specified ttl type index - int32_t ScanIndex(const ::openmldb::api::ScanRequest* request, const ::openmldb::api::TableMeta& meta, - const std::map>& vers_schema, CombineIterator* combine_it, - std::string* pairs, uint32_t* count, bool* is_finish); - int32_t ScanIndex(const ::openmldb::api::ScanRequest* request, const ::openmldb::api::TableMeta& meta, - const std::map>& vers_schema, CombineIterator* combine_it, - butil::IOBuf* buf, uint32_t* count, bool* is_finish); + const std::map>& vers_schema, bool use_attachment, + CombineIterator* combine_it, butil::IOBuf* buf, uint32_t* count, bool* is_finish); int32_t CountIndex(uint64_t expire_time, uint64_t expire_cnt, ::openmldb::storage::TTLType ttl_type, ::openmldb::storage::TableIterator* it, const ::openmldb::api::CountRequest* request, @@ -281,9 +279,7 @@ class TabletImpl : public ::openmldb::api::TabletServer { public: explicit UpdateAggrClosure(const std::function& callback) : callback_(callback) {} - void Run() override { - callback_(); - } + void Run() override { callback_(); } private: std::function callback_; @@ -313,30 +309,29 @@ class TabletImpl : public ::openmldb::api::TabletServer { int CreateTableInternal(const ::openmldb::api::TableMeta* table_meta, std::string& msg); // NOLINT void MakeSnapshotInternal(uint32_t tid, uint32_t pid, uint64_t end_offset, - std::shared_ptr<::openmldb::api::TaskInfo> task); + std::shared_ptr<::openmldb::api::TaskInfo> task, bool is_force); void SendSnapshotInternal(const std::string& endpoint, uint32_t tid, uint32_t pid, uint32_t remote_tid, std::shared_ptr<::openmldb::api::TaskInfo> task); void DumpIndexDataInternal(std::shared_ptr<::openmldb::storage::Table> table, std::shared_ptr<::openmldb::storage::MemTableSnapshot> memtable_snapshot, - uint32_t partition_num, - const std::vector<::openmldb::common::ColumnKey>& column_keys, + uint32_t partition_num, const std::vector<::openmldb::common::ColumnKey>& column_keys, uint64_t offset, std::shared_ptr<::openmldb::api::TaskInfo> task); void SendIndexDataInternal(std::shared_ptr<::openmldb::storage::Table> table, const std::map& pid_endpoint_map, std::shared_ptr<::openmldb::api::TaskInfo> task); - void LoadIndexDataInternal(uint32_t tid, uint32_t pid, uint32_t cur_pid, - uint32_t partition_num, uint64_t last_time, - std::shared_ptr<::openmldb::api::TaskInfo> task); + void LoadIndexDataInternal(uint32_t tid, uint32_t pid, uint32_t cur_pid, uint32_t partition_num, uint64_t last_time, + std::shared_ptr<::openmldb::api::TaskInfo> task); + + base::Status TruncateTableInternal(uint32_t tid, uint32_t pid); void ExtractIndexDataInternal(std::shared_ptr<::openmldb::storage::Table> table, - std::shared_ptr<::openmldb::storage::MemTableSnapshot> memtable_snapshot, - const std::vector<::openmldb::common::ColumnKey>& column_key, - uint32_t partition_num, uint64_t offset, bool contain_dump, - std::shared_ptr<::openmldb::api::TaskInfo> task); + std::shared_ptr<::openmldb::storage::MemTableSnapshot> memtable_snapshot, + const std::vector<::openmldb::common::ColumnKey>& column_key, uint32_t partition_num, + uint64_t offset, bool contain_dump, std::shared_ptr<::openmldb::api::TaskInfo> task); void SchedMakeSnapshot(); @@ -360,7 +355,7 @@ class TabletImpl : public ::openmldb::api::TabletServer { int LoadTableInternal(uint32_t tid, uint32_t pid, std::shared_ptr<::openmldb::api::TaskInfo> task_ptr); int LoadDiskTableInternal(uint32_t tid, uint32_t pid, const ::openmldb::api::TableMeta& table_meta, - std::shared_ptr<::openmldb::api::TaskInfo> task_ptr); + std::shared_ptr<::openmldb::api::TaskInfo> task_ptr); int WriteTableMeta(const std::string& path, const ::openmldb::api::TableMeta* table_meta); int UpdateTableMeta(const std::string& path, ::openmldb::api::TableMeta* table_meta, bool for_add_column); @@ -373,8 +368,7 @@ class TabletImpl : public ::openmldb::api::TabletServer { void SetTaskStatus(std::shared_ptr<::openmldb::api::TaskInfo>& task_ptr, // NOLINT ::openmldb::api::TaskStatus status); - int GetTaskStatus(const std::shared_ptr<::openmldb::api::TaskInfo>& task_ptr, - ::openmldb::api::TaskStatus* status); + int GetTaskStatus(const std::shared_ptr<::openmldb::api::TaskInfo>& task_ptr, ::openmldb::api::TaskStatus* status); bool IsExistTaskUnLock(const ::openmldb::api::TaskInfo& task); @@ -400,8 +394,7 @@ class TabletImpl : public ::openmldb::api::TabletServer { bool GetTableRootSize(uint32_t tid, uint32_t pid, const ::openmldb::common::StorageMode& mode, uint64_t& size); // NOLINT - int32_t GetSnapshotOffset(uint32_t tid, uint32_t pid, - ::openmldb::common::StorageMode storageMode, + int32_t GetSnapshotOffset(uint32_t tid, uint32_t pid, ::openmldb::common::StorageMode storageMode, std::string& msg, // NOLINT uint64_t& term, uint64_t& offset); // NOLINT @@ -411,9 +404,10 @@ class TabletImpl : public ::openmldb::api::TabletServer { bool GetRealEp(uint64_t tid, uint64_t pid, std::map* real_ep_map); - void ProcessQuery(RpcController* controller, const openmldb::api::QueryRequest* request, + void ProcessQuery(bool is_sub, RpcController* controller, const openmldb::api::QueryRequest* request, ::openmldb::api::QueryResponse* response, butil::IOBuf* buf); - void ProcessBatchRequestQuery(RpcController* controller, const openmldb::api::SQLBatchRequestQueryRequest* request, + void ProcessBatchRequestQuery(bool is_sub, RpcController* controller, + const openmldb::api::SQLBatchRequestQueryRequest* request, openmldb::api::SQLBatchRequestQueryResponse* response, butil::IOBuf& buf); // NOLINT @@ -421,11 +415,9 @@ class TabletImpl : public ::openmldb::api::TabletServer { const ::openmldb::storage::Dimensions& dimensions, uint64_t log_offset); bool CreateAggregatorInternal(const ::openmldb::api::CreateAggregatorRequest* request, - std::string& msg); //NOLINT + std::string& msg); // NOLINT - inline bool IsClusterMode() const { - return startup_mode_ == ::openmldb::type::StartupMode::kCluster; - } + inline bool IsClusterMode() const { return startup_mode_ == ::openmldb::type::StartupMode::kCluster; } std::string GetDBPath(const std::string& root_path, uint32_t tid, uint32_t pid); @@ -460,10 +452,8 @@ class TabletImpl : public ::openmldb::api::TabletServer { std::set sync_snapshot_set_; std::map> file_receiver_map_; BulkLoadMgr bulk_load_mgr_; - std::map<::openmldb::common::StorageMode, std::vector> - mode_root_paths_; - std::map<::openmldb::common::StorageMode, std::vector> - mode_recycle_root_paths_; + std::map<::openmldb::common::StorageMode, std::vector> mode_root_paths_; + std::map<::openmldb::common::StorageMode, std::vector> mode_recycle_root_paths_; std::atomic follower_; std::shared_ptr> real_ep_map_; // thread safe @@ -482,7 +472,7 @@ class TabletImpl : public ::openmldb::api::TabletServer { std::shared_ptr> global_variables_; - std::unique_ptr deploy_collector_; + std::unique_ptr deploy_collector_; std::atomic memory_used_ = 0; }; diff --git a/src/tablet/tablet_impl_keep_alive_test.cc b/src/tablet/tablet_impl_keep_alive_test.cc index eafd3338b4d..7339ca80607 100644 --- a/src/tablet/tablet_impl_keep_alive_test.cc +++ b/src/tablet/tablet_impl_keep_alive_test.cc @@ -66,7 +66,7 @@ TEST_F(TabletImplTest, KeepAlive) { FLAGS_endpoint = "127.0.0.1:9527"; FLAGS_zk_cluster = "127.0.0.1:6181"; FLAGS_zk_root_path = "/rtidb2"; - ZkClient zk_client(FLAGS_zk_cluster, "", 1000, "test1", FLAGS_zk_root_path); + ZkClient zk_client(FLAGS_zk_cluster, "", 1000, "test1", FLAGS_zk_root_path, "", ""); bool ok = zk_client.Init(); ASSERT_TRUE(ok); ok = zk_client.Mkdir("/rtidb2/nodes"); diff --git a/src/tablet/tablet_impl_test.cc b/src/tablet/tablet_impl_test.cc index da5cc626bf0..1a2de9e66d8 100644 --- a/src/tablet/tablet_impl_test.cc +++ b/src/tablet/tablet_impl_test.cc @@ -128,17 +128,12 @@ bool RollWLogFile(::openmldb::storage::WriteHandle** wh, ::openmldb::storage::Lo return true; } -void PrepareLatestTableData(TabletImpl& tablet, int32_t tid, int32_t pid, bool compress = false) { // NOLINT +void PrepareLatestTableData(TabletImpl& tablet, int32_t tid, int32_t pid) { // NOLINT for (int32_t i = 0; i < 100; i++) { ::openmldb::api::PutRequest prequest; ::openmldb::test::SetDimension(0, std::to_string(i % 10), prequest.add_dimensions()); prequest.set_time(i + 1); std::string value = ::openmldb::test::EncodeKV(std::to_string(i % 10), std::to_string(i)); - if (compress) { - std::string compressed; - ::snappy::Compress(value.c_str(), value.length(), &compressed); - value.swap(compressed); - } prequest.set_value(value); prequest.set_tid(tid); prequest.set_pid(pid); @@ -153,11 +148,6 @@ void PrepareLatestTableData(TabletImpl& tablet, int32_t tid, int32_t pid, bool c ::openmldb::test::SetDimension(0, "10", prequest.add_dimensions()); prequest.set_time(i % 10 + 1); std::string value = ::openmldb::test::EncodeKV("10", std::to_string(i)); - if (compress) { - std::string compressed; - ::snappy::Compress(value.c_str(), value.length(), &compressed); - value.swap(compressed); - } prequest.set_value(value); prequest.set_tid(tid); prequest.set_pid(pid); @@ -249,6 +239,23 @@ int PutKVData(uint32_t tid, uint32_t pid, const std::string& key, const std::str return presponse.code(); } +std::pair ScanFromTablet(uint32_t tid, uint32_t pid, const std::string& key, const std::string& idx_name, + uint64_t st, uint64_t et, TabletImpl* tablet) { + ::openmldb::api::ScanRequest sr; + sr.set_tid(tid); + sr.set_pid(pid); + sr.set_pk(key); + if (!idx_name.empty()) { + sr.set_idx_name(idx_name); + } + sr.set_st(st); + sr.set_et(et); + ::openmldb::api::ScanResponse srp; + MockClosure closure; + tablet->Scan(NULL, &sr, &srp, &closure); + return std::make_pair(srp.code(), srp.count()); +} + int GetTTL(TabletImpl& tablet, uint32_t tid, uint32_t pid, const std::string& index_name, // NOLINT ::openmldb::common::TTLSt* ttl) { ::openmldb::api::GetTableSchemaRequest request; @@ -1416,6 +1423,42 @@ TEST_P(TabletImplTest, ScanWithLatestN) { ASSERT_FALSE(kv_it.Valid()); } +TEST_P(TabletImplTest, Truncate) { + ::openmldb::common::StorageMode storage_mode = GetParam(); + TabletImpl tablet; + uint32_t id = counter++; + tablet.Init(""); + ASSERT_EQ(0, CreateDefaultTable("db0", "t0", id, 1, 0, 0, kAbsoluteTime, storage_mode, &tablet)); + MockClosure closure; + for (int ts = 100; ts < 200; ts++) { + ::openmldb::api::PutRequest prequest; + PackDefaultDimension("test1", &prequest); + prequest.set_time(ts); + prequest.set_value(::openmldb::test::EncodeKV("test1", "test" + std::to_string(ts))); + prequest.set_tid(id); + prequest.set_pid(1); + ::openmldb::api::PutResponse presponse; + tablet.Put(NULL, &prequest, &presponse, &closure); + ASSERT_EQ(0, presponse.code()); + } + ::openmldb::api::TraverseRequest sr; + sr.set_tid(id); + sr.set_pid(1); + sr.set_limit(1000); + auto srp = std::make_shared<::openmldb::api::TraverseResponse>(); + tablet.Traverse(NULL, &sr, srp.get(), &closure); + ASSERT_EQ(0, srp->code()); + ASSERT_EQ(100, (signed)srp->count()); + ::openmldb::api::TruncateTableRequest tr; + tr.set_tid(id); + tr.set_pid(1); + auto trp = std::make_shared<::openmldb::api::TruncateTableResponse>(); + tablet.TruncateTable(NULL, &tr, trp.get(), &closure); + ASSERT_EQ(0, trp->code()); + tablet.Traverse(NULL, &sr, srp.get(), &closure); + ASSERT_EQ(0, srp->code()); + ASSERT_EQ(0, (signed)srp->count()); +} TEST_P(TabletImplTest, Traverse) { ::openmldb::common::StorageMode storage_mode = GetParam(); @@ -5314,7 +5357,7 @@ TEST_P(TabletImplTest, PutCompress) { MockClosure closure; tablet.CreateTable(NULL, &request, &response, &closure); ASSERT_EQ(0, response.code()); - PrepareLatestTableData(tablet, id, 0, true); + PrepareLatestTableData(tablet, id, 0); } { @@ -5504,17 +5547,9 @@ TEST_F(TabletImplTest, AggregatorRecovery) { ASSERT_EQ(0, response.code()); sleep(3); - - ::openmldb::api::ScanRequest sr; - sr.set_tid(aggr_table_id); - sr.set_pid(1); - sr.set_pk("id1"); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); - ASSERT_EQ(0, (signed)srp.count()); + auto result = ScanFromTablet(aggr_table_id, 1, "id1", "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(0, result.second); auto aggrs = tablet.GetAggregators(base_table_id, 1); ASSERT_EQ(aggrs->size(), 1); auto aggr = aggrs->at(0); @@ -5586,26 +5621,13 @@ TEST_F(TabletImplTest, AggregatorRecovery) { ASSERT_EQ(0, response.code()); sleep(3); - - ::openmldb::api::ScanRequest sr; - sr.set_tid(aggr_table_id); - sr.set_pid(1); - sr.set_pk("id1"); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); - ASSERT_EQ(49, (signed)srp.count()); - sr.set_tid(aggr_table_id); - sr.set_pid(1); - sr.set_pk("id2"); - sr.set_st(100); - sr.set_et(0); - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); + auto result = ScanFromTablet(aggr_table_id, 1, "id1", "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(49, result.second); + result = ScanFromTablet(aggr_table_id, 1, "id2", "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); // 50 = 49 (the number of aggr value) + 1 (the number of out-of-order put) - ASSERT_EQ(50, (signed)srp.count()); + ASSERT_EQ(50, result.second); auto aggrs = tablet.GetAggregators(base_table_id, 1); ASSERT_EQ(aggrs->size(), 1); auto aggr = aggrs->at(0); @@ -5831,7 +5853,7 @@ TEST_F(TabletImplTest, AggregatorDeleteKey) { ::openmldb::api::PutRequest prequest; ::openmldb::test::SetDimension(0, key, prequest.add_dimensions()); prequest.set_time(i); - prequest.set_value(EncodeAggrRow("id1", i, i)); + prequest.set_value(EncodeAggrRow(key, i, i)); prequest.set_tid(base_table_id); prequest.set_pid(1); ::openmldb::api::PutResponse presponse; @@ -5844,31 +5866,17 @@ TEST_F(TabletImplTest, AggregatorDeleteKey) { // check the base table for (int32_t k = 1; k <= 2; k++) { std::string key = absl::StrCat("id", k); - ::openmldb::api::ScanRequest sr; - sr.set_tid(base_table_id); - sr.set_pid(1); - sr.set_pk(key); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); - ASSERT_EQ(100, (signed)srp.count()); + auto result = ScanFromTablet(base_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(100, result.second); } // check the pre-aggr table for (int32_t k = 1; k <= 2; k++) { std::string key = absl::StrCat("id", k); - ::openmldb::api::ScanRequest sr; - sr.set_tid(aggr_table_id); - sr.set_pid(1); - sr.set_pk(key); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); - ASSERT_EQ(49, (signed)srp.count()); + auto result = ScanFromTablet(aggr_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(49, result.second); auto aggrs = tablet.GetAggregators(base_table_id, 1); ASSERT_EQ(aggrs->size(), 1); @@ -5892,44 +5900,26 @@ TEST_F(TabletImplTest, AggregatorDeleteKey) { for (int32_t k = 1; k <= 2; k++) { std::string key = absl::StrCat("id", k); - ::openmldb::api::ScanRequest sr; - sr.set_tid(base_table_id); - sr.set_pid(1); - sr.set_pk(key); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); - ASSERT_EQ(k == 1 ? 0 : 100, (signed)srp.count()); + auto result = ScanFromTablet(base_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(k == 1 ? 0 : 100, result.second); } // check the pre-aggr table for (int32_t k = 1; k <= 2; k++) { std::string key = absl::StrCat("id", k); - ::openmldb::api::ScanRequest sr; - sr.set_tid(aggr_table_id); - sr.set_pid(1); - sr.set_pk(key); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); + auto result = ScanFromTablet(aggr_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + auto aggrs = tablet.GetAggregators(base_table_id, 1); + ASSERT_EQ(aggrs->size(), 1); + auto aggr = aggrs->at(0); + ::openmldb::storage::AggrBuffer* aggr_buffer = nullptr; if (k == 1) { - ASSERT_EQ(0, (signed)srp.count()); - auto aggrs = tablet.GetAggregators(base_table_id, 1); - ASSERT_EQ(aggrs->size(), 1); - auto aggr = aggrs->at(0); - ::openmldb::storage::AggrBuffer* aggr_buffer = nullptr; + ASSERT_EQ(0, result.second); ASSERT_FALSE(aggr->GetAggrBuffer(key, &aggr_buffer)); ASSERT_EQ(nullptr, aggr_buffer); } else { - ASSERT_EQ(49, (signed)srp.count()); - auto aggrs = tablet.GetAggregators(base_table_id, 1); - ASSERT_EQ(aggrs->size(), 1); - auto aggr = aggrs->at(0); - ::openmldb::storage::AggrBuffer* aggr_buffer; + ASSERT_EQ(49, result.second); aggr->GetAggrBuffer(key, &aggr_buffer); ASSERT_EQ(aggr_buffer->aggr_cnt_, 2); ASSERT_EQ(aggr_buffer->aggr_val_.vlong, 199); @@ -5964,44 +5954,26 @@ TEST_F(TabletImplTest, AggregatorDeleteKey) { for (int32_t k = 1; k <= 2; k++) { std::string key = absl::StrCat("id", k); - ::openmldb::api::ScanRequest sr; - sr.set_tid(base_table_id); - sr.set_pid(1); - sr.set_pk(key); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); - ASSERT_EQ(k == 1 ? 0 : 100, (signed)srp.count()); + auto result = ScanFromTablet(base_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(k == 1 ? 0 : 100, result.second); } // check the pre-aggr table for (int32_t k = 1; k <= 2; k++) { std::string key = absl::StrCat("id", k); - ::openmldb::api::ScanRequest sr; - sr.set_tid(aggr_table_id); - sr.set_pid(1); - sr.set_pk(key); - sr.set_st(100); - sr.set_et(0); - ::openmldb::api::ScanResponse srp; - tablet.Scan(NULL, &sr, &srp, &closure); - ASSERT_EQ(0, srp.code()); + auto result = ScanFromTablet(aggr_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + auto aggrs = tablet.GetAggregators(base_table_id, 1); + ASSERT_EQ(aggrs->size(), 1); + auto aggr = aggrs->at(0); + ::openmldb::storage::AggrBuffer* aggr_buffer = nullptr; if (k == 1) { - ASSERT_EQ(0, (signed)srp.count()); - auto aggrs = tablet.GetAggregators(base_table_id, 1); - ASSERT_EQ(aggrs->size(), 1); - auto aggr = aggrs->at(0); - ::openmldb::storage::AggrBuffer* aggr_buffer = nullptr; + ASSERT_EQ(0, result.second) << "scan key is " << key << " tid " << aggr_table_id; ASSERT_FALSE(aggr->GetAggrBuffer(key, &aggr_buffer)); ASSERT_EQ(nullptr, aggr_buffer); } else { - ASSERT_EQ(49, (signed)srp.count()); - auto aggrs = tablet.GetAggregators(base_table_id, 1); - ASSERT_EQ(aggrs->size(), 1); - auto aggr = aggrs->at(0); - ::openmldb::storage::AggrBuffer* aggr_buffer; + ASSERT_EQ(49, result.second); aggr->GetAggrBuffer(key, &aggr_buffer); ASSERT_EQ(aggr_buffer->aggr_cnt_, 2); ASSERT_EQ(aggr_buffer->aggr_val_.vlong, 199); @@ -6011,6 +5983,303 @@ TEST_F(TabletImplTest, AggregatorDeleteKey) { } } +struct DeleteInputParm { + DeleteInputParm() = default; + DeleteInputParm(const std::string& pk, const std::optional& start_ts_i, + const std::optional& end_ts_i) : key(pk), start_ts(start_ts_i), end_ts(end_ts_i) {} + std::string key; + std::optional start_ts = std::nullopt; + std::optional end_ts = std::nullopt; +}; + +struct DeleteExpectParm { + DeleteExpectParm() = default; + DeleteExpectParm(uint64_t base_t_cnt, uint64_t agg_t_cnt, uint64_t agg_cnt, uint64_t value, uint64_t t_value) : + base_table_cnt(base_t_cnt), aggr_table_cnt(agg_t_cnt), aggr_cnt(agg_cnt), + aggr_buffer_value(value), aggr_table_value(t_value) {} + uint64_t base_table_cnt = 0; + uint64_t aggr_table_cnt = 0; + uint32_t aggr_cnt = 0; + uint64_t aggr_buffer_value = 0; + uint64_t aggr_table_value = 0; +}; + +struct DeleteParm { + DeleteParm(const DeleteInputParm& input_p, const DeleteExpectParm& expect_p) : input(input_p), expect(expect_p) {} + DeleteInputParm input; + DeleteExpectParm expect; +}; + +class AggregatorDeleteTest : public ::testing::TestWithParam {}; + +TEST_P(AggregatorDeleteTest, AggregatorDeleteRange) { + uint32_t aggr_table_id = 0; + uint32_t base_table_id = 0; + const auto& parm = GetParam(); + TabletImpl tablet; + tablet.Init(""); + ::openmldb::api::TableMeta base_table_meta; + // base table + uint32_t id = counter++; + base_table_id = id; + ::openmldb::api::CreateTableRequest request; + ::openmldb::api::TableMeta* table_meta = request.mutable_table_meta(); + table_meta->set_tid(id); + AddDefaultAggregatorBaseSchema(table_meta); + base_table_meta.CopyFrom(*table_meta); + ::openmldb::api::CreateTableResponse response; + MockClosure closure; + tablet.CreateTable(NULL, &request, &response, &closure); + ASSERT_EQ(0, response.code()); + + // pre aggr table + id = counter++; + aggr_table_id = id; + ::openmldb::api::TableMeta agg_table_meta; + table_meta = request.mutable_table_meta(); + table_meta->Clear(); + table_meta->set_tid(id); + AddDefaultAggregatorSchema(table_meta); + agg_table_meta.CopyFrom(*table_meta); + tablet.CreateTable(NULL, &request, &response, &closure); + ASSERT_EQ(0, response.code()); + + // create aggr + ::openmldb::api::CreateAggregatorRequest aggr_request; + table_meta = aggr_request.mutable_base_table_meta(); + table_meta->CopyFrom(base_table_meta); + aggr_request.set_aggr_table_tid(aggr_table_id); + aggr_request.set_aggr_table_pid(1); + aggr_request.set_aggr_col("col3"); + aggr_request.set_aggr_func("sum"); + aggr_request.set_index_pos(0); + aggr_request.set_order_by_col("ts_col"); + aggr_request.set_bucket_size("5"); + ::openmldb::api::CreateAggregatorResponse aggr_response; + tablet.CreateAggregator(NULL, &aggr_request, &aggr_response, &closure); + ASSERT_EQ(0, response.code()); + + // put data to base table + for (int32_t k = 1; k <= 2; k++) { + std::string key = absl::StrCat("id", k); + for (int32_t i = 1; i <= 100; i++) { + ::openmldb::api::PutRequest prequest; + ::openmldb::test::SetDimension(0, key, prequest.add_dimensions()); + prequest.set_time(i); + prequest.set_value(EncodeAggrRow("id1", i, i)); + prequest.set_tid(base_table_id); + prequest.set_pid(1); + ::openmldb::api::PutResponse presponse; + MockClosure closure; + tablet.Put(NULL, &prequest, &presponse, &closure); + ASSERT_EQ(0, presponse.code()); + } + } + + // check the base table + for (int32_t k = 1; k <= 2; k++) { + std::string key = absl::StrCat("id", k); + auto result = ScanFromTablet(base_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(100, result.second); + } + + // check the pre-aggr table + for (int32_t k = 1; k <= 2; k++) { + std::string key = absl::StrCat("id", k); + auto result = ScanFromTablet(aggr_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + ASSERT_EQ(19, result.second); + + auto aggrs = tablet.GetAggregators(base_table_id, 1); + ASSERT_EQ(aggrs->size(), 1); + auto aggr = aggrs->at(0); + ::openmldb::storage::AggrBuffer* aggr_buffer; + aggr->GetAggrBuffer(key, &aggr_buffer); + ASSERT_EQ(aggr_buffer->aggr_cnt_, 5); + ASSERT_EQ(aggr_buffer->aggr_val_.vlong, 490); + ASSERT_EQ(aggr_buffer->binlog_offset_, 100 * k); + } + + // delete key id1 + ::openmldb::api::DeleteRequest dr; + ::openmldb::api::GeneralResponse res; + dr.set_tid(base_table_id); + dr.set_pid(1); + auto dim = dr.add_dimensions(); + dim->set_idx(0); + dim->set_key(parm.input.key); + if (parm.input.start_ts.has_value()) { + dr.set_ts(parm.input.start_ts.value()); + } + if (parm.input.end_ts.has_value()) { + dr.set_end_ts(parm.input.end_ts.value()); + } + tablet.Delete(NULL, &dr, &res, &closure); + ASSERT_EQ(0, res.code()); + + for (int32_t k = 1; k <= 2; k++) { + std::string key = absl::StrCat("id", k); + auto result = ScanFromTablet(base_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + if (k == 1) { + ASSERT_EQ(result.second, parm.expect.base_table_cnt); + } else { + ASSERT_EQ(result.second, 100); + } + } + + // check the pre-aggr table + for (int32_t k = 1; k <= 2; k++) { + std::string key = absl::StrCat("id", k); + auto result = ScanFromTablet(aggr_table_id, 1, key, "", 100, 0, &tablet); + ASSERT_EQ(0, result.first); + auto aggrs = tablet.GetAggregators(base_table_id, 1); + ASSERT_EQ(aggrs->size(), 1); + auto aggr = aggrs->at(0); + ::openmldb::storage::AggrBuffer* aggr_buffer = nullptr; + if (k == 1) { + ASSERT_EQ(result.second, parm.expect.aggr_table_cnt); + ASSERT_TRUE(aggr->GetAggrBuffer(key, &aggr_buffer)); + ASSERT_EQ(aggr_buffer->aggr_cnt_, parm.expect.aggr_cnt); + ASSERT_EQ(aggr_buffer->aggr_val_.vlong, parm.expect.aggr_buffer_value); + } else { + ASSERT_EQ(19, result.second); + aggr->GetAggrBuffer(key, &aggr_buffer); + ASSERT_EQ(aggr_buffer->aggr_cnt_, 5); + ASSERT_EQ(aggr_buffer->aggr_val_.vlong, 490); + ASSERT_EQ(aggr_buffer->binlog_offset_, 100 * k); + } + } + for (int i = 1; i <= 2; i++) { + std::string key = absl::StrCat("id", i); + ::openmldb::api::ScanRequest sr; + sr.set_tid(aggr_table_id); + sr.set_pid(1); + sr.set_pk(key); + sr.set_st(100); + sr.set_et(0); + std::shared_ptr<::openmldb::api::ScanResponse> srp = std::make_shared<::openmldb::api::ScanResponse>(); + tablet.Scan(nullptr, &sr, srp.get(), &closure); + ASSERT_EQ(0, srp->code()); + + ::openmldb::base::ScanKvIterator kv_it(key, srp); + codec::RowView row_view(agg_table_meta.column_desc()); + uint64_t last_k = 0; + int64_t total_val = 0; + while (kv_it.Valid()) { + uint64_t k = kv_it.GetKey(); + if (last_k != k) { + const int8_t* row_ptr = reinterpret_cast(kv_it.GetValue().data()); + char* aggr_val = nullptr; + uint32_t ch_length = 0; + ASSERT_EQ(row_view.GetValue(row_ptr, 4, &aggr_val, &ch_length), 0); + int64_t val = *reinterpret_cast(aggr_val); + total_val += val; + last_k = k; + } + kv_it.Next(); + } + if (i == 1) { + ASSERT_EQ(total_val, parm.expect.aggr_table_value); + } else { + ASSERT_EQ(total_val, 4560); + } + } +} + +// [st, et] +uint64_t ComputeAgg(uint64_t st, uint64_t et) { + uint64_t val = 0; + for (auto i = st; i <= et; i++) { + val += i; + } + return val; +} + +std::vector delete_cases = { + /*0*/ DeleteParm(DeleteInputParm("id1", std::nullopt, 200), + DeleteExpectParm(100, 19, 5, ComputeAgg(96, 100), ComputeAgg(1, 95))), + /*1*/ DeleteParm(DeleteInputParm("id1", std::nullopt, 100), + DeleteExpectParm(100, 19, 5, ComputeAgg(96, 100), ComputeAgg(1, 95))), + /*2*/ DeleteParm(DeleteInputParm("id1", 200, 100), + DeleteExpectParm(100, 19, 5, ComputeAgg(96, 100), ComputeAgg(1, 95))), + /*3*/ DeleteParm(DeleteInputParm("id1", 200, 99), + DeleteExpectParm(99, 19, 4, ComputeAgg(96, 99), ComputeAgg(1, 95))), + /*4*/ DeleteParm(DeleteInputParm("id1", 200, 98), + DeleteExpectParm(98, 19, 3, ComputeAgg(96, 98), ComputeAgg(1, 95))), + /*5*/ DeleteParm(DeleteInputParm("id1", 99, 97), + DeleteExpectParm(98, 19, 3, 100 + 96 + 97, ComputeAgg(1, 95))), + /*6*/ DeleteParm(DeleteInputParm("id1", 98, 96), + DeleteExpectParm(98, 19, 3, 100 + 99 + 96, ComputeAgg(1, 95))), + /*7*/ DeleteParm(DeleteInputParm("id1", 98, 95), + DeleteExpectParm(97, 19, 2, 100 + 99, ComputeAgg(1, 95))), + /*8*/ DeleteParm(DeleteInputParm("id1", 95, 94), + DeleteExpectParm(99, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 94))), + /*9*/ DeleteParm(DeleteInputParm("id1", 95, 91), + DeleteExpectParm(96, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 91))), + /*10*/ DeleteParm(DeleteInputParm("id1", 95, 90), + DeleteExpectParm(95, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 90))), + /*11*/ DeleteParm(DeleteInputParm("id1", 95, 89), + DeleteExpectParm(94, 21, 5, ComputeAgg(96, 100), ComputeAgg(1, 89))), + /*12*/ DeleteParm(DeleteInputParm("id1", 95, 86), + DeleteExpectParm(91, 21, 5, ComputeAgg(96, 100), ComputeAgg(1, 86))), + /*13*/ DeleteParm(DeleteInputParm("id1", 95, 85), + DeleteExpectParm(90, 19, 5, ComputeAgg(96, 100), ComputeAgg(1, 85))), + /*14*/ DeleteParm(DeleteInputParm("id1", 95, 84), + DeleteExpectParm(89, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 84))), + /*15*/ DeleteParm(DeleteInputParm("id1", 95, 81), + DeleteExpectParm(86, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 81))), + /*16*/ DeleteParm(DeleteInputParm("id1", 95, 80), + DeleteExpectParm(85, 18, 5, ComputeAgg(96, 100), ComputeAgg(1, 80))), + /*17*/ DeleteParm(DeleteInputParm("id1", 95, 79), + DeleteExpectParm(84, 19, 5, ComputeAgg(96, 100), ComputeAgg(1, 79))), + /*18*/ DeleteParm(DeleteInputParm("id1", 78, 76), + DeleteExpectParm(98, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 95) - 78 - 77)), + /*19*/ DeleteParm(DeleteInputParm("id1", 80, 75), + DeleteExpectParm(95, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 95) - ComputeAgg(76, 80))), + /*20*/ DeleteParm(DeleteInputParm("id1", 80, 74), + DeleteExpectParm(94, 21, 5, ComputeAgg(96, 100), ComputeAgg(1, 95) - ComputeAgg(75, 80))), + /*21*/ DeleteParm(DeleteInputParm("id1", 80, 68), + DeleteExpectParm(88, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 68) + ComputeAgg(81, 95))), + /*22*/ DeleteParm(DeleteInputParm("id1", 80, 58), + DeleteExpectParm(78, 18, 5, ComputeAgg(96, 100), ComputeAgg(1, 58) + ComputeAgg(81, 95))), + /*23*/ DeleteParm(DeleteInputParm("id1", 100, 94), DeleteExpectParm(94, 20, 0, 0, ComputeAgg(1, 94))), + /*24*/ DeleteParm(DeleteInputParm("id1", 100, 91), DeleteExpectParm(91, 20, 0, 0, ComputeAgg(1, 91))), + /*25*/ DeleteParm(DeleteInputParm("id1", 100, 90), DeleteExpectParm(90, 20, 0, 0, ComputeAgg(1, 90))), + /*26*/ DeleteParm(DeleteInputParm("id1", 100, 89), DeleteExpectParm(89, 21, 0, 0, ComputeAgg(1, 89))), + /*27*/ DeleteParm(DeleteInputParm("id1", 100, 85), DeleteExpectParm(85, 19, 0, 0, ComputeAgg(1, 85))), + /*28*/ DeleteParm(DeleteInputParm("id1", 100, 84), DeleteExpectParm(84, 20, 0, 0, ComputeAgg(1, 84))), + /*29*/ DeleteParm(DeleteInputParm("id1", 99, 84), DeleteExpectParm(85, 20, 1, 100, ComputeAgg(1, 84))), + /*30*/ DeleteParm(DeleteInputParm("id1", 96, 84), + DeleteExpectParm(88, 20, 4, ComputeAgg(97, 100), ComputeAgg(1, 84))), + /*31*/ DeleteParm(DeleteInputParm("id1", 2, 1), + DeleteExpectParm(99, 20, 5, ComputeAgg(96, 100), ComputeAgg(1, 95) - 2)), + /*32*/ DeleteParm(DeleteInputParm("id1", 2, std::nullopt), + DeleteExpectParm(98, 20, 5, ComputeAgg(96, 100), ComputeAgg(3, 95))), + /*33*/ DeleteParm(DeleteInputParm("id1", 5, std::nullopt), + DeleteExpectParm(95, 20, 5, ComputeAgg(96, 100), ComputeAgg(6, 95))), + /*34*/ DeleteParm(DeleteInputParm("id1", 6, std::nullopt), + DeleteExpectParm(94, 19, 5, ComputeAgg(96, 100), ComputeAgg(7, 95))), + /*35*/ DeleteParm(DeleteInputParm("id1", 6, 0), + DeleteExpectParm(94, 19, 5, ComputeAgg(96, 100), ComputeAgg(7, 95))), + /*36*/ DeleteParm(DeleteInputParm("id1", 6, 1), + DeleteExpectParm(95, 21, 5, ComputeAgg(96, 100), ComputeAgg(7, 95) + 1)), + /*37*/ DeleteParm(DeleteInputParm("id1", 10, 1), + DeleteExpectParm(91, 21, 5, ComputeAgg(96, 100), ComputeAgg(11, 95) + 1)), + /*38*/ DeleteParm(DeleteInputParm("id1", 11, 1), + DeleteExpectParm(90, 20, 5, ComputeAgg(96, 100), ComputeAgg(12, 95) + 1)), + /*39*/ DeleteParm(DeleteInputParm("id1", 11, 0), + DeleteExpectParm(89, 18, 5, ComputeAgg(96, 100), ComputeAgg(12, 95))), + /*40*/ DeleteParm(DeleteInputParm("id1", 11, std::nullopt), + DeleteExpectParm(89, 18, 5, ComputeAgg(96, 100), ComputeAgg(12, 95))), + /*41*/ DeleteParm(DeleteInputParm("id1", 100, std::nullopt), DeleteExpectParm(0, 2, 0, 0, 0)), + /*42*/ DeleteParm(DeleteInputParm("id1", 100, 0), DeleteExpectParm(0, 2, 0, 0, 0)), + /*43*/ DeleteParm(DeleteInputParm("id1", std::nullopt, 0), DeleteExpectParm(0, 2, 0, 0, 0)), +}; + +INSTANTIATE_TEST_SUITE_P(AggregatorTest, AggregatorDeleteTest, testing::ValuesIn(delete_cases)); + TEST_F(TabletImplTest, DeleteRange) { uint32_t id = counter++; MockClosure closure; diff --git a/src/zk/dist_lock_test.cc b/src/zk/dist_lock_test.cc index cf81d44ece2..0bf33604bf0 100644 --- a/src/zk/dist_lock_test.cc +++ b/src/zk/dist_lock_test.cc @@ -43,7 +43,7 @@ void OnLockedCallback() { call_invoked = true; } void OnLostCallback() {} TEST_F(DistLockTest, Lock) { - ZkClient client("127.0.0.1:6181", "", 10000, "127.0.0.1:9527", "/openmldb_lock"); + ZkClient client("127.0.0.1:6181", "", 10000, "127.0.0.1:9527", "/openmldb_lock", "", ""); bool ok = client.Init(); ASSERT_TRUE(ok); DistLock lock("/openmldb_lock/nameserver_lock", &client, boost::bind(&OnLockedCallback), @@ -59,7 +59,7 @@ TEST_F(DistLockTest, Lock) { lock.CurrentLockValue(current_lock); ASSERT_EQ("endpoint1", current_lock); call_invoked = false; - ZkClient client2("127.0.0.1:6181", "", 10000, "127.0.0.1:9527", "/openmldb_lock"); + ZkClient client2("127.0.0.1:6181", "", 10000, "127.0.0.1:9527", "/openmldb_lock", "", ""); ok = client2.Init(); if (!ok) { lock.Stop(); diff --git a/src/zk/zk_client.cc b/src/zk/zk_client.cc index 382ce4c00f2..ecc94c1251c 100644 --- a/src/zk/zk_client.cc +++ b/src/zk/zk_client.cc @@ -64,11 +64,15 @@ void ItemWatcher(zhandle_t* zh, int type, int state, const char* path, void* wat } ZkClient::ZkClient(const std::string& hosts, const std::string& real_endpoint, int32_t session_timeout, - const std::string& endpoint, const std::string& zk_root_path) + const std::string& endpoint, const std::string& zk_root_path, + const std::string& auth_schema, const std::string& cert) : hosts_(hosts), session_timeout_(session_timeout), endpoint_(endpoint), zk_root_path_(zk_root_path), + auth_schema_(auth_schema), + cert_(cert), + acl_vector_(ZOO_OPEN_ACL_UNSAFE), real_endpoint_(real_endpoint), nodes_root_path_(zk_root_path_ + "/nodes"), nodes_watch_callbacks_(), @@ -88,11 +92,15 @@ ZkClient::ZkClient(const std::string& hosts, const std::string& real_endpoint, i } ZkClient::ZkClient(const std::string& hosts, int32_t session_timeout, const std::string& endpoint, - const std::string& zk_root_path, const std::string& zone_path) + const std::string& zk_root_path, const std::string& zone_path, + const std::string& auth_schema, const std::string& cert) : hosts_(hosts), session_timeout_(session_timeout), endpoint_(endpoint), zk_root_path_(zk_root_path), + auth_schema_(auth_schema), + cert_(cert), + acl_vector_(ZOO_OPEN_ACL_UNSAFE), nodes_root_path_(zone_path), nodes_watch_callbacks_(), mu_(), @@ -133,6 +141,14 @@ bool ZkClient::Init(int log_level, const std::string& log_file) { PDLOG(WARNING, "fail to init zk handler with hosts %s, session_timeout %d", hosts_.c_str(), session_timeout_); return false; } + if (!cert_.empty()) { + if (zoo_add_auth(zk_, auth_schema_.c_str(), cert_.data(), cert_.length(), NULL, NULL) != ZOK) { + PDLOG(WARNING, "auth failed. schema: %s cert: %s", auth_schema_.c_str(), cert_.c_str()); + return false; + } + acl_vector_ = ZOO_CREATOR_ALL_ACL; + PDLOG(INFO, "auth ok. schema: %s cert: %s", auth_schema_.c_str(), cert_.c_str()); + } return true; } @@ -173,7 +189,7 @@ bool ZkClient::Register(bool startup_flag) { if (startup_flag) { value = "startup_" + endpoint_; } - int ret = zoo_create(zk_, node.c_str(), value.c_str(), value.size(), &ZOO_OPEN_ACL_UNSAFE, ZOO_EPHEMERAL, NULL, 0); + int ret = zoo_create(zk_, node.c_str(), value.c_str(), value.size(), &acl_vector_, ZOO_EPHEMERAL, NULL, 0); if (ret == ZOK) { PDLOG(INFO, "register self with endpoint %s ok", endpoint_.c_str()); registed_.store(true, std::memory_order_relaxed); @@ -231,7 +247,7 @@ bool ZkClient::RegisterName() { } PDLOG(WARNING, "set node with name %s value %s failed", sname.c_str(), value.c_str()); } else { - int ret = zoo_create(zk_, name.c_str(), value.c_str(), value.size(), &ZOO_OPEN_ACL_UNSAFE, 0, NULL, 0); + int ret = zoo_create(zk_, name.c_str(), value.c_str(), value.size(), &acl_vector_, 0, NULL, 0); if (ret == ZOK) { PDLOG(INFO, "register with name %s value %s ok", sname.c_str(), value.c_str()); return true; @@ -281,7 +297,7 @@ bool ZkClient::CreateNode(const std::string& node, const std::string& value, int uint32_t size = node.size() + 11; char path_buffer[size]; // NOLINT int ret = - zoo_create(zk_, node.c_str(), value.c_str(), value.size(), &ZOO_OPEN_ACL_UNSAFE, flags, path_buffer, size); + zoo_create(zk_, node.c_str(), value.c_str(), value.size(), &acl_vector_, flags, path_buffer, size); if (ret == ZOK) { assigned_path_name.assign(path_buffer, size - 1); PDLOG(INFO, "create node %s ok and real node name %s", node.c_str(), assigned_path_name.c_str()); @@ -371,9 +387,11 @@ bool ZkClient::GetNodeValueAndStat(const char* node, std::string* value, Stat* s bool ZkClient::DeleteNode(const std::string& node) { std::lock_guard lock(mu_); - if (zoo_delete(zk_, node.c_str(), -1) == ZOK) { + int ret = zoo_delete(zk_, node.c_str(), -1); + if (ret == ZOK) { return true; } + PDLOG(WARNING, "delete %s failed. error no is %d", node.c_str(), ret); return false; } @@ -597,7 +615,7 @@ bool ZkClient::MkdirNoLock(const std::string& path) { } full_path += *it; index++; - int ret = zoo_create(zk_, full_path.c_str(), "", 0, &ZOO_OPEN_ACL_UNSAFE, 0, NULL, 0); + int ret = zoo_create(zk_, full_path.c_str(), "", 0, &acl_vector_, 0, NULL, 0); if (ret == ZNODEEXISTS || ret == ZOK) { continue; } diff --git a/src/zk/zk_client.h b/src/zk/zk_client.h index e06c0de7e6a..344df5753e2 100644 --- a/src/zk/zk_client.h +++ b/src/zk/zk_client.h @@ -46,10 +46,12 @@ class ZkClient { // session_timeout, the session timeout // endpoint, the client endpoint ZkClient(const std::string& hosts, const std::string& real_endpoint, int32_t session_timeout, - const std::string& endpoint, const std::string& zk_root_path); + const std::string& endpoint, const std::string& zk_root_path, + const std::string& auth_schema, const std::string& cert); ZkClient(const std::string& hosts, int32_t session_timeout, const std::string& endpoint, - const std::string& zk_root_path, const std::string& zone_path); + const std::string& zk_root_path, const std::string& zone_path, + const std::string& auth_schema, const std::string& cert); ~ZkClient(); // init zookeeper connections @@ -145,6 +147,9 @@ class ZkClient { int32_t session_timeout_; std::string endpoint_; std::string zk_root_path_; + std::string auth_schema_; + std::string cert_; + struct ACL_vector acl_vector_; std::string real_endpoint_; FILE* zk_log_stream_file_ = NULL; diff --git a/src/zk/zk_client_test.cc b/src/zk/zk_client_test.cc index 0d4ffb5af83..04879c74359 100644 --- a/src/zk/zk_client_test.cc +++ b/src/zk/zk_client_test.cc @@ -49,13 +49,13 @@ void WatchCallback(const std::vector& endpoints) { } TEST_F(ZkClientTest, BadZk) { - ZkClient client("127.0.0.1:13181", "", session_timeout, "127.0.0.1:9527", "/openmldb"); + ZkClient client("127.0.0.1:13181", "", session_timeout, "127.0.0.1:9527", "/openmldb", "", ""); bool ok = client.Init(); ASSERT_FALSE(ok); } TEST_F(ZkClientTest, Init) { - ZkClient client("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb"); + ZkClient client("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb", "", ""); bool ok = client.Init(); ASSERT_TRUE(ok); ok = client.Register(); @@ -71,7 +71,7 @@ TEST_F(ZkClientTest, Init) { ok = client.WatchNodes(); ASSERT_TRUE(ok); { - ZkClient client2("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9528", "/openmldb"); + ZkClient client2("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9528", "/openmldb", "", ""); ok = client2.Init(); client2.Register(); ASSERT_TRUE(ok); @@ -83,7 +83,7 @@ TEST_F(ZkClientTest, Init) { } TEST_F(ZkClientTest, CreateNode) { - ZkClient client("127.0.0.1:6181", "", 1000, "127.0.0.1:9527", "/openmldb1"); + ZkClient client("127.0.0.1:6181", "", 1000, "127.0.0.1:9527", "/openmldb1", "", ""); bool ok = client.Init(); ASSERT_TRUE(ok); @@ -99,7 +99,7 @@ TEST_F(ZkClientTest, CreateNode) { ret = client.IsExistNode(node); ASSERT_EQ(ret, 0); - ZkClient client2("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb1"); + ZkClient client2("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb1", "", ""); ok = client2.Init(); ASSERT_TRUE(ok); @@ -109,7 +109,7 @@ TEST_F(ZkClientTest, CreateNode) { } TEST_F(ZkClientTest, ZkNodeChange) { - ZkClient client("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb1"); + ZkClient client("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb1", "", ""); bool ok = client.Init(); ASSERT_TRUE(ok); @@ -121,7 +121,7 @@ TEST_F(ZkClientTest, ZkNodeChange) { ret = client.IsExistNode(node); ASSERT_EQ(ret, 0); - ZkClient client2("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb1"); + ZkClient client2("127.0.0.1:6181", "", session_timeout, "127.0.0.1:9527", "/openmldb1", "", ""); ok = client2.Init(); ASSERT_TRUE(ok); std::atomic detect(false); @@ -146,6 +146,48 @@ TEST_F(ZkClientTest, ZkNodeChange) { ASSERT_TRUE(detect.load()); } +TEST_F(ZkClientTest, Auth) { + std::string node = "/openmldb_auth/node1"; + { + ZkClient client("127.0.0.1:6181", "", 1000, "127.0.0.1:9527", "/openmldb_auth", "digest", "user1:123456"); + bool ok = client.Init(); + ASSERT_TRUE(ok); + + int ret = client.IsExistNode(node); + ASSERT_EQ(ret, 1); + ok = client.CreateNode(node, "value"); + ASSERT_TRUE(ok); + ret = client.IsExistNode(node); + ASSERT_EQ(ret, 0); + } + { + ZkClient client("127.0.0.1:6181", "", 1000, "127.0.0.1:9527", "/openmldb_auth", "", ""); + bool ok = client.Init(); + ASSERT_TRUE(ok); + std::string value; + ASSERT_FALSE(client.GetNodeValue(node, value)); + ASSERT_FALSE(client.CreateNode("/openmldb_auth/node1/dd", "aaa")); + } + { + ZkClient client("127.0.0.1:6181", "", 1000, "127.0.0.1:9527", "/openmldb_auth", "digest", "user1:wrong"); + bool ok = client.Init(); + ASSERT_TRUE(ok); + std::string value; + ASSERT_FALSE(client.GetNodeValue(node, value)); + ASSERT_FALSE(client.CreateNode("/openmldb_auth/node1/dd", "aaa")); + } + { + ZkClient client("127.0.0.1:6181", "", 1000, "127.0.0.1:9527", "/openmldb_auth", "digest", "user1:123456"); + bool ok = client.Init(); + ASSERT_TRUE(ok); + std::string value; + ASSERT_TRUE(client.GetNodeValue(node, value)); + ASSERT_EQ("value", value); + ASSERT_TRUE(client.DeleteNode(node)); + ASSERT_TRUE(client.DeleteNode("/openmldb_auth")); + } +} + } // namespace zk } // namespace openmldb diff --git a/steps/centos6_build.sh b/steps/centos6_build.sh index a5614b10693..b5cee80838f 100644 --- a/steps/centos6_build.sh +++ b/steps/centos6_build.sh @@ -19,6 +19,7 @@ set +x # self build or workflow IN_WORKFLOW=${IN_WORKFLOW:-"false"} +USE_DEPS_CACHE=${USE_DEPS_CACHE:-"false"} # if download from openmldb.ai OPENMLDB_SOURCE=${OPENMLDB_SOURCE:-"false"} @@ -36,6 +37,13 @@ function tool_install() { chmod +x bazel } +if [ "$USE_DEPS_CACHE" == "true" ]; then + echo "use deps cache, exit" + exit 0 +else + echo "not use deps cache, install tools and build deps" +fi + if [ "$IN_WORKFLOW" == "true" ]; then echo "in workflow" else @@ -56,13 +64,12 @@ echo "add patch in fetch cmake" sed -i'' '34s/WITH_TOOLS=OFF$/WITH_TOOLS=OFF -DWITH_CORE_TOOLS=OFF/' third-party/cmake/FetchRocksDB.cmake # if BUILD_BUNDLED=OFF will download pre-built thirdparty, not good -# so we use cmake to build zetasql only -# it's hard to avoid add zetasql patch twice, so we check the dir to avoid -if [ -d ".deps/build/src/zetasql" ]; then - echo "zetasql already exists, skip add patch, if you want, rm .deps/build/src/zetasql* -rf" +# so we use cmake to build zetasql only and add patch to it +# it's hard to avoid add zetasql patch twice, so we check the stamp to avoid +if [ -e ".deps/build/src/zetasql-stamp/zetasql-build" ]; then + echo "zetasql already exists, skip add patch, if you want, rm .deps/build/src/zetasql-stamp/zetasql-build or whole .deps/build/src/zetasql*" else echo "modify in .deps needs a make first, download&build zetasql first(build will fail)" - # sed -i'' '31s/${BUILD_BUNDLED}/ON/' third-party/CMakeLists.txt cmake -S third-party -B "$(pwd)"/.deps -DSRC_INSTALL_DIR="$(pwd)"/thirdsrc -DDEPS_INSTALL_DIR="$(pwd)"/.deps/usr -DBUILD_BUNDLED=ON cmake --build "$(pwd)"/.deps --target zetasql echo "add patch in .deps zetasql" diff --git a/test/integration-test/openmldb-test-python/install.sh b/test/integration-test/openmldb-test-python/install.sh index 9b16e934807..1f2babc873c 100644 --- a/test/integration-test/openmldb-test-python/install.sh +++ b/test/integration-test/openmldb-test-python/install.sh @@ -17,7 +17,7 @@ set -eE -x CURRENT_DIR=$(dirname "$0") -SPARK_VERSION=0.8.2 +SPARK_VERSION=0.8.3 pushd "${CURRENT_DIR}" cp -r ../../../openmldb ./ sed -i"" -e "s/OPENMLDB_MODE:=standalone/OPENMLDB_MODE:=cluster/g" openmldb/conf/openmldb-env.sh diff --git a/test/test-tool/openmldb-deploy/install.sh b/test/test-tool/openmldb-deploy/install.sh index e0238b2d530..a75cc21fec1 100644 --- a/test/test-tool/openmldb-deploy/install.sh +++ b/test/test-tool/openmldb-deploy/install.sh @@ -32,7 +32,6 @@ cp -f ../release/bin/*.sh bin/ mv ../hosts conf/hosts sed -i"" -e "s/OPENMLDB_VERSION=[0-9]\.[0-9]\.[0-9]/OPENMLDB_VERSION=${VERSION}/g" conf/openmldb-env.sh -sed -i"" -e "s/OPENMLDB_MODE:=standalone/OPENMLDB_MODE:=cluster/g" conf/openmldb-env.sh sed -i"" -e "s/CLEAR_OPENMLDB_INSTALL_DIR=false/CLEAR_OPENMLDB_INSTALL_DIR=true/g" conf/openmldb-env.sh sh sbin/stop-all.sh sh sbin/clear-all.sh diff --git a/test/test-tool/openmldb-deploy/install_with_name.sh b/test/test-tool/openmldb-deploy/install_with_name.sh index 6ce1851f103..a1525767a36 100644 --- a/test/test-tool/openmldb-deploy/install_with_name.sh +++ b/test/test-tool/openmldb-deploy/install_with_name.sh @@ -32,7 +32,6 @@ rm -f bin/*.sh /bin/cp -f ../test/test-tool/openmldb-deploy/hosts conf/hosts sed -i"" -e "s/OPENMLDB_VERSION=[0-9]\.[0-9]\.[0-9]/OPENMLDB_VERSION=${VERSION}/g" conf/openmldb-env.sh -sed -i"" -e "s/OPENMLDB_MODE:=standalone/OPENMLDB_MODE:=cluster/g" conf/openmldb-env.sh sh sbin/deploy-all.sh for (( i=0; i<=2; i++ )) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 88fc0a877dc..6a7f8cb0e07 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -68,7 +68,7 @@ set(MAKEOPTS "$ENV{MAKEOPTS}" CACHE STRING "Extra options to make") message(STATUS "Install bundled dependencies into ${DEPS_INSTALL_DIR}") set(HYBRIDSQL_ASSERTS_HOME https://github.com/4paradigm/hybridsql-asserts) -set(HYBRIDSQL_ASSERTS_VERSION 0.5.2) +set(HYBRIDSQL_ASSERTS_VERSION 0.6.0) function(get_linux_lsb_release_information) execute_process(COMMAND bash ${CMAKE_SOURCE_DIR}/get-lsb-release.sh @@ -90,17 +90,17 @@ function(init_hybridsql_thirdparty_urls) else() if (LSB_RELEASE_ID_SHORT STREQUAL "centos") set(HYBRIDSQL_ASSERTS_URL "${HYBRIDSQL_ASSERTS_HOME}/releases/download/v${HYBRIDSQL_ASSERTS_VERSION}/thirdparty-${HYBRIDSQL_ASSERTS_VERSION}-linux-gnu-x86_64-centos.tar.gz" PARENT_SCOPE) - set(HYBRIDSQL_ASSERTS_HASH 919ee7aee4c89846f4e242530519b3c34a34567ddcf9f4361d413a44e2f7408c PARENT_SCOPE) + set(HYBRIDSQL_ASSERTS_HASH c415dfdc95a127cdce888aec84c7fa3c02f3c9cb973805dcf23b54517e422e36 PARENT_SCOPE) elseif(LSB_RELEASE_ID_SHORT STREQUAL "ubuntu") set(HYBRIDSQL_ASSERTS_URL "${HYBRIDSQL_ASSERTS_HOME}/releases/download/v${HYBRIDSQL_ASSERTS_VERSION}/thirdparty-${HYBRIDSQL_ASSERTS_VERSION}-linux-gnu-x86_64-ubuntu.tar.gz" PARENT_SCOPE) - set(HYBRIDSQL_ASSERTS_HASH 8bb1f7685bf778539e1f4ba499020504ebc89e8cefa9a294aa0122578ca70716 PARENT_SCOPE) + set(HYBRIDSQL_ASSERTS_HASH 8c95b5fd539c8362d934ae58879d9ae1c27bc0977ca09cc8316ba207e8aaaf1e PARENT_SCOPE) else() message(FATAL_ERROR "no pre-compiled thirdparty for your operation system, try compile thirdparty from source with '-DBUILD_BUNDLED=ON'") endif() endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(HYBRIDSQL_ASSERTS_URL "${HYBRIDSQL_ASSERTS_HOME}/releases/download/v${HYBRIDSQL_ASSERTS_VERSION}/thirdparty-${HYBRIDSQL_ASSERTS_VERSION}-darwin-i386.tar.gz" PARENT_SCOPE) - set(HYBRIDSQL_ASSERTS_HASH 663b0d945c95034b1e17411f3e795f98053bf248860a60025c7802634ce526d8 PARENT_SCOPE) + set(HYBRIDSQL_ASSERTS_HASH 062e606f1d76fe27003bdc23e643305bfa032eadec8c075e7ce6dc22d70f5044 PARENT_SCOPE) endif() endfunction() diff --git a/third-party/cmake/FetchBrpc.cmake b/third-party/cmake/FetchBrpc.cmake index f4318ac21b3..af7e3b910d1 100644 --- a/third-party/cmake/FetchBrpc.cmake +++ b/third-party/cmake/FetchBrpc.cmake @@ -12,16 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -set(BRPC_URL https://github.com/4paradigm/incubator-brpc/archive/a85d1bde8df3a3e2e59a64ea5a3ee3122f9c6daa.zip) -message(STATUS "build brpc from ${BRPC_URL}") +set(BRPC_URL https://github.com/apache/brpc) +set(BRPC_TAG d2b73ec955dd04b06ab55065d9f3b4de1e608bbd) +message(STATUS "build brpc from ${BRPC_URL}@${BRPC_TAG}") + +find_package(Git REQUIRED) ExternalProject_Add( brpc - URL ${BRPC_URL} - URL_HASH SHA256=ea86d39313bed981357d2669daf1a858fcf1ec363465eda2eec60a8504a2c38e + GIT_REPOSITORY ${BRPC_URL} + GIT_TAG ${BRPC_TAG} PREFIX ${DEPS_BUILD_DIR} DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/brpc INSTALL_DIR ${DEPS_INSTALL_DIR} + PATCH_COMMAND ${GIT_EXECUTABLE} checkout . + COMMAND ${GIT_EXECUTABLE} clean -dfx . + COMMAND ${GIT_EXECUTABLE} apply ${PROJECT_SOURCE_DIR}/patches/brpc-1.6.0.patch + COMMAND ${GIT_EXECUTABLE} apply ${PROJECT_SOURCE_DIR}/patches/brpc-1.6.0-2235.patch DEPENDS gflags glog protobuf snappy leveldb gperf openssl CONFIGURE_COMMAND ${CMAKE_COMMAND} -H -B . -DWITH_GLOG=ON -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} ${CMAKE_OPTS} BUILD_COMMAND ${CMAKE_COMMAND} --build . --target brpc-static -- ${MAKEOPTS} diff --git a/third-party/cmake/FetchZetasql.cmake b/third-party/cmake/FetchZetasql.cmake index 09d4f3d6761..b2b1d580593 100644 --- a/third-party/cmake/FetchZetasql.cmake +++ b/third-party/cmake/FetchZetasql.cmake @@ -13,10 +13,10 @@ # limitations under the License. set(ZETASQL_HOME https://github.com/4paradigm/zetasql) -set(ZETASQL_VERSION 0.3.0) -set(ZETASQL_HASH_DARWIN 1b7e9c68d7fee29abf734be57934440b6891d4e80e22d8a92832518914373bea) -set(ZETASQL_HASH_LINUX_UBUNTU 0efb4feb822440e91ccd8c04d3a102cac9730745550168266b3544224fc86a63) -set(ZETASQL_HASH_LINUX_CENTOS 098ecb71b8a3dd7d8c6887d3b2b9306f0a130434f135754fd9930ccb11d80fed) +set(ZETASQL_VERSION 0.3.1) +set(ZETASQL_HASH_DARWIN 48bfdfe5fa91d414b0bf8383f116bc2a1f558c12fa286e49ea5ceede366dfbcf) +set(ZETASQL_HASH_LINUX_UBUNTU 3847ed7a60aeda1192adf7d702076d2db2bd49258992e2af67515a57b8f6f6a6) +set(ZETASQL_HASH_LINUX_CENTOS e73e6259ab2df3ae7289a9ae78600b69a8fbb6e4890d07a1031ccb1e37fa4281) set(ZETASQL_TAG v${ZETASQL_VERSION}) function(init_zetasql_urls) diff --git a/third-party/patches/brpc-1.6.0-2235.patch b/third-party/patches/brpc-1.6.0-2235.patch new file mode 100644 index 00000000000..450b4c6f427 --- /dev/null +++ b/third-party/patches/brpc-1.6.0-2235.patch @@ -0,0 +1,91 @@ +diff --git a/src/brpc/builtin/prometheus_metrics_service.cpp b/src/brpc/builtin/prometheus_metrics_service.cpp +index 7bf8bbf359..88f675bb81 100644 +--- a/src/brpc/builtin/prometheus_metrics_service.cpp ++++ b/src/brpc/builtin/prometheus_metrics_service.cpp +@@ -82,6 +82,12 @@ class PrometheusMetricsDumper : public bvar::Dumper { + std::map _m; + }; + ++butil::StringPiece GetMetricsName(const std::string& name) { ++ auto pos = name.find_first_of('{'); ++ int size = (pos == std::string::npos) ? name.size() : pos; ++ return butil::StringPiece(name.data(), size); ++} ++ + bool PrometheusMetricsDumper::dump(const std::string& name, + const butil::StringPiece& desc) { + if (!desc.empty() && desc[0] == '"') { +@@ -93,8 +99,11 @@ bool PrometheusMetricsDumper::dump(const std::string& name, + // Leave it to DumpLatencyRecorderSuffix to output Summary. + return true; + } +- *_os << "# HELP " << name << '\n' +- << "# TYPE " << name << " gauge" << '\n' ++ ++ auto metrics_name = GetMetricsName(name); ++ ++ *_os << "# HELP " << metrics_name << '\n' ++ << "# TYPE " << metrics_name << " gauge" << '\n' + << name << " " << desc << '\n'; + return true; + } +diff --git a/src/brpc/builtin/prometheus_metrics_service.h b/src/brpc/builtin/prometheus_metrics_service.h +index c844e1e7a0..541b395c82 100644 +--- a/src/brpc/builtin/prometheus_metrics_service.h ++++ b/src/brpc/builtin/prometheus_metrics_service.h +@@ -31,6 +31,7 @@ class PrometheusMetricsService : public brpc_metrics { + ::google::protobuf::Closure* done) override; + }; + ++butil::StringPiece GetMetricsName(const std::string& name); + int DumpPrometheusMetricsToIOBuf(butil::IOBuf* output); + + } // namepace brpc +diff --git a/test/brpc_prometheus_metrics_service_unittest.cpp b/test/brpc_prometheus_metrics_service_unittest.cpp +new file mode 100644 +index 0000000000..b5b0bc10d5 +--- /dev/null ++++ b/test/brpc_prometheus_metrics_service_unittest.cpp +@@ -0,0 +1,42 @@ ++// Licensed to the Apache Software Foundation (ASF) under one ++// or more contributor license agreements. See the NOTICE file ++// distributed with this work for additional information ++// regarding copyright ownership. The ASF licenses this file ++// to you under the Apache License, Version 2.0 (the ++// "License"); you may not use this file except in compliance ++// with the License. You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, ++// software distributed under the License is distributed on an ++// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++// KIND, either express or implied. See the License for the ++// specific language governing permissions and limitations ++// under the License. ++ ++// Date: 2023/05/06 15:10:00 ++ ++#include ++ ++#include "butil/strings/string_piece.h" ++#include "butil/iobuf.h" ++#include "brpc/builtin/prometheus_metrics_service.h" ++ ++namespace { ++ ++class PrometheusMetricsDumperTest : public testing::Test { ++protected: ++ void SetUp() {} ++ void TearDown() {} ++}; ++ ++TEST_F(PrometheusMetricsDumperTest, GetMetricsName) { ++ EXPECT_EQ("", brpc::GetMetricsName("")); ++ ++ EXPECT_EQ("commit_count", brpc::GetMetricsName("commit_count")); ++ ++ EXPECT_EQ("commit_count", brpc::GetMetricsName("commit_count{region=\"1000\"}")); ++} ++ ++} diff --git a/third-party/patches/brpc-1.6.0.patch b/third-party/patches/brpc-1.6.0.patch new file mode 100644 index 00000000000..00dda45df47 --- /dev/null +++ b/third-party/patches/brpc-1.6.0.patch @@ -0,0 +1,120 @@ +diff --git a/src/brpc/builtin/status_service.cpp b/src/brpc/builtin/status_service.cpp +index a6f5a4dae8..67880ec1a3 100644 +--- a/src/brpc/builtin/status_service.cpp ++++ b/src/brpc/builtin/status_service.cpp +@@ -37,6 +37,8 @@ extern MethodStatus* g_client_msg_status; + extern MethodStatus* g_server_msg_status; + } + ++DECLARE_bool(enable_vars_service); ++ + // Defined in vars_service.cpp + void PutVarsHeading(std::ostream& os, bool expand_all); + +@@ -47,7 +49,7 @@ void StatusService::default_method(::google::protobuf::RpcController* cntl_base, + ClosureGuard done_guard(done); + Controller *cntl = static_cast(cntl_base); + const Server* server = cntl->server(); +- const bool use_html = UseHTML(cntl->http_request()); ++ const bool use_html = FLAGS_enable_vars_service ? UseHTML(cntl->http_request()) : false; + + // NOTE: the plain output also fits format of public/configure so that user + // can load values more easily. +diff --git a/src/brpc/server.cpp b/src/brpc/server.cpp +index ce5a0dd2a3..4e1fbbe424 100644 +--- a/src/brpc/server.cpp ++++ b/src/brpc/server.cpp +@@ -109,6 +109,13 @@ butil::static_atomic g_running_server_count = BUTIL_STATIC_ATOMIC_INIT(0); + // Following services may have security issues and are disabled by default. + DEFINE_bool(enable_dir_service, false, "Enable /dir"); + DEFINE_bool(enable_threads_service, false, "Enable /threads"); ++DEFINE_bool(enable_status_service, false, "Enable /status"); ++DEFINE_bool(enable_vars_service, false, "Enable /vars"); ++DEFINE_bool(enable_connections_service, false, "Enable /connections"); ++DEFINE_bool(enable_flags_service, false, "Enable /flags"); ++DEFINE_bool(enable_rpcz_service, false, "Enable /rpcz"); ++DEFINE_bool(enable_hotspots_service, false, "Enable /hotspots/cpu /hotspots/heap /hotspots/growth /hotspots/contention"); ++DEFINE_bool(enable_index_service, false, "Enable /index?as_more"); + + DECLARE_int32(usercode_backup_threads); + DECLARE_bool(usercode_in_pthread); +@@ -465,31 +472,31 @@ Server::~Server() { + + int Server::AddBuiltinServices() { + // Firstly add services shown in tabs. +- if (AddBuiltinService(new (std::nothrow) StatusService)) { ++ if (FLAGS_enable_status_service && AddBuiltinService(new (std::nothrow) StatusService)) { + LOG(ERROR) << "Fail to add StatusService"; + return -1; + } +- if (AddBuiltinService(new (std::nothrow) VarsService)) { ++ if (FLAGS_enable_vars_service && AddBuiltinService(new (std::nothrow) VarsService)) { + LOG(ERROR) << "Fail to add VarsService"; + return -1; + } +- if (AddBuiltinService(new (std::nothrow) ConnectionsService)) { ++ if (FLAGS_enable_connections_service && AddBuiltinService(new (std::nothrow) ConnectionsService)) { + LOG(ERROR) << "Fail to add ConnectionsService"; + return -1; + } +- if (AddBuiltinService(new (std::nothrow) FlagsService)) { ++ if (FLAGS_enable_flags_service && AddBuiltinService(new (std::nothrow) FlagsService)) { + LOG(ERROR) << "Fail to add FlagsService"; + return -1; + } +- if (AddBuiltinService(new (std::nothrow) RpczService)) { ++ if (FLAGS_enable_rpcz_service && AddBuiltinService(new (std::nothrow) RpczService)) { + LOG(ERROR) << "Fail to add RpczService"; + return -1; + } +- if (AddBuiltinService(new (std::nothrow) HotspotsService)) { ++ if (FLAGS_enable_hotspots_service && AddBuiltinService(new (std::nothrow) HotspotsService)) { + LOG(ERROR) << "Fail to add HotspotsService"; + return -1; + } +- if (AddBuiltinService(new (std::nothrow) IndexService)) { ++ if (FLAGS_enable_index_service && AddBuiltinService(new (std::nothrow) IndexService)) { + LOG(ERROR) << "Fail to add IndexService"; + return -1; + } +diff --git a/src/butil/errno.cpp b/src/butil/errno.cpp +index 9b964e114f..2e1c4d379d 100644 +--- a/src/butil/errno.cpp ++++ b/src/butil/errno.cpp +@@ -55,14 +55,19 @@ int DescribeCustomizedErrno( + #if defined(OS_MACOSX) + const int rc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE); + if (rc != EINVAL) +-#else +- desc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE); +- if (desc && strncmp(desc, "Unknown error", 13) != 0) +-#endif + { + fprintf(stderr, "WARNING: Fail to define %s(%d) which is already defined as `%s'", + error_name, error_code, desc); + } ++#else ++ desc = strerror_r(error_code, tls_error_buf, ERROR_BUFSIZE); ++ if (desc != tls_error_buf) ++ { ++ fprintf(stderr, ++ "%d is defined as `%s', probably is the system errno.\n", ++ error_code, desc); ++ } ++#endif + } + errno_desc[error_code - ERRNO_BEGIN] = description; + return 0; // must +diff --git a/src/bvar/default_variables.cpp b/src/bvar/default_variables.cpp +index be02c50a9a..f71f9dd7cf 100644 +--- a/src/bvar/default_variables.cpp ++++ b/src/bvar/default_variables.cpp +@@ -111,7 +111,7 @@ static bool read_proc_status(ProcStat &stat) { + } + const std::string& result = oss.str(); + if (sscanf(result.c_str(), "%d %d %d %d" +- "%d %u %ld %ld", ++ "%d %x %ld %ld", + &stat.pid, &stat.ppid, &stat.pgrp, &stat.session, + &stat.tpgid, &stat.flags, &stat.priority, &stat.nice) != 8) { + PLOG(WARNING) << "Fail to sscanf"; diff --git a/tools/openmldb_ops.py b/tools/openmldb_ops.py index c7ae0663b52..f3069254a65 100644 --- a/tools/openmldb_ops.py +++ b/tools/openmldb_ops.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging - +# for Python 2, don't use f-string log = logging.getLogger(__name__) import os import sys @@ -118,8 +118,8 @@ def RecoverPartition(executor, db, partitions, endpoint_status): db=db, table_name=table_name, pid=pid, leader_endpoint=leader_endpoint)) status = executor.LoadTable(leader_endpoint, table_name, tid, pid) if not status.OK(): - log.error("load table failed. db {db} name {table_name} tid {tid} pid {pid} endpoint {leader_endpoint} msg {status.GetMsg()}".format( - db=db, table_name=table_name, tid=tid, pid=pid, leader_endpoint=leader_endpoint, status=status)) + log.error("load table failed. db {db} name {table_name} tid {tid} pid {pid} endpoint {leader_endpoint} msg {status}".format( + db=db, table_name=table_name, tid=tid, pid=pid, leader_endpoint=leader_endpoint, status=status.GetMsg())) return Status(-1, "recover partition failed") if not partitions[leader_pos].IsAlive(): status = executor.UpdateTableAlive(db, table_name, pid, leader_endpoint, "yes") @@ -204,8 +204,9 @@ def RecoverData(executor): log.error("get all table failed") return for name in tables: + # if recover failed, continue to recover next table if not RecoverTable(executor, db, name).OK(): - return + log.error("recover table failed. db {db} name {name}, check log for detail".format(db=db, name=name)) def ChangeLeader(db, partition, src_endpoint, desc_endpoint, one_replica, restore = True): log.info( @@ -614,8 +615,9 @@ def PrettyPrint(data, header = None): sys.exit() executor = Executor(options.openmldb_bin_path, options.zk_cluster, options.zk_root_path) - if not executor.Connect().OK(): - log.error("connect OpenMLDB failed") + st = executor.Connect() + if not st.OK(): + log.error("connect OpenMLDB failed, {}".format(st.GetMsg())) sys.exit() if options.cmd in manage_ops: status, auto_failover = executor.GetAutofailover() diff --git a/tools/tool.py b/tools/tool.py index e64b172b49b..98876b2cc3a 100644 --- a/tools/tool.py +++ b/tools/tool.py @@ -16,6 +16,7 @@ import subprocess import sys import time +# for Python 2, don't use f-string log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO, format = '%(levelname)s: %(message)s') @@ -84,7 +85,7 @@ def Connect(self): cmd.append("--cmd=showns") status, output = self.RunWithRetuncode(cmd) if not status.OK() or status.GetMsg().find("zk client init failed") != -1: - return Status(-1, "get ns failed"), None + return Status(-1, "get ns failed") result = self.ParseResult(output) for record in result: if record[2] == "leader": @@ -97,7 +98,7 @@ def Connect(self): cmd.append("--cmd=showtablet") status, output = self.RunWithRetuncode(cmd) if not status.OK(): - return Status(-1, "get tablet failed"), None + return Status(-1, "get tablet failed") result = self.ParseResult(output) for record in result: if record[1] != '-': @@ -118,12 +119,13 @@ def RunWithRetuncode(self, command, useshell = USE_SHELL, env = os.environ): try: + log.info(" ".join(command)) p = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = useshell, universal_newlines = universal_newlines, env = env) - output = p.stdout.read() - p.wait() - errout = p.stderr.read() - p.stdout.close() - p.stderr.close() + output, errout = p.communicate() + # TODO(hw): the print from ns/tablet client are not standard, print it for debug + if output != "": + log.info(output) + # errout has glog output, don't print it if "error msg" in output: return Status(-1, output), output return Status(p.returncode, errout), output @@ -166,7 +168,7 @@ def GetAutofailover(self): return status, None if output.find("true") != -1: return Status(), True - return Status(), False; + return Status(), False def SetAutofailover(self, value): cmd = list(self.ns_base_cmd) @@ -276,6 +278,7 @@ def LoadTable(self, endpoint, name, tid, pid, sync = True): cmd = list(self.tablet_base_cmd) cmd.append("--endpoint=" + self.endpoint_map[endpoint]) cmd.append("--cmd=loadtable {} {} {} 0 8".format(name, tid, pid)) + log.info("run {cmd}".format(cmd = cmd)) status, output = self.RunWithRetuncode(cmd) time.sleep(1) if status.OK() and output.find("LoadTable ok") != -1: @@ -289,12 +292,12 @@ def LoadTable(self, endpoint, name, tid, pid, sync = True): if table_stat == "kTableNormal": return Status() elif table_stat == "kTableLoading" or table_stat == "kTableUndefined": - log.info("table is loading... tid {tid} pid {pid}".format(tid, pid)) + log.info("table is loading... tid {tid} pid {pid}".format(tid = tid, pid = pid)) else: - return Status(-1, "table stat is {table_stat}".format(table_stat)) + return Status(-1, "table stat is {table_stat}".format(table_stat = table_stat)) time.sleep(2) - return Status(-1, "execute load table failed") + return Status(-1, "execute load table failed, status {msg}, output {output}".format(msg = status.GetMsg(), output = output)) def GetLeaderFollowerOffset(self, endpoint, tid, pid): cmd = list(self.tablet_base_cmd)