Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

Commit

Permalink
[AtlasDB Proxy & DbKVS] Part 1: Long Identifier Support in Oracle (#5930
Browse files Browse the repository at this point in the history
)
  • Loading branch information
jeremyk-91 authored Mar 1, 2022
1 parent 5b5e1bb commit 2efb865
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,20 @@ private AtlasDbConstants() {

public static final String PRIMARY_KEY_CONSTRAINT_PREFIX = "pk_";

private static final int ORACLE_NAME_LENGTH_LIMIT = 30;
public static final int ORACLE_PRE_12_2_NAME_LENGTH_LIMIT = 30;
public static final int ORACLE_12_2_NAME_LENGTH_LIMIT = 128;

public static final int ATLASDB_ORACLE_TABLE_NAME_LIMIT =
AtlasDbConstants.ORACLE_NAME_LENGTH_LIMIT - PRIMARY_KEY_CONSTRAINT_PREFIX.length();
AtlasDbConstants.ORACLE_PRE_12_2_NAME_LENGTH_LIMIT - PRIMARY_KEY_CONSTRAINT_PREFIX.length();
public static final String ORACLE_NAME_MAPPING_TABLE = "atlasdb_table_names";
public static final String ORACLE_NAME_MAPPING_PK_CONSTRAINT =
PRIMARY_KEY_CONSTRAINT_PREFIX + ORACLE_NAME_MAPPING_TABLE;
public static final String ORACLE_OVERFLOW_SEQUENCE = "overflow_seq";
public static final int ORACLE_OVERFLOW_THRESHOLD = 2000;

public static final int ATLASDB_ORACLE_12_2_TABLE_NAME_LIMIT =
AtlasDbConstants.ORACLE_12_2_NAME_LENGTH_LIMIT - PRIMARY_KEY_CONSTRAINT_PREFIX.length();

public static final String NAMESPACE_PREFIX = "_n_";
public static final String NAMESPACE_SHORT_COLUMN_NAME = "s";
public static final byte[] NAMESPACE_SHORT_COLUMN_BYTES = PtBytes.toBytes(NAMESPACE_SHORT_COLUMN_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
Expand All @@ -26,6 +27,7 @@
import com.palantir.db.oracle.JdbcHandler;
import com.palantir.db.oracle.NativeOracleJdbcHandler;
import com.palantir.logsafe.Preconditions;
import com.palantir.logsafe.SafeArg;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Supplier;
Expand Down Expand Up @@ -80,6 +82,18 @@ public long compactionConnectionTimeout() {
return Duration.ofHours(10).toMillis();
}

@Value.Default
public boolean longIdentifierNamesSupported() {
return false;
}

@Value.Derived
public OracleIdentifierLengthLimits identifierLengthLimits() {
return longIdentifierNamesSupported()
? OracleIdentifierLengthLimitOptions.ORACLE_12_2
: OracleIdentifierLengthLimitOptions.LEGACY_PRE_ORACLE_12_2;
}

@Value.Default
@Override
public String tablePrefix() {
Expand All @@ -95,9 +109,15 @@ public TableReference metadataTable() {
@Value.Default
@JsonIgnore
public boolean useTableMapping() {
return true;
return forceTableMapping().orElse(true);
}

/**
* This should only be used for AtlasDB Proxy in specialized contexts.
*/
@JsonProperty("forceTableMappingIAmOnThePersistenceTeamAndKnowWhatIAmDoing")
public abstract Optional<Boolean> forceTableMapping();

@Override
public final String type() {
return TYPE;
Expand All @@ -109,18 +129,30 @@ protected final void checkOracleConfig() {
Preconditions.checkState(!tablePrefix().isEmpty(), "Oracle 'tablePrefix' must not be an empty string.");
Preconditions.checkState(!tablePrefix().startsWith("_"), "Oracle 'tablePrefix' cannot begin with underscore.");
Preconditions.checkState(tablePrefix().endsWith("_"), "Oracle 'tablePrefix' must end with an underscore.");
com.google.common.base.Preconditions.checkState(
tablePrefix().length() <= AtlasDbConstants.MAX_TABLE_PREFIX_LENGTH,
"Oracle 'tablePrefix' cannot be more than %s characters long.",
AtlasDbConstants.MAX_TABLE_PREFIX_LENGTH);
Preconditions.checkState(
!overflowTablePrefix().startsWith("_"), "Oracle 'overflowTablePrefix' cannot begin with underscore.");
Preconditions.checkState(
overflowTablePrefix().endsWith("_"), "Oracle 'overflowTablePrefix' must end with an underscore.");
com.google.common.base.Preconditions.checkState(
overflowTablePrefix().length() <= AtlasDbConstants.MAX_OVERFLOW_TABLE_PREFIX_LENGTH,
"Oracle 'overflowTablePrefix' cannot be more than %s characters long.",
AtlasDbConstants.MAX_OVERFLOW_TABLE_PREFIX_LENGTH);

checkTablePrefixLengthLimits();

Preconditions.checkState(
!(useTableMapping() && longIdentifierNamesSupported()),
"The table mapper does not support long identifier names yet. Please contact the AtlasDB team if you "
+ "wish to use these features in conjunction.");
}

private void checkTablePrefixLengthLimits() {
Preconditions.checkState(
tablePrefix().length() <= identifierLengthLimits().tablePrefixLengthLimit(),
"Oracle 'tablePrefix' exceeds the length limit.",
SafeArg.of("tablePrefixLengthLimit", identifierLengthLimits().tablePrefixLengthLimit()));
Preconditions.checkState(
overflowTablePrefix().length() <= identifierLengthLimits().overflowTablePrefixLengthLimit(),
"Oracle 'overflowTablePrefix' exceeds the length limit.",
SafeArg.of(
"overflowTablePrefixLengthLimit",
identifierLengthLimits().overflowTablePrefixLengthLimit()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* (c) Copyright 2022 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.atlasdb.keyvalue.dbkvs;

import com.palantir.atlasdb.AtlasDbConstants;

public final class OracleIdentifierLengthLimitOptions {
// This sequencing of dependencies may look strange, but is necessary to avoid breaking back-compat with users of
// the Oracle constants in AtlasDbConstants.
static final OracleIdentifierLengthLimits LEGACY_PRE_ORACLE_12_2 = ImmutableOracleIdentifierLengthLimits.builder()
.identifierLengthLimit(AtlasDbConstants.ORACLE_PRE_12_2_NAME_LENGTH_LIMIT)
.tablePrefixLengthLimit(AtlasDbConstants.MAX_TABLE_PREFIX_LENGTH)
.overflowTablePrefixLengthLimit(AtlasDbConstants.MAX_OVERFLOW_TABLE_PREFIX_LENGTH)
.build();

// In AtlasDB-Proxy, a user's physical namespace can be at most 48 characters.
// We want to have a bit of scope to add a bit more tracking data, hence 48 + 8
static final OracleIdentifierLengthLimits ORACLE_12_2 = ImmutableOracleIdentifierLengthLimits.builder()
.identifierLengthLimit(AtlasDbConstants.ORACLE_12_2_NAME_LENGTH_LIMIT)
.tablePrefixLengthLimit(48 + 8)
.overflowTablePrefixLengthLimit(48 + 8)
.build();

private OracleIdentifierLengthLimitOptions() {
// constants
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* (c) Copyright 2022 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.atlasdb.keyvalue.dbkvs;

import com.palantir.atlasdb.AtlasDbConstants;
import com.palantir.logsafe.Preconditions;
import com.palantir.logsafe.SafeArg;
import org.immutables.value.Value;

/**
* Tracks length limits on tables and identifier names within Oracle.
*/
@Value.Immutable
public interface OracleIdentifierLengthLimits {
int identifierLengthLimit();

int tablePrefixLengthLimit();

int overflowTablePrefixLengthLimit();

@Value.Derived
default int tableNameLengthLimit() {
return identifierLengthLimit() - AtlasDbConstants.PRIMARY_KEY_CONSTRAINT_PREFIX.length();
}

@Value.Check
default void check() {
Preconditions.checkState(
tablePrefixLengthLimit() < tableNameLengthLimit(),
"Table prefix length limit must be shorter than the table name length limit",
SafeArg.of("tablePrefixLengthLimit", tablePrefixLengthLimit()),
SafeArg.of("tableNameLengthLimit", tableNameLengthLimit()));
Preconditions.checkState(
overflowTablePrefixLengthLimit() < tableNameLengthLimit(),
"Overflow table prefix length limit must be shorter than the table name length limit",
SafeArg.of("overflowTablePrefixLengthLimit", overflowTablePrefixLengthLimit()),
SafeArg.of("tableNameLengthLimit", tableNameLengthLimit()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* (c) Copyright 2022 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.atlasdb.keyvalue.dbkvs;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.google.common.collect.ImmutableList;
import com.palantir.atlasdb.keyvalue.dbkvs.impl.OverflowMigrationState;
import com.palantir.logsafe.exceptions.SafeIllegalStateException;
import java.util.Collections;
import java.util.List;
import org.junit.Test;

public class OracleDdlConfigTest {
private static final String ACCEPTED_PREFIX = "a_";
private static final String ACCEPTED_OVERFLOW_PREFIX = "ao_";

@Test
public void standardTableAndOverflowPrefixesAreAccepted() {
assertThat(createLegacyCompatibleOracleConfigWithPrefixes(ACCEPTED_PREFIX, ACCEPTED_OVERFLOW_PREFIX))
.satisfies(config -> {
assertThat(config.tablePrefix()).isEqualTo(ACCEPTED_PREFIX);
assertThat(config.overflowTablePrefix()).isEqualTo(ACCEPTED_OVERFLOW_PREFIX);
});
}

@Test
public void tablePrefixesMustEndWithUnderscore() {
assertThatThrownBy(() -> createLegacyCompatibleOracleConfigWithPrefixes("a", ACCEPTED_OVERFLOW_PREFIX))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'tablePrefix' must end with an underscore");
}

@Test
public void tablePrefixesMustNotBeginWithUnderscore() {
assertThatThrownBy(() -> createLegacyCompatibleOracleConfigWithPrefixes("_t", ACCEPTED_OVERFLOW_PREFIX))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'tablePrefix' cannot begin with underscore");
}

@Test
public void overflowTablePrefixesMustEndWithUnderscore() {
assertThatThrownBy(() -> createLegacyCompatibleOracleConfigWithPrefixes(ACCEPTED_PREFIX, "ao"))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'overflowTablePrefix' must end with an underscore");
}

@Test
public void overflowTablePrefixesMustNotBeginWithUnderscore() {
assertThatThrownBy(() -> createLegacyCompatibleOracleConfigWithPrefixes(ACCEPTED_PREFIX, "_p"))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'overflowTablePrefix' cannot begin with underscore");
}

@Test
public void overflowPrefixHasMaximumLengthSix() {
assertThatCode(() ->
createLegacyCompatibleOracleConfigWithPrefixes(getPrefixWithLength(7), getPrefixWithLength(6)))
.doesNotThrowAnyException();
assertThatThrownBy(() ->
createLegacyCompatibleOracleConfigWithPrefixes(getPrefixWithLength(7), getPrefixWithLength(7)))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'overflowTablePrefix' exceeds the length limit")
.hasMessageContaining("6");
}

@Test
public void tablePrefixHasMaximumLengthSeven() {
assertThatThrownBy(() ->
createLegacyCompatibleOracleConfigWithPrefixes(getPrefixWithLength(8), getPrefixWithLength(6)))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'tablePrefix' exceeds the length limit")
.hasMessageContaining("7");
}

@Test
public void longPrefixesAllowedIfConfigSpecificallyAllowsThem() {
assertThatCode(() -> createLongNameSupportingOracleConfigWithPrefixes(
getPrefixWithLength(42), getPrefixWithLength(41)))
.doesNotThrowAnyException();
assertThatCode(() -> createLongNameSupportingOracleConfigWithPrefixes(
getPrefixWithLength(41), getPrefixWithLength(42)))
.doesNotThrowAnyException();
}

@Test
@SuppressWarnings("ResultOfMethodCallIgnored") // We're interested in whether this throws an exception or not!
public void tableMappingAndLongNameSupportNotAllowedTogether() {
assertThatThrownBy(() -> ImmutableOracleDdlConfig.builder()
.tablePrefix(getPrefixWithLength(5))
.overflowTablePrefix(getPrefixWithLength(4))
.overflowMigrationState(OverflowMigrationState.FINISHED)
.longIdentifierNamesSupported(true)
.useTableMapping(true)
.build())
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("The table mapper does not support long identifier names");
}

@Test
public void excessivelyLongPrefixesStillDisallowedEvenWithLongNameSupport() {
assertThatThrownBy(() -> createLongNameSupportingOracleConfigWithPrefixes(
getPrefixWithLength(57), getPrefixWithLength(14)))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'tablePrefix' exceeds the length limit")
.hasMessageContaining("56");
assertThatThrownBy(() -> createLongNameSupportingOracleConfigWithPrefixes(
getPrefixWithLength(22), getPrefixWithLength(57)))
.isInstanceOf(SafeIllegalStateException.class)
.hasMessageContaining("'overflowTablePrefix' exceeds the length limit")
.hasMessageContaining("56");
}

@Test
public void tableMappingIsByDefaultOn() {
assertThat(ImmutableOracleDdlConfig.builder()
.overflowMigrationState(OverflowMigrationState.FINISHED)
.build()
.useTableMapping())
.isTrue();
}

@Test
public void canSetTableMappingExplicitly() {
List<Boolean> values = ImmutableList.of(false, true);
for (boolean value : values) {
assertThat(ImmutableOracleDdlConfig.builder()
.overflowMigrationState(OverflowMigrationState.FINISHED)
.forceTableMapping(value)
.build()
.useTableMapping())
.isEqualTo(value);
}
}

private static OracleDdlConfig createLegacyCompatibleOracleConfigWithPrefixes(
String tablePrefix, String overflowTablePrefix) {
return createOracleDdlConfig(tablePrefix, overflowTablePrefix, false);
}

private static OracleDdlConfig createLongNameSupportingOracleConfigWithPrefixes(
String tablePrefix, String overflowTablePrefix) {
return createOracleDdlConfig(tablePrefix, overflowTablePrefix, true);
}

private static ImmutableOracleDdlConfig createOracleDdlConfig(
String tablePrefix, String overflowTablePrefix, boolean longIdentifierNamesSupported) {
return ImmutableOracleDdlConfig.builder()
.tablePrefix(tablePrefix)
.overflowTablePrefix(overflowTablePrefix)
.overflowMigrationState(OverflowMigrationState.FINISHED)
.longIdentifierNamesSupported(longIdentifierNamesSupported)
.useTableMapping(false)
.build();
}

private static String getPrefixWithLength(int length) {
return String.join("", Collections.nCopies(length - 1, "a")) + "_";
}
}
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-5930.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: improvement
improvement:
description: Support for long identifiers in Oracle KVS may be configured. This
is only allowed for users that don't use table mapping.
links:
- https://github.com/palantir/atlasdb/pull/5930

0 comments on commit 2efb865

Please sign in to comment.