This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 97500fe0c4c [branch-4.1][fix](load) Avoid global BE auth precheck for 
stream load (#66629) (#66924)
97500fe0c4c is described below

commit 97500fe0c4c1c302eed0362c606760a0e9e8fe6b
Author: Gavin Chou <[email protected]>
AuthorDate: Thu Aug 20 09:10:53 2026 +0800

    [branch-4.1][fix](load) Avoid global BE auth precheck for stream load 
(#66629) (#66924)
    
    Pick #66629 to branch-4.1.
    
    Original PR: https://github.com/apache/doris/pull/66629
    Cherry-picked from: bd12e5a2f76eb22f7d453d614d35b47c8c2ecb77
    Backport note: branch-4.1 does not have
    `HttpRequest::mark_send_reply()`, so the master-only call was dropped
    while resolving `stream_load.cpp`.
    
    Validation:
    - `sh format_code.sh` on the changed BE C++ files
    - `ninja -C be/ut_build_ASAN -j 39 doris_be_test`
    - `doris_be_test --gtest_filter=StreamLoadTest.*:HttpAuthTest.*` (9
    tests passed)
    
    Co-authored-by: gavinchou <[email protected]>
---
 be/src/service/http/action/http_stream.cpp         |  14 +-
 be/src/service/http/action/http_stream.h           |   6 +-
 be/src/service/http/action/stream_load.cpp         |  15 +-
 be/src/service/http/action/stream_load.h           |   6 +-
 be/src/service/http/action/stream_load_2pc.cpp     |   8 +-
 be/src/service/http/action/stream_load_2pc.h       |   5 +-
 .../test_dml_stream_load_be_auth_docker.groovy     | 321 +++++++++++++++++++++
 7 files changed, 345 insertions(+), 30 deletions(-)

diff --git a/be/src/service/http/action/http_stream.cpp 
b/be/src/service/http/action/http_stream.cpp
index 8509a187014..92cdf6b9c0b 100644
--- a/be/src/service/http/action/http_stream.cpp
+++ b/be/src/service/http/action/http_stream.cpp
@@ -91,10 +91,10 @@ 
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(http_stream_requests_total, MetricUnit::REQ
 DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(http_stream_duration_ms, 
MetricUnit::MILLISECONDS);
 DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(http_stream_current_processing, 
MetricUnit::REQUESTS);
 
-HttpStreamAction::HttpStreamAction(ExecEnv* exec_env)
-        : HttpHandlerWithAuth(exec_env, TPrivilegeHier::GLOBAL, 
TPrivilegeType::LOAD) {
-    // Use LOAD privilege type: requires LOAD permission
-    // Note: _exec_env is set by parent class HttpHandlerWithAuth
+HttpStreamAction::HttpStreamAction(ExecEnv* exec_env) : _exec_env(exec_env) {
+    // HTTP stream derives db/table from the SQL header and then forwards the
+    // request credentials to FE load RPCs for table-scoped LOAD checks. The 
generic
+    // BE HTTP auth hook only supports caller-provided resource metadata.
     _http_stream_entity =
             
DorisMetrics::instance()->metric_registry()->register_entity("http_stream");
     INT_COUNTER_METRIC_REGISTER(_http_stream_entity, 
http_stream_requests_total);
@@ -181,12 +181,6 @@ Status HttpStreamAction::_handle(HttpRequest* http_req, 
std::shared_ptr<StreamLo
 }
 
 int HttpStreamAction::on_header(HttpRequest* req) {
-    // Call parent's auth check first
-    int ret = HttpHandlerWithAuth::on_header(req);
-    if (ret != 0) {
-        return ret; // Auth failed, return error
-    }
-
     http_stream_current_processing->increment(1);
 
     std::shared_ptr<StreamLoadContext> ctx = 
std::make_shared<StreamLoadContext>(_exec_env);
diff --git a/be/src/service/http/action/http_stream.h 
b/be/src/service/http/action/http_stream.h
index 03b6a7539a2..5ecb19740fb 100644
--- a/be/src/service/http/action/http_stream.h
+++ b/be/src/service/http/action/http_stream.h
@@ -22,7 +22,7 @@
 #include <functional>
 
 #include "load/message_body_sink.h"
-#include "service/http/http_handler_with_auth.h"
+#include "service/http/http_handler.h"
 #include "util/client_cache.h"
 
 namespace doris {
@@ -31,7 +31,7 @@ class ExecEnv;
 class Status;
 class StreamLoadContext;
 
-class HttpStreamAction : public HttpHandlerWithAuth {
+class HttpStreamAction : public HttpHandler {
 public:
     HttpStreamAction(ExecEnv* exec_env);
     ~HttpStreamAction() override;
@@ -53,6 +53,8 @@ private:
     Status _handle_group_commit(HttpRequest* http_req, 
std::shared_ptr<StreamLoadContext> ctx);
 
 private:
+    ExecEnv* _exec_env;
+
     std::shared_ptr<MetricEntity> _http_stream_entity;
     IntCounter* http_stream_requests_total;
     IntCounter* http_stream_duration_ms;
diff --git a/be/src/service/http/action/stream_load.cpp 
b/be/src/service/http/action/stream_load.cpp
index c391895cc5c..a4ef0bca8f8 100644
--- a/be/src/service/http/action/stream_load.cpp
+++ b/be/src/service/http/action/stream_load.cpp
@@ -91,9 +91,11 @@ static const std::string ASYNC_MODE = "async_mode";
 TStreamLoadPutResult k_stream_load_put_result;
 #endif
 
-StreamLoadAction::StreamLoadAction(ExecEnv* exec_env)
-        : HttpHandlerWithAuth(exec_env, TPrivilegeHier::GLOBAL, 
TPrivilegeType::LOAD) {
-    // Use LOAD privilege type: requires LOAD permission
+StreamLoadAction::StreamLoadAction(ExecEnv* exec_env) : _exec_env(exec_env) {
+    // Stream load forwards the parsed HTTP credentials to FE load RPCs, where 
LOAD
+    // privilege is checked against the actual db/table/txn. A generic BE HTTP
+    // pre-check cannot model every stream-load variant and would duplicate 
that
+    // resource-scoped authorization.
     _stream_load_entity =
             
DorisMetrics::instance()->metric_registry()->register_entity("stream_load");
     INT_COUNTER_METRIC_REGISTER(_stream_load_entity, 
streaming_load_requests_total);
@@ -206,13 +208,6 @@ Status 
StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx) {
 }
 
 int StreamLoadAction::on_header(HttpRequest* req) {
-    // Call parent's auth check first
-    int ret = HttpHandlerWithAuth::on_header(req);
-    if (ret != 0) {
-        return ret; // Auth failed, return error
-    }
-
-    // Continue with stream load specific header processing
     streaming_load_current_processing->increment(1);
 
     std::shared_ptr<StreamLoadContext> ctx = 
std::make_shared<StreamLoadContext>(_exec_env);
diff --git a/be/src/service/http/action/stream_load.h 
b/be/src/service/http/action/stream_load.h
index ce03e1cdf6f..5803c1ea393 100644
--- a/be/src/service/http/action/stream_load.h
+++ b/be/src/service/http/action/stream_load.h
@@ -21,7 +21,7 @@
 #include <string>
 
 #include "common/metrics/metrics.h"
-#include "service/http/http_handler_with_auth.h"
+#include "service/http/http_handler.h"
 
 namespace doris {
 
@@ -30,7 +30,7 @@ class Status;
 class StreamLoadContext;
 class HttpRequest;
 
-class StreamLoadAction : public HttpHandlerWithAuth {
+class StreamLoadAction : public HttpHandler {
 public:
     StreamLoadAction(ExecEnv* exec_env);
     ~StreamLoadAction() override;
@@ -56,6 +56,8 @@ private:
     Status _handle_group_commit(HttpRequest* http_req, 
std::shared_ptr<StreamLoadContext> ctx);
 
 private:
+    ExecEnv* _exec_env;
+
     std::shared_ptr<MetricEntity> _stream_load_entity;
     IntCounter* streaming_load_requests_total;
     IntCounter* streaming_load_duration_ms;
diff --git a/be/src/service/http/action/stream_load_2pc.cpp 
b/be/src/service/http/action/stream_load_2pc.cpp
index 0239ed9fee6..15ca8066cf9 100644
--- a/be/src/service/http/action/stream_load_2pc.cpp
+++ b/be/src/service/http/action/stream_load_2pc.cpp
@@ -39,10 +39,10 @@
 
 namespace doris {
 
-StreamLoad2PCAction::StreamLoad2PCAction(ExecEnv* exec_env)
-        : HttpHandlerWithAuth(exec_env, TPrivilegeHier::GLOBAL, 
TPrivilegeType::LOAD) {
-    // Use LOAD privilege type: requires LOAD permission
-    // Note: _exec_env is set by parent class HttpHandlerWithAuth
+StreamLoad2PCAction::StreamLoad2PCAction(ExecEnv* exec_env) : 
_exec_env(exec_env) {
+    // 2PC commit/abort resolves the transaction's table list in FE and checks 
LOAD
+    // privilege for each table there. A BE HTTP pre-check may only have 
db/label or
+    // txn_id, so it would be less accurate and can reject valid table-scoped 
users.
 }
 
 void StreamLoad2PCAction::handle(HttpRequest* req) {
diff --git a/be/src/service/http/action/stream_load_2pc.h 
b/be/src/service/http/action/stream_load_2pc.h
index 939e9f8dbfa..3e1b3076eee 100644
--- a/be/src/service/http/action/stream_load_2pc.h
+++ b/be/src/service/http/action/stream_load_2pc.h
@@ -19,14 +19,14 @@
 
 #include <string>
 
-#include "service/http/http_handler_with_auth.h"
+#include "service/http/http_handler.h"
 
 namespace doris {
 
 class ExecEnv;
 class HttpRequest;
 
-class StreamLoad2PCAction : public HttpHandlerWithAuth {
+class StreamLoad2PCAction : public HttpHandler {
 public:
     StreamLoad2PCAction(ExecEnv* exec_env);
 
@@ -36,6 +36,7 @@ public:
     std::string get_success_info(const std::string msg, const std::string 
txn_operation);
 
 private:
+    ExecEnv* _exec_env;
 };
 
 } // namespace doris
diff --git 
a/regression-test/suites/auth_call/test_dml_stream_load_be_auth_docker.groovy 
b/regression-test/suites/auth_call/test_dml_stream_load_be_auth_docker.groovy
new file mode 100644
index 00000000000..46d06e5df81
--- /dev/null
+++ 
b/regression-test/suites/auth_call/test_dml_stream_load_be_auth_docker.groovy
@@ -0,0 +1,321 @@
+// 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.
+
+import org.apache.doris.regression.suite.ClusterOptions
+
+suite("test_dml_stream_load_be_auth_off_docker", "docker,auth_call") {
+    def options = new ClusterOptions()
+    options.cloudMode = true
+    options.feNum = 1
+    options.beNum = 1
+
+    docker(options) {
+        String suiteName = "test_dml_stream_load_be_auth_off_docker"
+        String tableUser = "${suiteName}_table_user"
+        String dbUser = "${suiteName}_db_user"
+        String pwd = "C123_567p"
+        String dbName = "${suiteName}_db"
+        String tableName = "${suiteName}_tb"
+        String otherTableName = "${suiteName}_other_tb"
+        String dataFile = 
"${context.file.parent}/../../data/auth_call/stream_load_data.csv"
+
+        def fe = cluster.getFeByIndex(1)
+        def be = cluster.getAllBackends(true).get(0)
+        String feHttpAddress = "${fe.host}:${fe.httpPort}"
+        String beHttpAddress = "${be.host}:${be.httpPort}"
+
+        def parseCurlResult = { String out, String err ->
+            def lines = out.readLines()
+            assertTrue("curl output should include http code, output=${out}, 
err=${err}", !lines.isEmpty())
+            def httpCode = lines.last() as int
+            def body = lines.size() == 1 ? "" : lines[0..-2].join("\n")
+            return [httpCode, body, err]
+        }
+
+        def streamLoad = { String authUser, String endpoint, String table, 
String label,
+                           boolean followRedirect, List extraHeaders ->
+            def command = [
+                    "curl", "--noproxy", "*", "-sS", "-w", "\n%{http_code}",
+                    "-u", "${authUser}:${pwd}",
+                    "-H", "label:${label}",
+                    "-H", "column_separator:,",
+            ]
+            if (followRedirect) {
+                command.add("--location-trusted")
+            }
+            extraHeaders.each { command.addAll(["-H", it]) }
+            command.addAll([
+                    "-T", dataFile,
+                    "http://${endpoint}/api/${dbName}/${table}/_stream_load";
+            ])
+            logger.info("stream load target: ${endpoint}, table: ${table}, 
label: ${label}, user: ${authUser}")
+            def process = command.execute()
+            process.waitForOrKill(7200000)
+            def out = process.text.trim()
+            def err = process.errorStream.text.trim()
+            logger.info("stream load out: ${out}, err: ${err}")
+            return parseCurlResult(out, err)
+        }
+
+        def streamLoad2pc = { String authUser, String table, def txnId, String 
txnOperation ->
+            def command = [
+                    "curl", "--noproxy", "*", "-sS", "-w", "\n%{http_code}",
+                    "-X", "PUT",
+                    "-u", "${authUser}:${pwd}",
+                    "-H", "txn_id:${txnId}",
+                    "-H", "txn_operation:${txnOperation}",
+                    
"http://${beHttpAddress}/api/${dbName}/${table}/_stream_load_2pc";
+            ]
+            logger.info("stream load 2pc target: ${beHttpAddress}, table: 
${table}, "
+                    + "txn operation: ${txnOperation}, user: ${authUser}")
+            def process = command.execute()
+            process.waitForOrKill(7200000)
+            def out = process.text.trim()
+            def err = process.errorStream.text.trim()
+            logger.info("stream load 2pc out: ${out}, err: ${err}")
+            return parseCurlResult(out, err)
+        }
+
+        def assertLoadFailed = { def result ->
+            assertEquals(200, result[0])
+            def json = parseJson(result[1])
+            assertTrue("stream load should fail, body=${result[1]}", 
json.Status != "Success")
+        }
+
+        def assertLoadSuccess = { def result ->
+            assertEquals(200, result[0])
+            def json = parseJson(result[1])
+            assertEquals("Success", json.Status)
+            assertEquals(3, json.NumberTotalRows)
+            assertEquals(3, json.NumberLoadedRows)
+            return json
+        }
+
+        try {
+            try_sql("DROP USER ${tableUser}")
+            try_sql("DROP USER ${dbUser}")
+            sql """DROP DATABASE IF EXISTS ${dbName}"""
+            sql """CREATE USER '${tableUser}' IDENTIFIED BY '${pwd}'"""
+            sql """CREATE USER '${dbUser}' IDENTIFIED BY '${pwd}'"""
+            def clusters = sql """SHOW CLUSTERS"""
+            assertTrue(!clusters.isEmpty())
+            sql """GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO 
${tableUser}"""
+            sql """GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO 
${dbUser}"""
+
+            sql """CREATE DATABASE ${dbName}"""
+            sql """
+                CREATE TABLE ${dbName}.${tableName} (
+                    id BIGINT,
+                    username VARCHAR(20)
+                )
+                DISTRIBUTED BY HASH(id) BUCKETS 1
+                PROPERTIES ("replication_num" = "1")
+            """
+            sql """
+                CREATE TABLE ${dbName}.${otherTableName} (
+                    id BIGINT,
+                    username VARCHAR(20)
+                )
+                DISTRIBUTED BY HASH(id) BUCKETS 1
+                PROPERTIES ("replication_num" = "1")
+            """
+
+            assertLoadFailed(streamLoad(tableUser, beHttpAddress, tableName, 
"off_denied_before_grant",
+                    false, []))
+
+            sql """GRANT LOAD_PRIV ON ${dbName}.${tableName} TO ${tableUser}"""
+
+            assertLoadSuccess(streamLoad(tableUser, beHttpAddress, tableName, 
"off_table_grant_be",
+                    false, []))
+            assertLoadSuccess(streamLoad(tableUser, feHttpAddress, tableName, 
"off_table_grant_fe",
+                    true, []))
+            def prepared = assertLoadSuccess(streamLoad(tableUser, 
beHttpAddress, tableName,
+                    "off_table_grant_2pc", false, ["two_phase_commit:true"]))
+            def committed = streamLoad2pc(tableUser, tableName, 
prepared.TxnId, "commit")
+            assertEquals(200, committed[0])
+            assertEquals("Success", parseJson(committed[1]).status)
+
+            assertLoadFailed(streamLoad(tableUser, beHttpAddress, 
otherTableName,
+                    "off_denied_other_table", false, []))
+
+            sql """GRANT LOAD_PRIV ON ${dbName}.* TO ${dbUser}"""
+            assertLoadSuccess(streamLoad(dbUser, beHttpAddress, 
otherTableName, "off_db_grant_be",
+                    false, []))
+
+            def tableCount = sql """SELECT COUNT(*) FROM 
${dbName}.${tableName}"""
+            assertEquals(9, tableCount[0][0] as int)
+            def otherTableCount = sql """SELECT COUNT(*) FROM 
${dbName}.${otherTableName}"""
+            assertEquals(3, otherTableCount[0][0] as int)
+        } finally {
+            try_sql """DROP DATABASE IF EXISTS ${dbName}"""
+            try_sql("DROP USER ${tableUser}")
+            try_sql("DROP USER ${dbUser}")
+        }
+    }
+}
+
+suite("test_dml_stream_load_be_auth_on_docker", "docker,auth_call") {
+    def options = new ClusterOptions()
+    options.cloudMode = true
+    options.feNum = 1
+    options.beNum = 1
+    options.beConfigs.add("enable_all_http_auth=true")
+
+    docker(options) {
+        String suiteName = "test_dml_stream_load_be_auth_on_docker"
+        String tableUser = "${suiteName}_table_user"
+        String dbUser = "${suiteName}_db_user"
+        String pwd = "C123_567p"
+        String dbName = "${suiteName}_db"
+        String tableName = "${suiteName}_tb"
+        String otherTableName = "${suiteName}_other_tb"
+        String dataFile = 
"${context.file.parent}/../../data/auth_call/stream_load_data.csv"
+
+        def fe = cluster.getFeByIndex(1)
+        def be = cluster.getAllBackends(true).get(0)
+        String feHttpAddress = "${fe.host}:${fe.httpPort}"
+        String beHttpAddress = "${be.host}:${be.httpPort}"
+
+        def parseCurlResult = { String out, String err ->
+            def lines = out.readLines()
+            assertTrue("curl output should include http code, output=${out}, 
err=${err}", !lines.isEmpty())
+            def httpCode = lines.last() as int
+            def body = lines.size() == 1 ? "" : lines[0..-2].join("\n")
+            return [httpCode, body, err]
+        }
+
+        def streamLoad = { String authUser, String endpoint, String table, 
String label,
+                           boolean followRedirect, List extraHeaders ->
+            def command = [
+                    "curl", "--noproxy", "*", "-sS", "-w", "\n%{http_code}",
+                    "-u", "${authUser}:${pwd}",
+                    "-H", "label:${label}",
+                    "-H", "column_separator:,",
+            ]
+            if (followRedirect) {
+                command.add("--location-trusted")
+            }
+            extraHeaders.each { command.addAll(["-H", it]) }
+            command.addAll([
+                    "-T", dataFile,
+                    "http://${endpoint}/api/${dbName}/${table}/_stream_load";
+            ])
+            logger.info("stream load target: ${endpoint}, table: ${table}, 
label: ${label}, user: ${authUser}")
+            def process = command.execute()
+            process.waitForOrKill(7200000)
+            def out = process.text.trim()
+            def err = process.errorStream.text.trim()
+            logger.info("stream load out: ${out}, err: ${err}")
+            return parseCurlResult(out, err)
+        }
+
+        def streamLoad2pc = { String authUser, String table, def txnId, String 
txnOperation ->
+            def command = [
+                    "curl", "--noproxy", "*", "-sS", "-w", "\n%{http_code}",
+                    "-X", "PUT",
+                    "-u", "${authUser}:${pwd}",
+                    "-H", "txn_id:${txnId}",
+                    "-H", "txn_operation:${txnOperation}",
+                    
"http://${beHttpAddress}/api/${dbName}/${table}/_stream_load_2pc";
+            ]
+            logger.info("stream load 2pc target: ${beHttpAddress}, table: 
${table}, "
+                    + "txn operation: ${txnOperation}, user: ${authUser}")
+            def process = command.execute()
+            process.waitForOrKill(7200000)
+            def out = process.text.trim()
+            def err = process.errorStream.text.trim()
+            logger.info("stream load 2pc out: ${out}, err: ${err}")
+            return parseCurlResult(out, err)
+        }
+
+        def assertLoadFailed = { def result ->
+            assertEquals(200, result[0])
+            def json = parseJson(result[1])
+            assertTrue("stream load should fail, body=${result[1]}", 
json.Status != "Success")
+        }
+
+        def assertLoadSuccess = { def result ->
+            assertEquals(200, result[0])
+            def json = parseJson(result[1])
+            assertEquals("Success", json.Status)
+            assertEquals(3, json.NumberTotalRows)
+            assertEquals(3, json.NumberLoadedRows)
+            return json
+        }
+
+        try {
+            try_sql("DROP USER ${tableUser}")
+            try_sql("DROP USER ${dbUser}")
+            sql """DROP DATABASE IF EXISTS ${dbName}"""
+            sql """CREATE USER '${tableUser}' IDENTIFIED BY '${pwd}'"""
+            sql """CREATE USER '${dbUser}' IDENTIFIED BY '${pwd}'"""
+            def clusters = sql """SHOW CLUSTERS"""
+            assertTrue(!clusters.isEmpty())
+            sql """GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO 
${tableUser}"""
+            sql """GRANT USAGE_PRIV ON CLUSTER `${clusters[0][0]}` TO 
${dbUser}"""
+
+            sql """CREATE DATABASE ${dbName}"""
+            sql """
+                CREATE TABLE ${dbName}.${tableName} (
+                    id BIGINT,
+                    username VARCHAR(20)
+                )
+                DISTRIBUTED BY HASH(id) BUCKETS 1
+                PROPERTIES ("replication_num" = "1")
+            """
+            sql """
+                CREATE TABLE ${dbName}.${otherTableName} (
+                    id BIGINT,
+                    username VARCHAR(20)
+                )
+                DISTRIBUTED BY HASH(id) BUCKETS 1
+                PROPERTIES ("replication_num" = "1")
+            """
+
+            assertLoadFailed(streamLoad(tableUser, beHttpAddress, tableName, 
"on_denied_before_grant",
+                    false, []))
+
+            sql """GRANT LOAD_PRIV ON ${dbName}.${tableName} TO ${tableUser}"""
+
+            assertLoadSuccess(streamLoad(tableUser, beHttpAddress, tableName, 
"on_table_grant_be",
+                    false, []))
+            assertLoadSuccess(streamLoad(tableUser, feHttpAddress, tableName, 
"on_table_grant_fe",
+                    true, []))
+            def prepared = assertLoadSuccess(streamLoad(tableUser, 
beHttpAddress, tableName,
+                    "on_table_grant_2pc", false, ["two_phase_commit:true"]))
+            def committed = streamLoad2pc(tableUser, tableName, 
prepared.TxnId, "commit")
+            assertEquals(200, committed[0])
+            assertEquals("Success", parseJson(committed[1]).status)
+
+            assertLoadFailed(streamLoad(tableUser, beHttpAddress, 
otherTableName,
+                    "on_denied_other_table", false, []))
+
+            sql """GRANT LOAD_PRIV ON ${dbName}.* TO ${dbUser}"""
+            assertLoadSuccess(streamLoad(dbUser, beHttpAddress, 
otherTableName, "on_db_grant_be",
+                    false, []))
+
+            def tableCount = sql """SELECT COUNT(*) FROM 
${dbName}.${tableName}"""
+            assertEquals(9, tableCount[0][0] as int)
+            def otherTableCount = sql """SELECT COUNT(*) FROM 
${dbName}.${otherTableName}"""
+            assertEquals(3, otherTableCount[0][0] as int)
+        } finally {
+            try_sql """DROP DATABASE IF EXISTS ${dbName}"""
+            try_sql("DROP USER ${tableUser}")
+            try_sql("DROP USER ${dbUser}")
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to