Skip to content

Commit

Permalink
Improves validation performance of types defined in the spec
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed Sep 22, 2023
1 parent dd3ed60 commit 9e119e7
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 396 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ internal interface DeferredReference : TypeInternal {
throw UnresolvedTypeReferenceException("This should be unreachable because this method should not be called before the call to DeferredReferenceManager.resolve() is complete.")
}
}
override fun getBaseType(): TypeBuiltin = if (isResolved()) resolve().getBaseType() else illegalMethodCallBeforeResolving()
override fun getBaseType(): SpecType = if (isResolved()) resolve().getBaseType() else illegalMethodCallBeforeResolving()
override fun isValidForBaseType(value: IonValue): Boolean = if (isResolved()) resolve().isValidForBaseType(value) else illegalMethodCallBeforeResolving()
override fun validate(value: IonValue, issues: Violations) = if (isResolved()) resolve().validate(value, issues) else illegalMethodCallBeforeResolving()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,6 @@ internal class IonSchemaSystemImpl(
private val warnCallback: (() -> String) -> Unit
) : IonSchemaSystem {

private val schemaCores = mapOf(
IonSchemaVersion.v1_0 to SchemaCore(this, IonSchemaVersion.v1_0),
IonSchemaVersion.v2_0 to SchemaCore(this, IonSchemaVersion.v2_0)
)

internal fun getBuiltInTypesSchema(version: IonSchemaVersion) = schemaCores[version]!!

private val schemaContentCache = SchemaContentCache(this::loadSchemaContent)

// Set to be used to detect cycle in import dependencies
Expand Down Expand Up @@ -101,7 +94,7 @@ internal class IonSchemaSystemImpl(

private fun createSchema(referenceManager: DeferredReferenceManager, version: IonSchemaVersion, schemaId: String?, isl: List<IonValue>): SchemaInternal {
return when (version) {
IonSchemaVersion.v1_0 -> SchemaImpl_1_0(referenceManager, this, schemaCores[version]!!, isl.iterator(), schemaId)
IonSchemaVersion.v1_0 -> SchemaImpl_1_0(referenceManager, this, isl.iterator(), schemaId)
IonSchemaVersion.v2_0 -> SchemaImpl_2_0(referenceManager, this, isl, schemaId)
}
}
Expand Down
155 changes: 0 additions & 155 deletions ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SchemaCore.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import com.amazon.ionschema.internal.util.markReadOnly
internal class SchemaImpl_1_0 private constructor(
referenceManager: DeferredReferenceManager,
private val schemaSystem: IonSchemaSystemImpl,
private val schemaCore: SchemaCore,
schemaContent: Iterator<IonValue>,
override val schemaId: String?,
preloadedImports: Map<String, Import>,
Expand All @@ -52,10 +51,9 @@ internal class SchemaImpl_1_0 private constructor(
internal constructor(
referenceManager: DeferredReferenceManager,
schemaSystem: IonSchemaSystemImpl,
schemaCore: SchemaCore,
schemaContent: Iterator<IonValue>,
schemaId: String?
) : this(referenceManager, schemaSystem, schemaCore, schemaContent, schemaId, emptyMap(), mutableMapOf())
) : this(referenceManager, schemaSystem, schemaContent, schemaId, emptyMap(), mutableMapOf())

override val isl: IonDatagram

Expand Down Expand Up @@ -255,16 +253,16 @@ internal class SchemaImpl_1_0 private constructor(
typeMap[type.name] = type
}

override fun getType(name: String) = schemaCore.getType(name) ?: types[name]
override fun getType(name: String) = SpecTypes[name] ?: types[name]

override fun getInScopeType(name: String) = (schemaCore.getType(name) ?: types[name])
override fun getInScopeType(name: String) = SpecTypes[name] ?: types[name]

override fun getDeclaredType(name: String) = declaredTypes[name]

override fun getDeclaredTypes(): Iterator<TypeInternal> = declaredTypes.values.iterator()

override fun getTypes(): Iterator<TypeInternal> =
(schemaCore.getTypes().asSequence() + types.values.asSequence())
(SpecTypes.asSequence() + types.values.asSequence())
.filter { it is ImportedType || it is TypeImpl }
.iterator()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,7 @@ internal class SchemaImpl_2_0 internal constructor(
var foundFooter = false
var foundAnyType = false

importedTypes += schemaSystem.getBuiltInTypesSchema(ionSchemaLanguageVersion)
.getDeclaredTypes()
.asSequence()
.associateBy { it.name }
importedTypes += SpecTypes.asMap()

schemaContent.mapTo(isl) { it.clone() }
.markReadOnly()
Expand Down Expand Up @@ -315,7 +312,7 @@ internal class SchemaImpl_2_0 internal constructor(

override fun getDeclaredTypes(): Iterator<TypeInternal> = declaredTypes.values.iterator()

override fun getTypes(): Iterator<TypeInternal> = (declaredTypes.values + importedTypes.values.filterIsInstance<TypeBuiltin>()).iterator()
override fun getTypes(): Iterator<TypeInternal> = (declaredTypes.values + importedTypes.values.filterIsInstance<SpecType>()).iterator()

override fun newType(isl: String) = newType(
schemaSystem.ionSystem.singleValue(isl) as IonStruct
Expand Down
134 changes: 134 additions & 0 deletions ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.ionschema.internal

import com.amazon.ion.IonType
import com.amazon.ion.IonValue
import com.amazon.ion.system.IonSystemBuilder
import com.amazon.ionschema.Violation
import com.amazon.ionschema.Violations
import com.amazon.ionschema.internal.util.schemaTypeName

/**
* Represents a type that is defined in the Ion Schema Specification.
*
* All implementations should be as lightweight as possible (e.g. no delegation to other [TypeInternal] instances)
* because these are the most commonly used types that are used as part of building almost every user-defined type.
*/
internal sealed class SpecType : TypeInternal

private val ionSystem = IonSystemBuilder.standard().build()

/**
* The `$any` type gets special treatment because it is a no-op.
*/
private object UniversalType : SpecType() {
override val schemaId: String?
get() = null
override fun getBaseType(): SpecType = this
override fun isValidForBaseType(value: IonValue): Boolean = true

Check warning on line 29 in ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt#L27-L29

Added lines #L27 - L29 were not covered by tests
override val name: String = "\$any"
override val isl: IonValue = ionSystem.newSymbol("\$any")
override fun validate(value: IonValue, issues: Violations) = Unit

Check warning on line 32 in ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt#L32

Added line #L32 was not covered by tests
}

/**
* The `any` type gets special treatment because it is the default type for ISL 1.0
*/
private object AnyType : SpecType() {
override val schemaId: String?
get() = null
override fun getBaseType(): SpecType = this
override fun isValidForBaseType(value: IonValue): Boolean = true

Check warning on line 42 in ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt#L40-L42

Added lines #L40 - L42 were not covered by tests
override val name: String = "any"
override val isl: IonValue = ionSystem.newSymbol("any")
override fun validate(value: IonValue, issues: Violations) {
if (value.isNullValue) {
val typeName = value.type.schemaTypeName()
issues.add(Violation(isl, "type_mismatch", message = "expected type any, found null $typeName"))
}
}
}

/**
* The `nothing` type gets special treatment because all data is invalid.
*/
private object EmptyType : SpecType() {
override val schemaId: String?
get() = null
override fun getBaseType(): SpecType = this
override fun isValidForBaseType(value: IonValue): Boolean = true

Check warning on line 60 in ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt#L58-L60

Added lines #L58 - L60 were not covered by tests
override val name: String = "nothing"
override val isl: IonValue = ionSystem.newSymbol("nothing")
override fun validate(value: IonValue, issues: Violations) {
val typeName = value.type.schemaTypeName()
issues.add(Violation(isl, "type_mismatch", message = "expected type nothing, found $typeName"))
}
}

/**
* An implementation of [SpecType] that is valid for a set of [IonType]s and optionally the corresponding typed nulls.
*/
private class GenericSpecTypeImpl(override val name: String, private val ionTypes: Set<IonType>, private val nullsAllowed: Boolean) : SpecType() {
override val schemaId: String?
get() = null

Check warning on line 74 in ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/internal/SpecType.kt#L74

Added line #L74 was not covered by tests

override fun getBaseType(): SpecType = this

override fun isValidForBaseType(value: IonValue): Boolean = value.type in ionTypes

override val isl: IonValue = ionSystem.newSymbol(name)

override fun validate(value: IonValue, issues: Violations) {
val isCorrectType = value.type in ionTypes
val isNullCheckOkay = nullsAllowed or !value.isNullValue
if (!isCorrectType || !isNullCheckOkay) {
val maybeNull = if (value.isNullValue) "null " else ""
val typeName = value.type.schemaTypeName()
issues.add(Violation(isl, "type_mismatch", message = "expected type $name, found $maybeNull$typeName"))
}
}
}

/**
* Object for holding all instances of [SpecType].
*/
internal object SpecTypes {
private val allTypes: MutableMap<String, SpecType> = mutableMapOf()

init {
// Micro DSL so that we can declaratively create the Spec types.
infix fun String.isNonNull(types: Set<IonType>) {
allTypes[this] = GenericSpecTypeImpl(this, types, nullsAllowed = false)
}
infix fun String.isMaybeNull(types: Set<IonType>) {
allTypes[this] = GenericSpecTypeImpl(this, types, nullsAllowed = true)
}

// Creates all the SpecTypes that correspond directly to _one_ IonType.
IonType.values().forEach {
val typeName = it.schemaTypeName()
if (it != IonType.NULL) typeName isNonNull setOf(it)
if (it != IonType.DATAGRAM) "$$typeName" isMaybeNull setOf(it)
}

"text" isNonNull setOf(IonType.STRING, IonType.SYMBOL)
"\$text" isMaybeNull setOf(IonType.STRING, IonType.SYMBOL)
"number" isNonNull setOf(IonType.INT, IonType.FLOAT, IonType.DECIMAL)
"\$number" isMaybeNull setOf(IonType.INT, IonType.FLOAT, IonType.DECIMAL)
"lob" isNonNull setOf(IonType.BLOB, IonType.CLOB)
"\$lob" isMaybeNull setOf(IonType.BLOB, IonType.CLOB)
allTypes["any"] = AnyType
allTypes["\$any"] = UniversalType
allTypes["nothing"] = EmptyType
}

/** Gets one of the spec types by name */
operator fun get(name: String): SpecType? = allTypes[name]

/** Gets all the spec types as a map */
fun asMap(): Map<String, SpecType> = allTypes

/** Gets all the spec types as a sequence */
fun asSequence() = allTypes.values.asSequence()
}
Loading

0 comments on commit 9e119e7

Please sign in to comment.