Skip to content
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

misc: upgrade to latest version of smithy-kotlin #1338

Merged
merged 36 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
165c837
feat: business metrics
0marperez May 23, 2024
489e432
Fix incorrect import
0marperez May 24, 2024
2c3bc06
use native function instead of JVM only
0marperez May 24, 2024
16a750f
update smithy version in libs.versions for smithy projections
0marperez May 24, 2024
08b3965
Merge branch 'main' into business-metrics
0marperez May 24, 2024
77d617c
small fixes to keep in line with smithy kotlin
0marperez Jun 6, 2024
2b09fff
Merge branch 'business-metrics' of https://github.com/awslabs/aws-sdk…
0marperez Jun 6, 2024
cfc9823
pr feedback
0marperez Jun 11, 2024
31570a9
merge from main
0marperez Jun 11, 2024
9ecd76e
remove unused import
0marperez Jun 11, 2024
0d98563
feedback
0marperez Jun 13, 2024
53a324d
misc: dynamodb e2e tests
0marperez Jun 14, 2024
24cf5e0
re-add accidentally removed file
0marperez Jun 14, 2024
cbae584
add missing blank space in re-added file
0marperez Jun 14, 2024
3f457b9
feedback and test pagination
0marperez Jun 14, 2024
5015c1e
merge from main
0marperez Jun 14, 2024
31b640d
depend on snapshot version of smithy
0marperez Jun 14, 2024
1277851
merge from main
0marperez Jun 14, 2024
2242491
api dump
0marperez Jun 14, 2024
d165743
use snapshot version so CI passes
0marperez Jun 14, 2024
bbccd01
use latest released version of smithy kotlin
0marperez Jun 17, 2024
369b908
use latest smithy kotlin version
0marperez Jun 17, 2024
1a4f99b
fix CI ?
0marperez Jun 17, 2024
559c6bf
trigger CI
0marperez Jun 17, 2024
e34063f
pull from main
0marperez Jun 17, 2024
a40ca34
merge from main
0marperez Jun 18, 2024
e8137c2
remove scripts not intended for commit
0marperez Jun 18, 2024
b9cc831
update smithy kotlin version
0marperez Jun 18, 2024
1991918
upgrade smithy kotlin version
0marperez Jun 18, 2024
7568a83
Upgrade to latest version of smithy-kotlin
lauzadis Jun 21, 2024
24d999e
CI
lauzadis Jun 21, 2024
0710315
merge business metrics
0marperez Jun 21, 2024
d317e2c
merge dynamodb e2e tests
0marperez Jun 21, 2024
b8c5238
Revert "fix: revert dynamodb e2e test (#1334)"
lauzadis Jun 21, 2024
615e6ad
Revert "fix: revert business metrics (#1333)"
lauzadis Jun 21, 2024
486fe2e
Merge branch 'misc-upgrade-smithy-kotlin' of github.com:awslabs/aws-s…
lauzadis Jun 21, 2024
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
32 changes: 32 additions & 0 deletions aws-runtime/aws-http/api/aws-http.api
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public final class aws/sdk/kotlin/runtime/http/AwsUserAgentMetadata$Companion {
public final class aws/sdk/kotlin/runtime/http/AwsUserAgentMetadataKt {
public static final field AWS_APP_ID_ENV Ljava/lang/String;
public static final field AWS_APP_ID_PROP Ljava/lang/String;
public static final field BUSINESS_METRICS_MAX_LENGTH I
}

public final class aws/sdk/kotlin/runtime/http/ExecutionEnvMetadata {
Expand Down Expand Up @@ -139,6 +140,14 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AddUserAgentMetadata
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
}

public final class aws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric : java/lang/Enum, aws/smithy/kotlin/runtime/businessmetrics/BusinessMetric {
public static final field S3_EXPRESS_BUCKET Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public fun getIdentifier ()Ljava/lang/String;
public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
public static fun values ()[Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
}

public final class aws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
public static final field INSTANCE Laws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor;
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand All @@ -162,6 +171,29 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor :
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
}

public final class aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
public fun <init> ()V
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun modifyBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun modifyBeforeRetryLoop (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun modifyBeforeSerialization (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun modifyBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun modifyBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun readAfterAttempt (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V
public fun readAfterDeserialization (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V
public fun readAfterExecution (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V
public fun readAfterSerialization (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
public fun readAfterSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
public fun readAfterTransmit (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)V
public fun readBeforeAttempt (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
public fun readBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)V
public fun readBeforeExecution (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;)V
public fun readBeforeSerialization (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;)V
public fun readBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
}

public final class aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
public fun <init> ()V
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import kotlin.jvm.JvmInline

internal const val AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"
public const val AWS_APP_ID_ENV: String = "AWS_SDK_UA_APP_ID"
private const val USER_AGENT_SPEC_VERSION = "2.1"
public const val BUSINESS_METRICS_MAX_LENGTH: Int = 1024

// non-standard environment variables/properties
public const val AWS_APP_ID_PROP: String = "aws.userAgentAppId"
Expand Down Expand Up @@ -65,7 +67,7 @@ public data class AwsUserAgentMetadata(
get() = buildList {
add(sdkMetadata)
customMetadata?.extras?.takeIf { it.containsKey("internal") }?.let { add("md/internal") }
add(uaPair("ua", "2.0")) // User agent specification version 2.0
add(uaPair("ua", USER_AGENT_SPEC_VERSION))
add(apiMetadata)
add(osMetadata)
add(languageMetadata)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.runtime.http.interceptors

import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH
import aws.sdk.kotlin.runtime.http.middleware.USER_AGENT
import aws.smithy.kotlin.runtime.InternalApi
import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric
import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.http.request.toBuilder

/**
* Appends business metrics to the `User-Agent` header.
*/
public class BusinessMetricsInterceptor : HttpInterceptor {
override suspend fun modifyBeforeTransmit(context: ProtocolRequestInterceptorContext<Any, HttpRequest>): HttpRequest {
context.executionContext.getOrNull(BusinessMetrics)?.let { metrics ->
val metricsString = formatMetrics(metrics)
val currentUserAgentHeader = context.protocolRequest.headers[USER_AGENT]
val modifiedRequest = context.protocolRequest.toBuilder()

modifiedRequest.headers[USER_AGENT] = currentUserAgentHeader + metricsString

return modifiedRequest.build()
}
return context.protocolRequest
}
}

/**
* Makes sure the metrics do not exceed the maximum size and truncates them if so.
*/
private fun formatMetrics(metrics: MutableSet<String>): String {
if (metrics.isEmpty()) return ""
val metricsString = metrics.joinToString(",", "m/")
val metricsByteArray = metricsString.encodeToByteArray()

if (metricsByteArray.size <= BUSINESS_METRICS_MAX_LENGTH) return metricsString

val lastCommaIndex = metricsByteArray
.sliceArray(0 until 1024)
.indexOfLast { it == ','.code.toByte() }
.takeIf { it != -1 }

lastCommaIndex?.let {
return metricsByteArray.decodeToString(
0,
lastCommaIndex,
true,
)
}

throw IllegalStateException("Business metrics are incorrectly formatted: $metricsString")
}

/**
* AWS SDK specific business metrics
*/
@InternalApi
public enum class AwsBusinessMetric(public override val identifier: String) : BusinessMetric {
S3_EXPRESS_BUCKET("J"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class AwsUserAgentMetadataTest {
val expected = listOf(
"aws-sdk-kotlin/1.2.3",
"md/internal",
"ua/2.0",
"ua/2.1",
"api/test-service#1.2.3",
"os/linux#ubuntu-20.04",
"lang/kotlin#1.4.31",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.runtime.http.interceptors

import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH
import aws.sdk.kotlin.runtime.http.middleware.USER_AGENT
import aws.smithy.kotlin.runtime.businessmetrics.SmithyBusinessMetric
import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
import aws.smithy.kotlin.runtime.collections.get
import aws.smithy.kotlin.runtime.http.*
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.operation.ExecutionContext
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class BusinessMetricsInterceptorTest {
@Test
fun noBusinessMetrics() = runTest {
val executionContext = ExecutionContext()
val interceptor = BusinessMetricsInterceptor()
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
val userAgentHeader = request.headers[USER_AGENT]!!

assertFalse(userAgentHeader.endsWith("m/"))
}

@Test
fun businessMetrics() = runTest {
val executionContext = ExecutionContext()
executionContext.emitBusinessMetric(AwsBusinessMetric.S3_EXPRESS_BUCKET)

val interceptor = BusinessMetricsInterceptor()
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
val userAgentHeader = request.headers[USER_AGENT]!!

assertTrue(
userAgentHeader.endsWith(
"m/${AwsBusinessMetric.S3_EXPRESS_BUCKET.identifier}",
),
)
}

@Test
fun multipleBusinessMetrics() = runTest {
val executionContext = ExecutionContext()
executionContext.emitBusinessMetric(AwsBusinessMetric.S3_EXPRESS_BUCKET)
executionContext.emitBusinessMetric(SmithyBusinessMetric.GZIP_REQUEST_COMPRESSION)

val interceptor = BusinessMetricsInterceptor()
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
val userAgentHeader = request.headers[USER_AGENT]!!

assertTrue(
userAgentHeader.endsWith(
"m/${AwsBusinessMetric.S3_EXPRESS_BUCKET.identifier},${SmithyBusinessMetric.GZIP_REQUEST_COMPRESSION.identifier}",
),
)
}

@Test
fun truncateBusinessMetrics() = runTest {
val executionContext = ExecutionContext()
executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf()

for (i in 0..1024) {
executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics].add(i.toString())
}

val rawMetrics = executionContext[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics]
val rawMetricsString = rawMetrics.joinToString(",", "m/")
val rawMetricsByteArray = rawMetricsString.encodeToByteArray()

assertTrue(rawMetricsByteArray.size >= BUSINESS_METRICS_MAX_LENGTH)

val interceptor = BusinessMetricsInterceptor()
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
val userAgentHeader = request.headers[USER_AGENT]!!
val truncatedMetrics = "m/" + userAgentHeader.substringAfter("m/")

assertTrue(truncatedMetrics.encodeToByteArray().size <= BUSINESS_METRICS_MAX_LENGTH)
assertFalse(truncatedMetrics.endsWith(","))
}

@Test
fun malformedBusinessMetrics() = runTest {
val executionContext = ExecutionContext()

executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf(
"A".repeat(BUSINESS_METRICS_MAX_LENGTH),
)

val interceptor = BusinessMetricsInterceptor()

assertFailsWith<IllegalStateException>("Business metrics are incorrectly formatted:") {
interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
}
}
}

private fun interceptorContext(executionContext: ExecutionContext): ProtocolRequestInterceptorContext<Any, HttpRequest> =
object : ProtocolRequestInterceptorContext<Any, HttpRequest> {
override val protocolRequest: HttpRequest = HttpRequest(
HttpMethod.GET,
Url.parse("https://test.aws.com?foo=bar"),
Headers {
append(USER_AGENT, "aws-sdk-kotlin/1.2.3 ua/2.1 api/test-service#1.2.3...")
},
)
override val executionContext: ExecutionContext = executionContext
override val request: Any = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class UserAgentTest {
assertTrue(request.headers.contains(X_AMZ_USER_AGENT))
assertEquals("aws-sdk-kotlin/1.2.3", request.headers[X_AMZ_USER_AGENT])
assertTrue(
request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3 ua/2.0 api/test-service#1.2.3"),
request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3 ua/2.1 api/test-service#1.2.3"),
"$USER_AGENT header didn't start with expected value. Found: ${request.headers[USER_AGENT]}",
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ object AwsRuntimeTypes {
object Interceptors : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors") {
val AddUserAgentMetadataInterceptor = symbol("AddUserAgentMetadataInterceptor")
val UnsupportedSigningAlgorithmInterceptor = symbol("UnsupportedSigningAlgorithmInterceptor")
val BusinessMetricsInterceptor = symbol("BusinessMetricsInterceptor")
val AwsBusinessMetric = symbol("AwsBusinessMetric")
}

object Retries {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.codegen

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointBusinessMetrics
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
import software.amazon.smithy.model.shapes.OperationShape

/**
* Renders the addition of the [BusinessMetricsInterceptor] and endpoint business metrics emitters
*/
class BusinessMetricsIntegration : KotlinIntegration {
override val order: Byte
get() = super.order

override val sectionWriters: List<SectionWriterBinding>
get() = listOf(
SectionWriterBinding(EndpointBusinessMetrics, endpointBusinessMetricsSectionWriter),
)

private val endpointBusinessMetricsSectionWriter = SectionWriter { writer, _ ->
writer.write("")
writer.write(
"if (endpoint.attributes.contains(#T)) request.context.#T(#T.SERVICE_ENDPOINT_OVERRIDE)",
RuntimeTypes.Core.BusinessMetrics.ServiceEndpointOverride,
RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric,
RuntimeTypes.Core.BusinessMetrics.SmithyBusinessMetric,
)
writer.write(
"if (endpoint.attributes.contains(#T)) request.context.#T(#T.ACCOUNT_ID_BASED_ENDPOINT)",
RuntimeTypes.Core.BusinessMetrics.AccountIdBasedEndpointAccountId,
RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric,
RuntimeTypes.Core.BusinessMetrics.SmithyBusinessMetric,
)
writer.write(
"if (endpoint.attributes.contains(#T.SigningService) && endpoint.attributes[#T.SigningService] == \"s3express\") request.context.#T(#T.S3_EXPRESS_BUCKET)",
AwsSigningAttributes,
AwsSigningAttributes,
RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric,
AwsRuntimeTypes.Http.Interceptors.AwsBusinessMetric,
)
writer.write("")
}

override fun customizeMiddleware(
ctx: ProtocolGenerator.GenerationContext,
resolved: List<ProtocolMiddleware>,
): List<ProtocolMiddleware> = resolved + userAgentBusinessMetricsMiddleware

private val userAgentBusinessMetricsMiddleware = object : ProtocolMiddleware {
override val name: String = "UserAgentBusinessMetrics"
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
writer.write(
"op.interceptors.add(#T())",
AwsRuntimeTypes.Http.Interceptors.BusinessMetricsInterceptor,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ aws.sdk.kotlin.codegen.customization.cloudfrontkeyvaluestore.BackfillSigV4ACusto
aws.sdk.kotlin.codegen.customization.s3.express.SigV4S3ExpressAuthSchemeIntegration
aws.sdk.kotlin.codegen.customization.s3.express.S3ExpressIntegration
aws.sdk.kotlin.codegen.customization.s3.S3ExpiresIntegration
aws.sdk.kotlin.codegen.BusinessMetricsIntegration
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ coroutines-version = "1.7.3"
atomicfu-version = "0.23.1"

# smithy-kotlin codegen and runtime are versioned separately
smithy-kotlin-runtime-version = "1.2.7"
smithy-kotlin-codegen-version = "0.32.7"
smithy-kotlin-runtime-version = "1.2.10"
smithy-kotlin-codegen-version = "0.32.10"

# codegen
smithy-version = "1.49.0"
Expand Down
11 changes: 11 additions & 0 deletions services/dynamodb/e2eTest/src/DynamoDbTestUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.services.dynamodb

/**
* Checks if a Dynamo DB table exists based on its name
*/
internal suspend fun DynamoDbClient.tableExists(name: String): Boolean =
this.listTables {}.tableNames?.contains(name) ?: false
Loading
Loading