Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83078c9
Add continuations to struct type name tests
arnaud-lacurie Nov 13, 2025
64d5a83
Add more tests
arnaud-lacurie Nov 13, 2025
2e9890b
Fix struct type metadata preservation in query results and continuations
arnaud-lacurie Nov 14, 2025
7a808ab
Add some tests around named structs
arnaud-lacurie Nov 14, 2025
47333ac
Add validation for dynamic struct type compatibility
arnaud-lacurie Nov 16, 2025
b12aea9
Fix style and remove unnecessary sorting
arnaud-lacurie Nov 17, 2025
21eea73
Add additional tests to cover teamscale coverage gap
arnaud-lacurie Nov 17, 2025
c3f9c81
Add test coverage for ContinuedPhysicalQueryPlan.withExecutionContext
arnaud-lacurie Nov 17, 2025
f3307bf
Merge branch 'main' into struct_type_metadata_fix
arnaud-lacurie Nov 17, 2025
9435474
Extract type enrichment logic to TypeMetadataEnricher utility class
arnaud-lacurie Nov 17, 2025
a694dfc
Change TypeMetadataEnricher class to final
arnaud-lacurie Nov 17, 2025
5ce3daf
Remove deprecated `WITH CONTINUATION` syntax now that `EXECUTE CONTIN…
arnaud-lacurie Nov 18, 2025
cff2f13
Merge branch 'remove_with_continuation' into struct_type_metadata_fix
arnaud-lacurie Nov 20, 2025
6a66aaa
Merge branch 'struct_type_metadata_fix' of github.com:arnaud-lacurie/…
arnaud-lacurie Nov 20, 2025
5760a9d
Merge remote-tracking branch 'upstream/main' into struct_type_metadat…
arnaud-lacurie Nov 26, 2025
42bbe3c
Address teamscale issues
arnaud-lacurie Nov 27, 2025
9d1ba5e
Minor fix
arnaud-lacurie Nov 27, 2025
757b9b8
Merge branch 'struct_type_metadata_fix' into struct_type_metadata_fix_2
arnaud-lacurie Nov 27, 2025
c4073d3
Fix test
arnaud-lacurie Nov 27, 2025
0e48d8c
Fix checkstyle
arnaud-lacurie Nov 27, 2025
1ff2955
Remove unused method
arnaud-lacurie Nov 28, 2025
6af1e5c
Move getDataTypes into Expressions
arnaud-lacurie Nov 28, 2025
76e6542
Refactor semantic type capture to use StructType
arnaud-lacurie Nov 28, 2025
063bc47
Remove unused import
arnaud-lacurie Nov 28, 2025
4c122af
Remove unnecessary file
arnaud-lacurie Nov 28, 2025
93b5f87
Remove unused method
arnaud-lacurie Nov 28, 2025
0ddfe4f
Merge branch 'struct_type_metadata_fix' into struct_type_metadata_fix_2
arnaud-lacurie Nov 28, 2025
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 @@ -102,14 +102,18 @@ public final class RecordLayerSchemaTemplate implements SchemaTemplate {

private final boolean intermingleTables;

@Nonnull
private final Map<String, DataType.Named> auxiliaryTypes;

private RecordLayerSchemaTemplate(@Nonnull final String name,
@Nonnull final Set<RecordLayerTable> tables,
@Nonnull final Set<RecordLayerInvokedRoutine> invokedRoutines,
@Nonnull final Set<RecordLayerView> views,
int version,
boolean enableLongRows,
boolean storeRowVersions,
boolean intermingleTables) {
boolean intermingleTables,
@Nonnull final Map<String, DataType.Named> auxiliaryTypes) {
this.name = name;
this.tables = ImmutableSet.copyOf(tables);
this.invokedRoutines = ImmutableSet.copyOf(invokedRoutines);
Expand All @@ -118,6 +122,7 @@ private RecordLayerSchemaTemplate(@Nonnull final String name,
this.enableLongRows = enableLongRows;
this.storeRowVersions = storeRowVersions;
this.intermingleTables = intermingleTables;
this.auxiliaryTypes = ImmutableMap.copyOf(auxiliaryTypes);
this.metaDataSupplier = Suppliers.memoize(this::buildRecordMetadata);
this.tableIndexMappingSupplier = Suppliers.memoize(this::computeTableIndexMapping);
this.indexesSupplier = Suppliers.memoize(this::computeIndexes);
Expand All @@ -133,7 +138,8 @@ private RecordLayerSchemaTemplate(@Nonnull final String name,
boolean enableLongRows,
boolean storeRowVersions,
boolean intermingleTables,
@Nonnull final RecordMetaData cachedMetadata) {
@Nonnull final RecordMetaData cachedMetadata,
@Nonnull final Map<String, DataType.Named> auxiliaryTypes) {
this.name = name;
this.version = version;
this.tables = ImmutableSet.copyOf(tables);
Expand All @@ -142,6 +148,7 @@ private RecordLayerSchemaTemplate(@Nonnull final String name,
this.enableLongRows = enableLongRows;
this.storeRowVersions = storeRowVersions;
this.intermingleTables = intermingleTables;
this.auxiliaryTypes = ImmutableMap.copyOf(auxiliaryTypes);
this.metaDataSupplier = Suppliers.memoize(() -> cachedMetadata);
this.tableIndexMappingSupplier = Suppliers.memoize(this::computeTableIndexMapping);
this.indexesSupplier = Suppliers.memoize(this::computeIndexes);
Expand Down Expand Up @@ -625,10 +632,10 @@ public RecordLayerSchemaTemplate build() {

if (cachedMetadata != null) {
return new RecordLayerSchemaTemplate(name, new LinkedHashSet<>(tables.values()),
new LinkedHashSet<>(invokedRoutines.values()), new LinkedHashSet<>(views.values()), version, enableLongRows, storeRowVersions, intermingleTables, cachedMetadata);
new LinkedHashSet<>(invokedRoutines.values()), new LinkedHashSet<>(views.values()), version, enableLongRows, storeRowVersions, intermingleTables, cachedMetadata, auxiliaryTypes);
} else {
return new RecordLayerSchemaTemplate(name, new LinkedHashSet<>(tables.values()),
new LinkedHashSet<>(invokedRoutines.values()), new LinkedHashSet<>(views.values()), version, enableLongRows, storeRowVersions, intermingleTables);
new LinkedHashSet<>(invokedRoutines.values()), new LinkedHashSet<>(views.values()), version, enableLongRows, storeRowVersions, intermingleTables, auxiliaryTypes);
}
}

Expand Down Expand Up @@ -756,6 +763,7 @@ public Builder toBuilder() {
.setIntermingleTables(intermingleTables)
.addTables(getTables())
.addInvokedRoutines(getInvokedRoutines())
.addViews(getViews());
.addViews(getViews())
.addAuxiliaryTypes(auxiliaryTypes.values());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* StructTypeValidator.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2021-2025 Apple Inc. and the FoundationDB project authors
*
* 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.apple.foundationdb.relational.recordlayer.metadata;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.relational.api.exceptions.ErrorCode;
import com.apple.foundationdb.relational.api.metadata.DataType;
import com.apple.foundationdb.relational.util.Assert;

import javax.annotation.Nonnull;
import java.util.Locale;

/**
* Utility class for validating struct type compatibility.
* Provides centralized logic for comparing struct types, with support for
* ignoring nullability differences and recursive validation of nested structs.
*/
@API(API.Status.EXPERIMENTAL)
public final class StructTypeValidator {

private StructTypeValidator() {
// Utility class - prevent instantiation
}

/**
* Check if two struct types are compatible, ignoring nullability differences.
* Two struct types are considered compatible if:
* - They have the same number of fields
* - Each corresponding field has the same type code (ignoring nullability)
* - If recursive=true, nested struct fields are recursively validated
*
* @param expected The expected struct type
* @param provided The provided struct type
* @param recursive If true, recursively validate nested struct types
* @return true if the struct types are compatible, false otherwise
*/
public static boolean areStructTypesCompatible(@Nonnull DataType.StructType expected,
@Nonnull DataType.StructType provided,
boolean recursive) {
final var expectedFields = expected.getFields();
final var providedFields = provided.getFields();

// Check field count
if (!Integer.valueOf(expectedFields.size()).equals(providedFields.size())) {
return false;
}

// Check each field type
for (int i = 0; i < expectedFields.size(); i++) {
final var expectedFieldType = expectedFields.get(i).getType();
final var providedFieldType = providedFields.get(i).getType();

// Compare type codes (ignoring nullability)
if (!expectedFieldType.getCode().equals(providedFieldType.getCode())) {
return false;
}

// Recursively validate nested structs if requested
if (recursive && expectedFieldType instanceof DataType.StructType && providedFieldType instanceof DataType.StructType) {
if (!areStructTypesCompatible((DataType.StructType) expectedFieldType,
(DataType.StructType) providedFieldType,
true)) {
return false;
}
}
}

return true;
}

/**
* Validate that two struct types are compatible, throwing an exception if they are not.
* This is a wrapper around {@link #areStructTypesCompatible} that throws an exception
* with a detailed error message if the types are incompatible.
*
* @param expected The expected struct type
* @param provided The provided struct type
* @param structName The name of the struct being validated (for error messages)
* @param recursive If true, recursively validate nested struct types
* @throws com.apple.foundationdb.relational.api.exceptions.RelationalException if the types are incompatible
*/
public static void validateStructTypesCompatible(@Nonnull DataType.StructType expected,
@Nonnull DataType.StructType provided,
@Nonnull String structName,
boolean recursive) {
final var expectedFields = expected.getFields();
final var providedFields = provided.getFields();

// Check field count
if (!Integer.valueOf(expectedFields.size()).equals(providedFields.size())) {
Assert.failUnchecked(ErrorCode.CANNOT_CONVERT_TYPE,
String.format(Locale.ROOT,
"Struct type '%s' has incompatible signatures: expected %d fields but got %d fields",
structName, expectedFields.size(), providedFields.size()));
}

// Check each field type
for (int i = 0; i < expectedFields.size(); i++) {
final var expectedFieldType = expectedFields.get(i).getType();
final var providedFieldType = providedFields.get(i).getType();

// Compare type codes (ignoring nullability)
if (!expectedFieldType.getCode().equals(providedFieldType.getCode())) {
Assert.failUnchecked(ErrorCode.CANNOT_CONVERT_TYPE,
String.format(Locale.ROOT,
"Struct type '%s' has incompatible field at position %d: expected %s but got %s",
structName, i + 1, expectedFieldType.getCode(), providedFieldType.getCode()));
}

// Recursively validate nested structs if requested
if (recursive && expectedFieldType instanceof DataType.StructType && providedFieldType instanceof DataType.StructType) {
// StructType extends Named, so we can always get the name
final var expectedStructName = ((DataType.StructType) expectedFieldType).getName();
validateStructTypesCompatible((DataType.StructType) expectedFieldType,
(DataType.StructType) providedFieldType,
expectedStructName,
true);
}
}
}
}
Loading
Loading