Skip to content
Open
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
109 changes: 109 additions & 0 deletions core/src/main/java/org/apache/iceberg/functions/Action.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.iceberg.functions;

import java.io.Serializable;
import java.util.Objects;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.util.SerializableFunction;

/**
* A column projection action from the ReadRestrictions spec.
*
* <p>{@link #bind(Type)} returns the masking {@link SerializableFunction} for a given column type;
* all bound functions return null for null input. Per spec all predefined actions preserve the
* input column type, so the bound function maps {@code T -> T}.
*
* @param <T> column value type
*/
public interface Action<T> extends Serializable {

String MASK_ALPHANUM = "mask-alphanum";
String MASK_TO_FIXED_VALUE = "mask-to-fixed-value";
String REPLACE_WITH_NULL = "replace-with-null";
String SHOW_FIRST_4 = "show-first-4";
String SHOW_LAST_4 = "show-last-4";
String TRUNCATE_TO_YEAR = "truncate-to-year";
String TRUNCATE_TO_MONTH = "truncate-to-month";
String SHA_256_GLOBAL = "sha-256-global";
String SHA_256_QUERY_LOCAL = "sha-256-query-local";
String APPLY_EXPRESSION = "apply-expression";

/** The action discriminator string as sent on the wire. */
String actionType();

/** The field id of the column this action applies to. */
int fieldId();

/**
* Returns a function that applies this action to values of the given {@link Type}.
*
* @throws IllegalArgumentException if the type is not supported by this action.
*/
default SerializableFunction<T, T> bind(Type type) {
throw new UnsupportedOperationException("bind is not implemented for " + getClass().getName());
}

/**
* Variant that accepts a per-query salt. Only {@link Sha256QueryLocal} uses the salt; other
* actions ignore it and delegate to {@link #bind(Type)}.
*/
default SerializableFunction<T, T> bind(Type type, byte[] salt) {
return bind(type);
}

/** Returns true if this action can be bound to the given {@link Type}. */
boolean canBind(Type type);

/** Base for all concrete actions; holds the field id. */
abstract class BaseAction<T> implements Action<T> {
private final int fieldId;

BaseAction(int fieldId) {
this.fieldId = fieldId;
}

@Override
public final int fieldId() {
return fieldId;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Action)) {
return false;
}
Action<?> other = (Action<?>) o;
return fieldId == other.fieldId() && actionType().equals(other.actionType());
}

@Override
public int hashCode() {
return Objects.hash(actionType(), fieldId);
}

@Override
public String toString() {
return actionType() + "(" + fieldId + ")";
}
}
}
41 changes: 41 additions & 0 deletions core/src/main/java/org/apache/iceberg/functions/Actions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.iceberg.functions;

import org.apache.iceberg.util.SerializableFunction;

/** Package-private helpers shared by {@link Action} implementations. */
final class Actions {

private Actions() {}

/**
* Base for masking functions where null input must pass through as null unchanged (spec: "For all
* actions, if the input column value is NULL, the output MUST be NULL."). Subclasses implement
* {@link #applyNonNull(Object)} and don't have to repeat the guard.
*/
abstract static class NullSafeFunction<S, T> implements SerializableFunction<S, T> {
@Override
public final T apply(S value) {
return value == null ? null : applyNonNull(value);
}

protected abstract T applyNonNull(S value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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.iceberg.functions;

import java.util.Objects;
import org.apache.iceberg.expressions.Expression;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.util.SerializableFunction;

/**
* Replaces the column value with the result of a server-provided Expression. Not supported by this
* client yet — Iceberg Expressions are currently boolean-only, so binding returns a function that
* throws on apply.
*/
public final class ApplyExpression extends Action.BaseAction<Object> {
private final Expression expression;

public ApplyExpression(int fieldId, Expression expression) {
super(fieldId);
Preconditions.checkArgument(expression != null, "Invalid expression: null");
this.expression = expression;
}

public Expression expression() {
return expression;
}

@Override
public String actionType() {
return APPLY_EXPRESSION;
}

@Override
public boolean canBind(Type type) {
return true;
}

@Override
public SerializableFunction<Object, Object> bind(Type type) {
return ApplyExpressionFn.INSTANCE;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ApplyExpression)) {
return false;
}
ApplyExpression other = (ApplyExpression) o;
return fieldId() == other.fieldId() && Objects.equals(expression, other.expression);
}

@Override
public int hashCode() {
return Objects.hash(actionType(), fieldId(), expression);
}

@Override
public String toString() {
return actionType() + "(" + fieldId() + ", " + expression + ")";
}

private static final class ApplyExpressionFn implements SerializableFunction<Object, Object> {
static final ApplyExpressionFn INSTANCE = new ApplyExpressionFn();

@Override
public Object apply(Object value) {
throw new UnsupportedOperationException(
"apply-expression column projection is not supported by this client "
+ "(Iceberg Expression is currently boolean-only)");
}
}
}
82 changes: 82 additions & 0 deletions core/src/main/java/org/apache/iceberg/functions/MaskAlphanum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.iceberg.functions;

import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.util.SerializableFunction;

/** Redacts every Unicode code point in a string per the mask-alphanum rules. */
public final class MaskAlphanum extends Action.BaseAction<String> {
public MaskAlphanum(int fieldId) {
super(fieldId);
}

@Override
public String actionType() {
return MASK_ALPHANUM;
}

@Override
public boolean canBind(Type type) {
return type.typeId() == Type.TypeID.STRING;
}

@Override
public SerializableFunction<String, String> bind(Type type) {
Preconditions.checkArgument(canBind(type), "mask-alphanum requires STRING type, got %s", type);
return MaskAlphanumFn.INSTANCE;
}

/**
* Maps a code point through the mask-alphanum rules; also used by {@link ShowFirst4} and {@link
* ShowLast4} on the code points outside their preserved windows:
*
* <ul>
* <li>ASCII digits (0-9) -&gt; {@code 'n'}
* <li>Structural punctuation kept as-is: {@code ( ) , . - @}
* <li>Everything else -&gt; {@code 'x'}
* </ul>
*/
static int maskCodePoint(int cp) {
if (cp >= 0x30 && cp <= 0x39) {
return 'n';
} else if (cp == '(' || cp == ')' || cp == ',' || cp == '.' || cp == '-' || cp == '@') {
return cp;
} else {
return 'x';
}
}

private static final class MaskAlphanumFn extends Actions.NullSafeFunction<String, String> {
static final MaskAlphanumFn INSTANCE = new MaskAlphanumFn();

@Override
protected String applyNonNull(String input) {
StringBuilder sb = new StringBuilder(input.length());
int offset = 0;
while (offset < input.length()) {
int cp = input.codePointAt(offset);
sb.appendCodePoint(maskCodePoint(cp));
offset += Character.charCount(cp);
}
return sb.toString();
}
}
}
Loading