Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2790,7 +2790,7 @@
"catalogDesc" : null,
"isInput" : false,
"setCurrentDatabaseIfMissing" : false,
"comment" : ""
"comment" : "Paimon"
} ],
"opType" : "QUERY",
"queryDescs" : [ ],
Expand All @@ -2811,7 +2811,7 @@
"catalogDesc" : null,
"isInput" : false,
"setCurrentDatabaseIfMissing" : false,
"comment" : ""
"comment" : "Paimon"
}, {
"fieldName" : "sourceTable",
"fieldExtractor" : "DataSourceV2RelationTableExtractor",
Expand All @@ -2821,7 +2821,7 @@
"catalogDesc" : null,
"isInput" : true,
"setCurrentDatabaseIfMissing" : false,
"comment" : ""
"comment" : "Paimon"
} ],
"opType" : "QUERY",
"queryDescs" : [ ],
Expand All @@ -2842,7 +2842,7 @@
"catalogDesc" : null,
"isInput" : false,
"setCurrentDatabaseIfMissing" : false,
"comment" : ""
"comment" : "Paimon"
} ],
"opType" : "QUERY",
"queryDescs" : [ ],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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.
*/

package org.apache.kyuubi.plugin.spark.authz

import org.apache.kyuubi.Utils
import org.apache.kyuubi.plugin.spark.authz.OperationType._
import org.apache.kyuubi.plugin.spark.authz.ranger.AccessType
import org.apache.kyuubi.tags.PaimonTest
import org.apache.kyuubi.util.AssertionUtils._

