Skip to content

Commit 0f494ca

Browse files
authored
Merge pull request #1505 from Kotlin/df-schema-matches
`DataFrameSchema.equals()` including order and `CompareResult.Matches`
2 parents 2e2abad + f1bc489 commit 0f494ca

File tree

12 files changed

+199
-40
lines changed

12 files changed

+199
-40
lines changed

core/api/core.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6783,11 +6783,14 @@ public final class org/jetbrains/kotlinx/dataframe/schema/CompareResult : java/l
67836783
public static final field Equals Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67846784
public static final field IsDerived Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67856785
public static final field IsSuper Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
6786+
public static final field Matches Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67866787
public static final field None Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67876788
public final fun combine (Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;)Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67886789
public static fun getEntries ()Lkotlin/enums/EnumEntries;
67896790
public final fun isEqual ()Z
67906791
public final fun isSuperOrEqual ()Z
6792+
public final fun isSuperOrMatches ()Z
6793+
public final fun matches ()Z
67916794
public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67926795
public static fun values ()[Lorg/jetbrains/kotlinx/dataframe/schema/CompareResult;
67936796
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/CodeGeneratorImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema
4242
private fun renderNullability(nullable: Boolean) = if (nullable) "?" else ""
4343

4444
internal fun Iterable<Marker>.filterRequiredForSchema(schema: DataFrameSchema) =
45-
filter { it.isOpen && it.schema.compare(schema, ComparisonMode.STRICT_FOR_NESTED_SCHEMAS).isSuperOrEqual() }
45+
filter { it.isOpen && it.schema.compare(schema, ComparisonMode.STRICT_FOR_NESTED_SCHEMAS).isSuperOrMatches() }
4646

4747
internal val charsToQuote = """[ `(){}\[\].<>'"/|\\!?@:;%^&*#$-]""".toRegex()
4848

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/ReplCodeGeneratorImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal class ReplCodeGeneratorImpl : ReplCodeGenerator {
6363
if (currentMarker.isOpen) {
6464
val columnSchema = currentMarker.schema
6565
// for mutable properties we do strong typing only at the first processing, after that we allow its type to be more general than actual dataframe type
66-
if (wasProcessedBefore || columnSchema == targetSchema) {
66+
if (wasProcessedBefore || columnSchema.compare(targetSchema).matches()) {
6767
// property scheme is valid for current dataframe, but we should also check that all compatible open markers are implemented by it
6868
val requiredBaseMarkers = registeredMarkers.values.filterRequiredForSchema(columnSchema)
6969
if (requiredBaseMarkers.any() && requiredBaseMarkers.all { currentMarker.implements(it) }) {

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/SchemaProcessorImpl.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ internal class SchemaProcessorImpl(
2323
override val generatedMarkers = mutableListOf<Marker>()
2424

2525
private fun DataFrameSchema.getAllSuperMarkers() =
26-
registeredMarkers
27-
.filter { it.isOpen && it.schema.compare(this, ComparisonMode.STRICT_FOR_NESTED_SCHEMAS).isSuperOrEqual() }
26+
registeredMarkers.filter {
27+
it.isOpen && it.schema.compare(this, ComparisonMode.STRICT_FOR_NESTED_SCHEMAS).isSuperOrMatches()
28+
}
2829

2930
private fun List<Marker>.onlyLeafs(): List<Marker> {
3031
val skip = flatMap { it.allSuperMarkers.keys }.toSet()
@@ -81,8 +82,6 @@ internal class SchemaProcessorImpl(
8182
columnSchema.nullable,
8283
renderAsList = true,
8384
)
84-
85-
else -> throw NotImplementedError()
8685
}
8786

8887
return schema.columns.asIterable().sortedBy { it.key }.flatMapIndexed { index, column ->
@@ -159,7 +158,7 @@ internal class SchemaProcessorImpl(
159158
val markerName: String
160159
val required = schema.getRequiredMarkers()
161160
val existingMarker = registeredMarkers.firstOrNull {
162-
(!isOpen || it.isOpen) && it.schema == schema && it.implementsAll(required)
161+
(!isOpen || it.isOpen) && it.schema.compare(schema).matches() && it.implementsAll(required)
163162
}
164163
if (existingMarker != null) {
165164
return existingMarker

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/schema/DataFrameSchemaImpl.kt

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,23 @@ package org.jetbrains.kotlinx.dataframe.impl.schema
33
import org.jetbrains.kotlinx.dataframe.impl.renderType
44
import org.jetbrains.kotlinx.dataframe.schema.ColumnSchema
55
import org.jetbrains.kotlinx.dataframe.schema.CompareResult
6-
import org.jetbrains.kotlinx.dataframe.schema.CompareResult.Equals
76
import org.jetbrains.kotlinx.dataframe.schema.CompareResult.IsDerived
87
import org.jetbrains.kotlinx.dataframe.schema.CompareResult.IsSuper
8+
import org.jetbrains.kotlinx.dataframe.schema.CompareResult.Matches
99
import org.jetbrains.kotlinx.dataframe.schema.CompareResult.None
1010
import org.jetbrains.kotlinx.dataframe.schema.ComparisonMode
1111
import org.jetbrains.kotlinx.dataframe.schema.ComparisonMode.STRICT
1212
import org.jetbrains.kotlinx.dataframe.schema.ComparisonMode.STRICT_FOR_NESTED_SCHEMAS
1313
import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema
1414
import org.jetbrains.kotlinx.dataframe.schema.plus
15-
import kotlin.collections.forEach
1615

1716
public class DataFrameSchemaImpl(override val columns: Map<String, ColumnSchema>) : DataFrameSchema {
1817

1918
override fun compare(other: DataFrameSchema, comparisonMode: ComparisonMode): CompareResult {
2019
require(other is DataFrameSchemaImpl)
21-
if (this === other) return Equals
20+
if (this === other) return Matches
2221

23-
var result: CompareResult = Equals
22+
var result: CompareResult = Matches
2423

2524
// check for each column in this schema if there is a column with the same name in the other schema
2625
// - if so, check those schemas for equality, taking comparisonMode into account
@@ -54,11 +53,47 @@ public class DataFrameSchemaImpl(override val columns: Map<String, ColumnSchema>
5453
return result
5554
}
5655

57-
override fun equals(other: Any?): Boolean = other is DataFrameSchema && this.compare(other).isEqual()
56+
/**
57+
* Returns `true` if, and only if,
58+
* [this schema][this] has the same columns **in the same order** as the [other schema][other].
59+
* The types must also match exactly.
60+
*
61+
* Use [compare][DataFrameSchema.compare] it the order does not matter and
62+
* for other comparison options.
63+
*
64+
* @see [DataFrameSchema.compare]
65+
* @see [CompareResult.matches]
66+
*/
67+
override fun equals(other: Any?): Boolean {
68+
if (this === other) return true
69+
if (other !is DataFrameSchema) return false
70+
if (this.compare(other) != Matches) return false
71+
if (columns.keys.toList() != other.columns.keys.toList()) return false
72+
73+
for ((name, col) in columns) {
74+
val other = other.columns[name]!!
75+
when (col) {
76+
is ColumnSchema.Group -> {
77+
other as ColumnSchema.Group // safe to cast because of compare
78+
if (col.schema != other.schema) return false
79+
}
80+
81+
is ColumnSchema.Frame -> {
82+
other as ColumnSchema.Frame // safe to cast because of compare
83+
if (col.schema != other.schema) return false
84+
}
85+
86+
// already checked by compare
87+
is ColumnSchema.Value -> Unit
88+
}
89+
}
90+
91+
return true
92+
}
5893

5994
override fun toString(): String = render()
6095

61-
override fun hashCode(): Int = columns.hashCode()
96+
override fun hashCode(): Int = columns.toList().hashCode()
6297
}
6398

6499
internal fun DataFrameSchemaImpl.render(): String {

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/schema/ColumnSchema.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public sealed class ColumnSchema {
4343

4444
public fun compare(other: Value, comparisonMode: ComparisonMode = LENIENT): CompareResult =
4545
when {
46-
type == other.type -> CompareResult.Equals
46+
type == other.type -> CompareResult.Matches
4747
comparisonMode == STRICT -> CompareResult.None
4848
type.isSubtypeOf(other.type) -> CompareResult.IsDerived
4949
type.isSupertypeOf(other.type) -> CompareResult.IsSuper
@@ -80,7 +80,7 @@ public sealed class ColumnSchema {
8080
) + CompareResult.compareNullability(thisIsNullable = nullable, otherIsNullable = other.nullable)
8181
}
8282

83-
/** Checks equality just on kind, type, or schema. */
83+
/** Checks equality by kind, type, or schema. TODO was matching, check if == works. */
8484
override fun equals(other: Any?): Boolean {
8585
val otherType = other as? ColumnSchema ?: return false
8686
if (otherType.kind != kind) return false
@@ -94,7 +94,7 @@ public sealed class ColumnSchema {
9494

9595
public fun compare(other: ColumnSchema, comparisonMode: ComparisonMode = LENIENT): CompareResult {
9696
if (kind != other.kind) return CompareResult.None
97-
if (this === other) return CompareResult.Equals
97+
if (this === other) return CompareResult.Matches
9898
return when (this) {
9999
is Value -> compare(other as Value, comparisonMode)
100100
is Group -> compare(other as Group, comparisonMode)

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/schema/CompareResult.kt

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,93 @@
11
package org.jetbrains.kotlinx.dataframe.schema
22

3+
import org.jetbrains.kotlinx.dataframe.util.COMPARE_RESULT_EQUALS
4+
35
public enum class CompareResult {
6+
7+
// TODO can be reintroduced at 1.1 to support "equals exactly" as CompareResult
8+
@Deprecated(
9+
message = COMPARE_RESULT_EQUALS,
10+
replaceWith = ReplaceWith("Matches", "org.jetbrains.kotlinx.dataframe.schema.CompareResult.Matches"),
11+
level = DeprecationLevel.ERROR,
12+
)
413
Equals,
14+
15+
/**
16+
* If the other schema has columns this has not,
17+
* or their columns have a more specific type than in this schema,
18+
* this is considered "super".
19+
*/
520
IsSuper,
21+
22+
/**
23+
* If this schema has columns the other has not,
24+
* or their columns have a less specific type than in this schema,
25+
* this is considered "derived".
26+
*/
627
IsDerived,
28+
29+
/** The two schemas are incomparable. */
730
None,
31+
32+
/**
33+
* Both schemas contain exactly the same columns, column groups, and frame columns,
34+
* though their order might still be different.
35+
*/
36+
Matches,
837
;
938

10-
public fun isSuperOrEqual(): Boolean = this == Equals || this == IsSuper
39+
/**
40+
* True if
41+
* - both schemas contain exactly the same columns, column groups, and frame columns
42+
* - or the other schema has columns this has not
43+
* - or their columns have a more specific type than in this schema
44+
*
45+
* The column order might still be different.
46+
*/
47+
public fun isSuperOrMatches(): Boolean = this == Matches || this == IsSuper
48+
49+
@Deprecated(
50+
message = COMPARE_RESULT_EQUALS,
51+
replaceWith = ReplaceWith("isSuperOrMatches()"),
52+
level = DeprecationLevel.ERROR,
53+
)
54+
public fun isSuperOrEqual(): Boolean = isSuperOrMatches()
55+
56+
/**
57+
* True if both schemas contain exactly the same columns, column groups, and frame columns,
58+
* though their order might still be different.
59+
*/
60+
public fun matches(): Boolean = this == Matches
61+
62+
@Deprecated(
63+
message = COMPARE_RESULT_EQUALS,
64+
replaceWith = ReplaceWith("matches()"),
65+
level = DeprecationLevel.ERROR,
66+
)
67+
public fun isEqual(): Boolean = this.matches()
1168

12-
public fun isEqual(): Boolean = this == Equals
69+
/**
70+
* Temporary helper method to avoid breaking changes.
71+
*/
72+
@Deprecated(
73+
message = COMPARE_RESULT_EQUALS,
74+
level = DeprecationLevel.WARNING,
75+
)
76+
private fun isDeprecatedEquals(): Boolean = this != IsSuper && this != IsDerived && this != None && this != Matches
1377

1478
public fun combine(other: CompareResult): CompareResult =
1579
when (this) {
16-
Equals -> other
80+
Matches -> other
1781
None -> None
18-
IsDerived -> if (other == Equals || other == IsDerived) this else None
19-
IsSuper -> if (other == Equals || other == IsSuper) this else None
82+
IsDerived -> if (other == Matches || other == IsDerived || other.isDeprecatedEquals()) this else None
83+
IsSuper -> if (other == Matches || other == IsSuper || other.isDeprecatedEquals()) this else None
84+
else -> other
2085
}
2186

2287
public companion object {
2388
public fun compareNullability(thisIsNullable: Boolean, otherIsNullable: Boolean): CompareResult =
2489
when {
25-
thisIsNullable == otherIsNullable -> Equals
90+
thisIsNullable == otherIsNullable -> Matches
2691
thisIsNullable -> IsSuper
2792
else -> IsDerived
2893
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/schema/DataFrameSchema.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ public interface DataFrameSchema {
66
public val columns: Map<String, ColumnSchema>
77

88
/**
9+
* Compares this schema with [other] schema.
10+
*
911
* @param comparisonMode The [mode][ComparisonMode] to compare the schema's by.
1012
* By default, generated markers for leafs aren't used as supertypes: `@DataSchema(isOpen = false)`
1113
* Setting [comparisonMode] to [ComparisonMode.STRICT_FOR_NESTED_SCHEMAS] takes this into account
1214
* for internal codegen logic.
15+
*
16+
* @return a [CompareResult] that indicates whether this schema compared to [other] is
17+
* [matching][CompareResult.Matches] (neglecting order),
18+
* [derived][CompareResult.IsDerived], [superset][CompareResult.IsSuper], or [incomparable][CompareResult.None].
1319
*/
1420
public fun compare(other: DataFrameSchema, comparisonMode: ComparisonMode = ComparisonMode.LENIENT): CompareResult
1521
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ internal const val DISPLAY_CONFIGURATION = "This constructor is only here for bi
147147

148148
internal const val DISPLAY_CONFIGURATION_COPY = "This function is only here for binary compatibility. $MESSAGE_1_0"
149149

150+
internal const val COMPARE_RESULT_EQUALS =
151+
"'Equals' is deprecated in favor of 'Matches' to clarify column order is irrelevant. $MESSAGE_1_0"
152+
150153
// endregion
151154

152155
// region WARNING in 1.0, ERROR in 1.1

0 commit comments

Comments
 (0)