-
Notifications
You must be signed in to change notification settings - Fork 271
Rename SUPPORTED_CATALOG_CONNECTION_TYPES #1959
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7febf6e
742b6c8
7d8f87b
3222567
0236466
c51edb7
9e79d08
fa45fde
2147b66
6087435
14076b5
d99408a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,10 @@ | |
package org.apache.polaris.core.config; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
@@ -41,6 +43,7 @@ public abstract class PolarisConfiguration<T> { | |
|
||
public final String key; | ||
public final String description; | ||
public final Set<String> legacyKeys; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this value used for property config value lookup? Sorry, if I missed it in the diff 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is used when registering a new configuration. Previously (before this change), we would go over existingConfigurations check if it matched any existingConfiguration's key. Now, we check if the new configuration (both key and legacyKeys) match either an existing key (line 61) or existing legacyKeys (line 65 - 76) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct... but we should also use it to find config values in case the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way I have implemented it, any configuration always has a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think I get what you mean. I am guessing you meant the aspect of reading in the properties. If so I will send a fix shortly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thx! To clarify: old config entries should keep working until we drop support for them (e.g. in 2.0). Let's add a WARN log message when we have to fall back to the "legacy" property name so that users are notified in runtime and are more aware of the need to migrating to the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I did not notice this before, but I think having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I will send out a follow up PR. |
||
public final T defaultValue; | ||
private final Optional<String> catalogConfigImpl; | ||
private final Optional<String> catalogConfigUnsafeImpl; | ||
|
@@ -55,9 +58,22 @@ public abstract class PolarisConfiguration<T> { | |
*/ | ||
private static void registerConfiguration(PolarisConfiguration<?> configuration) { | ||
for (PolarisConfiguration<?> existingConfiguration : allConfigurations) { | ||
if (existingConfiguration.key.equals(configuration.key)) { | ||
if (existingConfiguration.key.equals(configuration.key) | ||
|| configuration.legacyKeys.contains(existingConfiguration.key)) { | ||
throw new IllegalArgumentException( | ||
String.format("Config '%s' is already in use", existingConfiguration.key)); | ||
} else if (existingConfiguration.legacyKeys.contains(configuration.key)) { | ||
throw new IllegalArgumentException( | ||
String.format("Config '%s' is already in use", configuration.key)); | ||
} else if (!configuration.legacyKeys.isEmpty()) { | ||
Set<String> legacyKeys = new HashSet<>(existingConfiguration.legacyKeys); | ||
legacyKeys.retainAll(configuration.legacyKeys); | ||
if (!legacyKeys.isEmpty()) { | ||
throw new IllegalArgumentException( | ||
String.format( | ||
"Config '%s' is already in use", | ||
legacyKeys.stream().collect(Collectors.joining(",")))); | ||
} | ||
} else { | ||
var configs = | ||
Stream.of( | ||
|
@@ -86,18 +102,28 @@ public static List<PolarisConfiguration<?>> getAllConfigurations() { | |
@SuppressWarnings("unchecked") | ||
protected PolarisConfiguration( | ||
String key, | ||
Set<String> legacyKeys, | ||
String description, | ||
T defaultValue, | ||
Optional<String> catalogConfig, | ||
Optional<String> catalogConfigUnsafe) { | ||
this.key = key; | ||
this.legacyKeys = legacyKeys; | ||
this.description = description; | ||
this.defaultValue = defaultValue; | ||
this.catalogConfigImpl = catalogConfig; | ||
this.catalogConfigUnsafeImpl = catalogConfigUnsafe; | ||
this.typ = (Class<T>) defaultValue.getClass(); | ||
} | ||
|
||
public boolean hasLegacyKeys() { | ||
return !legacyKeys.isEmpty(); | ||
} | ||
|
||
public Set<String> legacyKeys() { | ||
return legacyKeys; | ||
} | ||
|
||
public boolean hasCatalogConfig() { | ||
return catalogConfigImpl.isPresent(); | ||
} | ||
|
@@ -126,6 +152,7 @@ T cast(Object value) { | |
|
||
public static class Builder<T> { | ||
private String key; | ||
private Set<String> legacyKeys = new HashSet<>(); | ||
private String description; | ||
private T defaultValue; | ||
private Optional<String> catalogConfig = Optional.empty(); | ||
|
@@ -136,6 +163,16 @@ public Builder<T> key(String key) { | |
return this; | ||
} | ||
|
||
public Builder<T> legacyKey(String legacyKey) { | ||
legacyKeys.add(legacyKey); | ||
return this; | ||
} | ||
|
||
public Builder<T> legacyKeys(Set<String> legacyKeys) { | ||
this.legacyKeys.addAll(legacyKeys); | ||
return this; | ||
} | ||
|
||
public Builder<T> description(String description) { | ||
this.description = description; | ||
return this; | ||
|
@@ -190,7 +227,7 @@ public FeatureConfiguration<T> buildFeatureConfiguration() { | |
validateOrThrow(); | ||
FeatureConfiguration<T> config = | ||
new FeatureConfiguration<>( | ||
key, description, defaultValue, catalogConfig, catalogConfigUnsafe); | ||
key, legacyKeys, description, defaultValue, catalogConfig, catalogConfigUnsafe); | ||
PolarisConfiguration.registerConfiguration(config); | ||
return config; | ||
} | ||
|
@@ -203,7 +240,7 @@ public BehaviorChangeConfiguration<T> buildBehaviorChangeConfiguration() { | |
} | ||
BehaviorChangeConfiguration<T> config = | ||
new BehaviorChangeConfiguration<>( | ||
key, description, defaultValue, catalogConfig, catalogConfigUnsafe); | ||
key, legacyKeys, description, defaultValue, catalogConfig, catalogConfigUnsafe); | ||
PolarisConfiguration.registerConfiguration(config); | ||
return config; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,7 +99,27 @@ public interface PolarisConfigurationStore { | |
*/ | ||
default <T> @Nonnull T getConfiguration( | ||
@Nonnull RealmContext realmContext, PolarisConfiguration<T> config) { | ||
T result = getConfiguration(realmContext, config.key, config.defaultValue); | ||
T result = getConfiguration(realmContext, config.key); | ||
|
||
if (result == null && config.hasLegacyKeys()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the key overrides the legacy keys? |
||
for (String legacyKey : config.legacyKeys()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will lead to nondeterministic behavior if two legacy keys for a feature have different values |
||
T legacyResult = getConfiguration(realmContext, legacyKey); | ||
if (legacyResult != null) { | ||
LOGGER.warn( | ||
"Legacy configuration key '{}' is in use. Please use '{}' instead.", | ||
legacyKey, | ||
config.key); | ||
if (result == null) { | ||
result = legacyResult; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (result == null) { | ||
result = config.defaultValue; | ||
} | ||
|
||
return tryCast(config, result); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* 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.polaris.core.config; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
public class PolarisConfigurationTest { | ||
|
||
@Test | ||
void testFeatureConfigurationWithLegacyKey() { | ||
FeatureConfiguration<Boolean> config = | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("FEATURE_CONFIG_WITH_LEGACY_KEY") | ||
.legacyKey("OLD_FEATURE_CONFIG_WITH_LEGACY_KEY") | ||
.description("Test configuration with deprecated key") | ||
.defaultValue(true) | ||
.buildFeatureConfiguration(); | ||
|
||
assertThat(config.hasLegacyKeys()).isTrue(); | ||
assertThat(config.legacyKeys().contains("OLD_FEATURE_CONFIG_WITH_LEGACY_KEY")).isTrue(); | ||
assertThat(config.key).isEqualTo("FEATURE_CONFIG_WITH_LEGACY_KEY"); | ||
assertThat(config.description).isEqualTo("Test configuration with deprecated key"); | ||
assertThat(config.defaultValue).isTrue(); | ||
} | ||
|
||
@Test | ||
void testBehaviorChangeConfigurationWithLegacyKey() { | ||
BehaviorChangeConfiguration<String> config = | ||
PolarisConfiguration.<String>builder() | ||
.key("BEHAVIOR_CONFIG_WITH_LEGACY_KEY") | ||
.legacyKey("OLD_BEHAVIOR_CONFIG_WITH_LEGACY_KEY") | ||
.description("Test behavior configuration with deprecated key") | ||
.defaultValue("test-value") | ||
.buildBehaviorChangeConfiguration(); | ||
|
||
assertThat(config.hasLegacyKeys()).isTrue(); | ||
assertThat(config.legacyKeys().contains("OLD_BEHAVIOR_CONFIG_WITH_LEGACY_KEY")).isTrue(); | ||
assertThat(config.key).isEqualTo("BEHAVIOR_CONFIG_WITH_LEGACY_KEY"); | ||
assertThat(config.defaultValue).isEqualTo("test-value"); | ||
} | ||
|
||
@Test | ||
void testDuplicateKeyValidation() { | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("DUPLICATE_KEY_TEST_1") | ||
.description("First configuration") | ||
.defaultValue(true) | ||
.buildFeatureConfiguration(); | ||
|
||
assertThatThrownBy( | ||
() -> | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("DUPLICATE_KEY_TEST_1") | ||
.description("Second configuration") | ||
.defaultValue(false) | ||
.buildFeatureConfiguration()) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("Config 'DUPLICATE_KEY_TEST_1' is already in use"); | ||
} | ||
|
||
@Test | ||
void testDuplicateLegacyKeysValidation() { | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("NEW_KEY_DEPRECATED_TEST_2") | ||
.legacyKey("LEGACY_KEY_TEST_2_1") | ||
.legacyKey("LEGACY_KEY_TEST_2_2") | ||
.legacyKey("LEGACY_KEY_TEST_2_3") | ||
.description("First configuration with deprecated key") | ||
.defaultValue(true) | ||
.buildFeatureConfiguration(); | ||
|
||
assertThatThrownBy( | ||
() -> | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("ANOTHER_NEW_KEY_2") | ||
.legacyKey("LEGACY_KEY_TEST_2_1") | ||
.legacyKey("LEGACY_KEY_TEST_2_3") | ||
.description("Second configuration with same deprecated key") | ||
.defaultValue(false) | ||
.buildFeatureConfiguration()) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("Config 'LEGACY_KEY_TEST_2_3,LEGACY_KEY_TEST_2_1' is already in use"); | ||
} | ||
|
||
@Test | ||
void testNewKeyMatchingExistingLegacyKeyValidation() { | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("ORIGINAL_KEY_3") | ||
.legacyKey("OLD_LEGACY_KEY_3") | ||
.description("First configuration with deprecated key") | ||
.defaultValue(true) | ||
.buildFeatureConfiguration(); | ||
|
||
assertThatThrownBy( | ||
() -> | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("OLD_LEGACY_KEY_3") | ||
.description("Configuration with key matching existing deprecated key") | ||
.defaultValue(false) | ||
.buildFeatureConfiguration()) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("Config 'OLD_LEGACY_KEY_3' is already in use"); | ||
} | ||
|
||
@Test | ||
void testLegacyKeyMatchingExistingKeyValidation() { | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("EXISTING_KEY_4") | ||
.description("First configuration") | ||
.defaultValue(true) | ||
.buildFeatureConfiguration(); | ||
|
||
assertThatThrownBy( | ||
() -> | ||
PolarisConfiguration.<Boolean>builder() | ||
.key("NEW_KEY_FOR_VALIDATION_4") | ||
.legacyKey("EXISTING_KEY_4") | ||
.legacyKey("EXISTING_KEY_4_1") | ||
.description("Configuration with deprecated key matching existing key") | ||
.defaultValue(false) | ||
.buildFeatureConfiguration()) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("Config 'EXISTING_KEY_4' is already in use"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can you have multiple
legacyKeys
but not multiplekeys
? It seems like maybe both should beSet
s that support multiple calls.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is my understanding: A PolarisConfiguration or a configuration for any quarkus app has a
name
(key
in PoalrisConfiguration) that is used to identify it. The key/name is the identity of the configuration. The point of adding support for legacyKeys was to enable identity changes wherein we can for certain time support deprecated keys. I don't see why we should have multiple identifying keys mapping to a single entity. If you ever need multiple identities, then the legacy keys do support the need but eventually each entity must converge to a single identity.Please let me know if I am missing something here.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just seems odd that we would support multiple invalid keys but not multiple valid keys. In my original comment here I wrote:
rather than
... with all but one marked for deprecation ...
because I had this in mind.I do agree that the situation where you'd have multiple valid configs seems contrived, but so does the situation where you'd have multiple invalid configs or really even one invalid config. So if we're overhauling the whole key system, I think it makes sense to go all the way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we support multiple invalid keys. We track multiple
legacy
keys.From a semantic perspective, a
PrimaryKey
in a RDBMS, akey
in a key-value store or a Java Map (or any other programming language) maps to a single row, value or entity. In all these examples, two keys never map to the same entity.What we have with the
legacyKeys
is a way to track historic versions of the key-value pair. In any DBMS or KV store that supports multi-versioning, it's pretty normal to have multiple entries (multiple versions) for a single key. When the primaryKey/key changes, the DBMS/KV store adds a tombstone for the old record and creates a new record with the new key. While changing keys in PolarisConfig, we are essentially creating a new key-value pair in itself but its nice to track the older key, hence the support for legacy keys. Essentially one could rewrite the entire PolarisConfig class with a Map where thekey
is the active key and the value is everything else. In which case, we'd track thelegacyKeys
as a part of the value.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The key is not a primary key though. There's no RDBMS or KV store storing these; they describe entries in a config file. It seems reasonable to expect that multiple different entries could be used to configure the same feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider, too, that the current code needs to deal with a situation where the key has value
true
but the legacyKey has valuefalse
. Or two legacy keys have different values. Clearly, the key(s) is not unique and the code needs to handle multiple keys for a given feature.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree with this:
You can have two configurations that store the same logical value and then let the specific module decide upon how to reconcile any conflicting values. But the config service should not register two configurations for the same feature.
Regarding the case where the
key
istrue
andlegacyKey
is false. I don't see how it is any different from a default value being defined say FeatureConfiguration.java, a value being defined in application.properties and then a JVM system property. In this case the system property takes precedence. Similarly thekey
takes precedence overlegacyKey
.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your current PR has this functionality... except only partially:
What is the rationale behind this asymmetry? If, as stated before, it is because the key is thought of as a primary key then this is just not correct. You can write a valid config that violates the "primary key constraint" by duplicating an entry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The case with 0 keys is not possible. We can't define a PolarisConfig without a key. That was always the case. Unless you'd prefer if we annotate the field with
@NonNull
in the constructor.The
registerConfiguration
function will actually disallow duplicate entries and throw anIllegalArgumentException
. That check is a part of the existing code, this PR is not change it. This PR goes further and checks that any existingkey
orlegacyKey
does not overlap with a new configuration that is being registered (again bothkey
andlegacyKeys
).I think we're looking at two entirely different aspects of a configuration.
FeatureConfiguration
orBehaviorChangeConfiguration
.The two are orthogonal to each other. We should impose constraints on how PolarisConfigs are declared. We can't however impose constraints on how the user defines these configs. If the user does have multiple definitions of the same config, then we apply the one with the highest priority and discard the rest. If indeed we should disallow multiple definitions, then any config that has a default value defined should essentially disallow any overwrites in application.properties. AFAIK, Quarkus doesn't do that and there isn't a way for the app to determine such a condition.