@PaimonTest
class PaimonCatalogPrivilegesBuilderSuite extends V2CommandsPrivilegesSuite {
override protected val catalogImpl: String = "hive"
override protected val sqlExtensions: String =
"org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions"
override protected def format = "paimon"

override protected val supportsUpdateTable = false
override protected val supportsMergeIntoTable = false
override protected val supportsDelete = false
override protected val supportsPartitionGrammar = true
override protected val supportsPartitionManagement = false
// Paimon maps TRUNCATE TABLE / INSERT OVERWRITE PARTITION to its own
// logical plans, so the generic V2 assertions don't hold here. The
// catalog e2e suite covers Paimon-specific truncate and dynamic
// partition overwrite behaviour.
override protected val supportsTruncateTable = false
override protected val supportsOverwritePartitionsDynamic = false

// Paimon's V2 catalog does not populate table owner on analyzed plans for
// CreateTable/ReplaceTable/DropTable/TruncateTable. Relax the inherited
// owner check so the generic V2 assertions still hold.
override protected def checkV2TableOwner(po: PrivilegeObject): Unit = {}

override def beforeAll(): Unit = {
spark.conf.set(
s"spark.sql.catalog.$catalogV2",
"org.apache.paimon.spark.SparkCatalog")
spark.conf.set(
s"spark.sql.catalog.$catalogV2.warehouse",
Utils.createTempDir("paimon-hadoop").toString)
super.beforeAll()
}

test("DeleteFromPaimonTable") {
val plan = sql(s"DELETE FROM $catalogTable WHERE key = 1 ").queryExecution.analyzed
val (inputs, outputs, operationType) = PrivilegesBuilder.build(plan, spark)
assert(operationType === QUERY)
assert(outputs.size === 1)
val po = outputs.head
assert(po.actionType === PrivilegeObjectActionType.UPDATE)
assert(po.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW)
assertEqualsIgnoreCase(namespace)(po.dbname)
assertEqualsIgnoreCase(catalogTableShort)(po.objectName)
assert(po.columns.isEmpty)
val accessType = AccessType(po, operationType, isInput = false)
assert(accessType === AccessType.UPDATE)
}

test("UpdatePaimonTable") {
val plan = sql(s"UPDATE $catalogTable SET value = 'b' WHERE key = 1 ").queryExecution.analyzed
val (inputs, outputs, operationType) = PrivilegesBuilder.build(plan, spark)
assert(operationType === QUERY)
assert(outputs.size === 1)
val po = outputs.head
assert(po.actionType === PrivilegeObjectActionType.UPDATE)
assert(po.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW)
assertEqualsIgnoreCase(namespace)(po.dbname)
assertEqualsIgnoreCase(catalogTableShort)(po.objectName)
assert(po.columns.isEmpty)
val accessType = AccessType(po, operationType, isInput = false)
assert(accessType === AccessType.UPDATE)
}

test("MergeIntoPaimonTable") {
val table = "MergeIntoPaimonTable"
withV2Table(table) { tableId =>
sql(s"CREATE TABLE $tableId (key int, value String) USING paimon " +
s"OPTIONS ('primary-key' = 'key')")
val plan = sql(s"MERGE INTO $tableId t " +
s"USING (SELECT * FROM $catalogTable) s " +
s"ON t.key = s.key " +
s"WHEN MATCHED THEN UPDATE SET t.value = s.value " +
s"WHEN NOT MATCHED THEN INSERT *").queryExecution.analyzed
val (inputs, outputs, operationType) = PrivilegesBuilder.build(plan, spark)
assert(operationType === QUERY)
assert(inputs.nonEmpty)
val sourcePo = inputs.head
assert(sourcePo.actionType === PrivilegeObjectActionType.OTHER)
assert(sourcePo.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW)
assertEqualsIgnoreCase(namespace)(sourcePo.dbname)
assertEqualsIgnoreCase(catalogTableShort)(sourcePo.objectName)

assert(outputs.size === 1)
val po = outputs.head
assert(po.actionType === PrivilegeObjectActionType.UPDATE)
assert(po.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW)
assertEqualsIgnoreCase(namespace)(po.dbname)
assertEqualsIgnoreCase(table)(po.objectName)
assert(po.columns.isEmpty)
val accessType = AccessType(po, operationType, isInput = false)
assert(accessType === AccessType.UPDATE)
}
}

test("PaimonCallProcedure") {
val table = "PaimonCallProcedure"
withV2Table(table) { tableId =>
sql(s"CREATE TABLE IF NOT EXISTS $tableId (key int, value String) USING paimon " +
s"OPTIONS ('primary-key' = 'key')")
sql(s"INSERT INTO $tableId VALUES (1, 'a'), (2, 'b'), (3, 'c')")

val plan = sql(s"CALL $catalogV2.sys.rollback (table => '$tableId', version => '1')")
.queryExecution.analyzed
val (inputs, outputs, operationType) = PrivilegesBuilder.build(plan, spark)
assert(operationType === ALTERTABLE_PROPERTIES)
assert(inputs.size === 0)
assert(outputs.size === 1)
val po = outputs.head
assert(po.actionType === PrivilegeObjectActionType.OTHER)
assert(po.privilegeObjectType === PrivilegeObjectType.TABLE_OR_VIEW)
assertEqualsIgnoreCase(namespace)(po.dbname)
assertEqualsIgnoreCase(table)(po.objectName)
val accessType = AccessType(po, operationType, isInput = false)
assert(accessType === AccessType.ALTER)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ abstract class V2CommandsPrivilegesSuite extends PrivilegesBuilderSuite {
protected val supportsDelete: Boolean
protected val supportsPartitionGrammar: Boolean
protected val supportsPartitionManagement: Boolean
// Some V2 catalogs (e.g. Paimon) translate TRUNCATE TABLE and dynamic
// partition overwrite into engine-specific logical plans whose
// operation type / target output differ from the generic Spark V2
// shape these tests assert against. Subclasses that need different
// coverage can flip these to false; the inherited tests are then
// assumed-skipped.
protected val supportsTruncateTable: Boolean = true
protected val supportsOverwritePartitionsDynamic: Boolean = true

val catalogV2 = "local"
val namespace = "catalog_ns"
Expand Down Expand Up @@ -288,6 +296,7 @@ abstract class V2CommandsPrivilegesSuite extends PrivilegesBuilderSuite {

test("OverwritePartitionsDynamic") {
assume(supportsPartitionGrammar)
assume(supportsOverwritePartitionsDynamic)

try {
sql("SET spark.sql.sources.partitionOverwriteMode=dynamic")
Expand Down Expand Up @@ -500,6 +509,7 @@ abstract class V2CommandsPrivilegesSuite extends PrivilegesBuilderSuite {
}

test("TruncateTable") {
assume(supportsTruncateTable)

val plan = executePlan(s"TRUNCATE TABLE $catalogTable").analyzed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ object PaimonCommands extends CommandSpecs[TableCommandSpec] {
val paimonFileStoreTableDesc = TableDesc(
"relation",
classOf[DataSourceV2RelationTableExtractor],
actionTypeDesc = Some(actionTypeDesc))
actionTypeDesc = Some(actionTypeDesc),
comment = "Paimon")
TableCommandSpec(cmd, Seq(paimonFileStoreTableDesc))
}

Expand All @@ -39,7 +40,8 @@ object PaimonCommands extends CommandSpecs[TableCommandSpec] {
val paimonFileStoreTableDesc = TableDesc(
"relation",
classOf[DataSourceV2RelationTableExtractor],
actionTypeDesc = Some(actionTypeDesc))
actionTypeDesc = Some(actionTypeDesc),
comment = "Paimon")
TableCommandSpec(cmd, Seq(paimonFileStoreTableDesc))
}

Expand All @@ -49,11 +51,13 @@ object PaimonCommands extends CommandSpecs[TableCommandSpec] {
val targetTableDesc = TableDesc(
"targetTable",
classOf[DataSourceV2RelationTableExtractor],
actionTypeDesc = Some(actionTypeDesc))
actionTypeDesc = Some(actionTypeDesc),
comment = "Paimon")
val sourceTableDesc = TableDesc(
"sourceTable",
classOf[DataSourceV2RelationTableExtractor],
isInput = true)
isInput = true,
comment = "Paimon")
TableCommandSpec(cmd, Seq(targetTableDesc, sourceTableDesc))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ class PaimonCatalogRangerSparkExtensionSuite extends RangerSparkExtensionSuite {
}
}

test("Producers") {
test("Procedures") {
if (isSparkV34OrGreater) {
withCleanTmpResources(Seq(
(s"$catalogV2.$namespace1.$table1", "table"))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.
*/

package org.apache.kyuubi.plugin.spark.authz.ranger.datamasking

import org.apache.spark.SparkConf
import org.scalactic.source
import org.scalatest.Tag

import org.apache.kyuubi.Utils
import org.apache.kyuubi.plugin.spark.authz.util.AuthZUtils._
import org.apache.kyuubi.tags.PaimonTest

@PaimonTest
class DataMaskingForPaimonSuite extends DataMaskingTestBase {
private def isSupportedVersion = isScalaV212 || isSparkV40OrGreater
override protected val sqlExtensions: String =
if (isSupportedVersion) "org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions"
else ""

override protected def extraSparkConf: SparkConf = {
super.extraSparkConf
.set("spark.sql.defaultCatalog", "testcat")
.set(
"spark.sql.catalog.testcat",
"org.apache.paimon.spark.SparkCatalog")
.set(
s"spark.sql.catalog.testcat.warehouse",
Utils.createTempDir("paimon-hadoop").toString)
}

override protected val catalogImpl: String = "in-memory"

override protected val supportPurge: Boolean = false

override protected def format: String = "USING paimon"

override protected def test(testName: String, testTags: Tag*)(
testFun: => Any)(implicit pos: source.Position): Unit = {
if (isSupportedVersion) {
super.test(testName, testTags: _*)(testFun)(pos)
}
}

override def beforeAll(): Unit = {
if (isSupportedVersion) {
super.beforeAll()
}
}

override def afterAll(): Unit = {
if (isSupportedVersion) {
super.afterAll()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.
*/

package org.apache.kyuubi.plugin.spark.authz.ranger.rowfiltering

import org.apache.spark.SparkConf
import org.scalactic.source
import org.scalatest.Tag

import org.apache.kyuubi.Utils
import org.apache.kyuubi.plugin.spark.authz.util.AuthZUtils._
import org.apache.kyuubi.tags.PaimonTest

@PaimonTest
class RowFilteringForPaimonSuite extends RowFilteringTestBase {
private def isSupportedVersion = isScalaV212 || isSparkV40OrGreater
override protected val sqlExtensions: String =
if (isSupportedVersion) "org.apache.paimon.spark.extensions.PaimonSparkSessionExtensions"
else ""

override protected val extraSparkConf: SparkConf = {
new SparkConf()
.set("spark.sql.defaultCatalog", "testcat")
.set(
"spark.sql.catalog.testcat",
"org.apache.paimon.spark.SparkCatalog")
.set(
s"spark.sql.catalog.testcat.warehouse",
Utils.createTempDir("paimon-hadoop").toString)
}

override protected val catalogImpl: String = "in-memory"

override protected val supportPurge: Boolean = false

override protected def format: String = "USING paimon"

override protected def test(testName: String, testTags: Tag*)(
testFun: => Any)(implicit pos: source.Position): Unit = {
if (isSupportedVersion) {
super.test(testName, testTags: _*)(testFun)(pos)
}
}

override def beforeAll(): Unit = {
if (isSupportedVersion) {
super.beforeAll()
}
}

override def afterAll(): Unit = {
if (isSupportedVersion) {
super.afterAll()
}
}
}
Loading