diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasDescriptor.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasDescriptor.kt deleted file mode 100644 index 642f43015..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasDescriptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.kakao.actionbase.core.metadata - -import com.kakao.actionbase.core.Constants - -import com.fasterxml.jackson.annotation.JsonIgnore - -data class AliasDescriptor( - override val tenant: String, - val database: String, - val alias: String, - val table: String, - override val active: Boolean = true, - override val comment: String = Constants.DEFAULT_COMMENT, - override val revision: Long = Constants.DEFAULT_REVISION, - override val createdAt: Long = Constants.DEFAULT_CREATED_AT, - override val createdBy: String = Constants.DEFAULT_CREATED_BY, - override val updatedAt: Long = Constants.DEFAULT_UPDATED_AT, - override val updatedBy: String = Constants.DEFAULT_UPDATED_BY, -) : V3Descriptor { - @JsonIgnore - override val id: AliasId = AliasId(tenant, database, alias) -} diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasId.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasId.kt deleted file mode 100644 index 7d1b80e3c..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasId.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kakao.actionbase.core.metadata - -data class AliasId( - val tenant: String, - val database: String, - val alias: String, -) : Id diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseDescriptor.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseDescriptor.kt deleted file mode 100644 index b4afb934f..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseDescriptor.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.kakao.actionbase.core.metadata - -import com.kakao.actionbase.core.Constants -import com.kakao.actionbase.core.metadata.payload.DatabaseCreateRequest -import com.kakao.actionbase.core.metadata.payload.DatabaseUpdateRequest - -import com.fasterxml.jackson.annotation.JsonIgnore - -data class DatabaseDescriptor( - override val tenant: String, - val database: String, - override val active: Boolean = true, - override val comment: String = Constants.DEFAULT_COMMENT, - override val revision: Long = Constants.DEFAULT_REVISION, - override val createdAt: Long = Constants.DEFAULT_CREATED_AT, - override val createdBy: String = Constants.DEFAULT_CREATED_BY, - override val updatedAt: Long = Constants.DEFAULT_UPDATED_AT, - override val updatedBy: String = Constants.DEFAULT_UPDATED_BY, -) : V3Descriptor { - @JsonIgnore - override val id: DatabaseId = DatabaseId(tenant, database) - - fun toCreateRequest(): DatabaseCreateRequest = - DatabaseCreateRequest( - database = database, - comment = comment, - ) - - fun toUpdateRequest(): DatabaseUpdateRequest = - DatabaseUpdateRequest( - active = active, - comment = comment, - ) -} diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseId.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseId.kt deleted file mode 100644 index 131350b26..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseId.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.kakao.actionbase.core.metadata - -data class DatabaseId( - val tenant: String, - val database: String, -) : Id diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatastoreId.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatastoreId.kt deleted file mode 100644 index 8c5e6b38d..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatastoreId.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.kakao.actionbase.core.metadata - -data class DatastoreId( - val tenant: String, - val datastore: String, -) : Id diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableDescriptor.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableDescriptor.kt deleted file mode 100644 index 24cdaaf67..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableDescriptor.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.kakao.actionbase.core.metadata - -import com.kakao.actionbase.core.Constants -import com.kakao.actionbase.core.metadata.common.ModelSchema -import com.kakao.actionbase.core.metadata.common.MutationMode - -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonSubTypes -import com.fasterxml.jackson.annotation.JsonTypeInfo -import com.fasterxml.jackson.annotation.JsonTypeName - -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "type", -) -@JsonSubTypes( - JsonSubTypes.Type(value = TableDescriptor.Edge::class, name = "edge"), - JsonSubTypes.Type(value = TableDescriptor.ImmutableEdge::class, name = "immutableEdge"), - JsonSubTypes.Type(value = TableDescriptor.MultiEdge::class, name = "multiEdge"), - JsonSubTypes.Type(value = TableDescriptor.Vertex::class, name = "vertex"), -) -sealed class TableDescriptor : V3Descriptor { - abstract val database: String - - abstract val table: String - - abstract val schema: T - - abstract val mode: MutationMode - - abstract val storage: String - - @JsonTypeName("edge") - data class Edge( - override val tenant: String, - override val database: String, - override val table: String, - override val schema: ModelSchema.Edge, - override val mode: MutationMode, - override val storage: String, - override val active: Boolean = true, - override val comment: String = Constants.DEFAULT_COMMENT, - override val revision: Long = Constants.DEFAULT_REVISION, - override val createdAt: Long = Constants.DEFAULT_CREATED_AT, - override val createdBy: String = Constants.DEFAULT_CREATED_BY, - override val updatedAt: Long = Constants.DEFAULT_UPDATED_AT, - override val updatedBy: String = Constants.DEFAULT_UPDATED_BY, - ) : TableDescriptor() { - @JsonIgnore - override val id: TableId = TableId(tenant, database, table) - } - - @JsonTypeName("immutableEdge") - data class ImmutableEdge( - override val tenant: String, - override val database: String, - override val table: String, - override val schema: ModelSchema.ImmutableEdge, - override val mode: MutationMode, - override val storage: String, - override val active: Boolean = true, - override val comment: String = Constants.DEFAULT_COMMENT, - override val revision: Long = Constants.DEFAULT_REVISION, - override val createdAt: Long = Constants.DEFAULT_CREATED_AT, - override val createdBy: String = Constants.DEFAULT_CREATED_BY, - override val updatedAt: Long = Constants.DEFAULT_UPDATED_AT, - override val updatedBy: String = Constants.DEFAULT_UPDATED_BY, - ) : TableDescriptor() { - @JsonIgnore - override val id: TableId = TableId(tenant, database, table) - } - - @JsonTypeName("multiEdge") - data class MultiEdge( - override val tenant: String, - override val database: String, - override val table: String, - override val schema: ModelSchema.MultiEdge, - override val mode: MutationMode, - override val storage: String, - override val active: Boolean = true, - override val comment: String = Constants.DEFAULT_COMMENT, - override val revision: Long = Constants.DEFAULT_REVISION, - override val createdAt: Long = Constants.DEFAULT_CREATED_AT, - override val createdBy: String = Constants.DEFAULT_CREATED_BY, - override val updatedAt: Long = Constants.DEFAULT_UPDATED_AT, - override val updatedBy: String = Constants.DEFAULT_UPDATED_BY, - ) : TableDescriptor() { - @JsonIgnore - override val id: TableId = TableId(tenant, database, table) - } - - @JsonTypeName("vertex") - data class Vertex( - override val tenant: String, - override val database: String, - override val table: String, - override val schema: ModelSchema.Vertex, - override val mode: MutationMode, - override val storage: String, - override val active: Boolean = true, - override val comment: String = Constants.DEFAULT_COMMENT, - override val revision: Long = Constants.DEFAULT_REVISION, - override val createdAt: Long = Constants.DEFAULT_CREATED_AT, - override val createdBy: String = Constants.DEFAULT_CREATED_BY, - override val updatedAt: Long = Constants.DEFAULT_UPDATED_AT, - override val updatedBy: String = Constants.DEFAULT_UPDATED_BY, - ) : TableDescriptor() { - @JsonIgnore - override val id: TableId = TableId(tenant, database, table) - } -} diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableId.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableId.kt deleted file mode 100644 index 005503950..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableId.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kakao.actionbase.core.metadata - -data class TableId( - val tenant: String, - val database: String, - val table: String, -) : Id diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TenantId.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TenantId.kt deleted file mode 100644 index 997b18f1c..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/TenantId.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kakao.actionbase.core.metadata - -data class TenantId( - val tenant: String, -) : Id diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/V3Descriptor.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/V3Descriptor.kt deleted file mode 100644 index 8120b355c..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/V3Descriptor.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.kakao.actionbase.core.metadata - -interface V3Descriptor { - val id: ID - - val active: Boolean - - val tenant: String - - val comment: String - - val revision: Long - - val createdAt: Long - - val createdBy: String - - val updatedAt: Long - - val updatedBy: String -} diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseCreateRequest.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseCreateRequest.kt deleted file mode 100644 index 210a32e48..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseCreateRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.kakao.actionbase.core.metadata.payload - -data class DatabaseCreateRequest( - val database: String, - val comment: String, - val active: Boolean = true, - val version: Long = System.currentTimeMillis(), -) diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseUpdateRequest.kt b/core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseUpdateRequest.kt deleted file mode 100644 index 3c4312f55..000000000 --- a/core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseUpdateRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kakao.actionbase.core.metadata.payload - -data class DatabaseUpdateRequest( - val active: Boolean? = null, - val comment: String? = null, - val version: Long = System.currentTimeMillis(), -) diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasDescriptor.kt b/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasDescriptor.kt index 517e23b75..a11e7fcaa 100644 --- a/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasDescriptor.kt +++ b/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasDescriptor.kt @@ -1,7 +1,5 @@ package com.kakao.actionbase.core.v2.metadata -import com.kakao.actionbase.core.metadata.AliasDescriptor as V3AliasDescriptor - import com.kakao.actionbase.core.v2.metadata.common.V2Identifier import com.fasterxml.jackson.annotation.JsonIgnore @@ -27,13 +25,4 @@ data class V2AliasDescriptor( @JsonIgnore val targetLabel = V2Identifier.Companion.of(target).name - - fun toV3(tenant: String): V3AliasDescriptor = - V3AliasDescriptor( - tenant = tenant, - database = id.service, - alias = id.alias, - table = targetLabel, - comment = desc, - ) } diff --git a/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceDescriptor.kt b/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceDescriptor.kt index ecff0a559..f7a0ae655 100644 --- a/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceDescriptor.kt +++ b/core/src/main/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceDescriptor.kt @@ -1,7 +1,5 @@ package com.kakao.actionbase.core.v2.metadata -import com.kakao.actionbase.core.metadata.DatabaseDescriptor - import com.fasterxml.jackson.annotation.JsonIgnore data class V2ServiceDescriptor( @@ -11,12 +9,4 @@ data class V2ServiceDescriptor( ) : V2Descriptor { @JsonIgnore override val id: V2ServiceId = V2ServiceId(name) - - fun toV3(tenant: String): DatabaseDescriptor = - DatabaseDescriptor( - tenant = tenant, - database = name, - active = active, - comment = desc, - ) } diff --git a/core/src/test/kotlin/com/kakao/actionbase/core/metadata/AliasSerializationTest.kt b/core/src/test/kotlin/com/kakao/actionbase/core/metadata/AliasSerializationTest.kt deleted file mode 100644 index db258b686..000000000 --- a/core/src/test/kotlin/com/kakao/actionbase/core/metadata/AliasSerializationTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.kakao.actionbase.core.metadata - -import com.kakao.actionbase.test.documentations.params.ObjectSource -import com.kakao.actionbase.test.documentations.params.ObjectSourceParameterizedTest -import com.kakao.actionbase.test.json.PrettyObjectWriter - -import kotlin.test.assertEquals - -import com.fasterxml.jackson.module.kotlin.readValue - -class AliasSerializationTest { - val prettyWriter = PrettyObjectWriter.DEFAULT - - val objectMapper = prettyWriter.objectMapper - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - descriptor: { - "tenant": "test_tenant", - "database": "test_database", - "alias": "test_alias", - "table": "test_table" - } - expected: |- - { - "tenant": "test_tenant", - "database": "test_database", - "alias": "test_alias", - "table": "test_table", - "active": true, - "comment": "", - "revision": -1, - "createdAt": -1, - "createdBy": "", - "updatedAt": -1, - "updatedBy": "" - } - """, - ) - fun `serializes to JSON`( - descriptor: AliasDescriptor, - expected: String, - ) { - assertEquals(expected, prettyWriter.writeValueAsString(descriptor)) - } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - input: '{"tenant":"test_tenant","database":"test_database","alias":"test_alias","table":"test_table"}' - expected: { - "tenant": "test_tenant", - "database": "test_database", - "alias": "test_alias", - "table": "test_table" - } - """, - ) - fun `deserializes from JSON`( - input: String, - expected: AliasDescriptor, - ) { - assertEquals(expected, objectMapper.readValue(input)) - } -} diff --git a/core/src/test/kotlin/com/kakao/actionbase/core/metadata/DatabaseSerializationTest.kt b/core/src/test/kotlin/com/kakao/actionbase/core/metadata/DatabaseSerializationTest.kt deleted file mode 100644 index 91e9f2205..000000000 --- a/core/src/test/kotlin/com/kakao/actionbase/core/metadata/DatabaseSerializationTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.kakao.actionbase.core.metadata - -import com.kakao.actionbase.test.documentations.params.ObjectSource -import com.kakao.actionbase.test.documentations.params.ObjectSourceParameterizedTest -import com.kakao.actionbase.test.json.PrettyObjectWriter - -import kotlin.test.assertEquals - -import com.fasterxml.jackson.module.kotlin.readValue - -class DatabaseSerializationTest { - val prettyWriter = PrettyObjectWriter.DEFAULT - - val objectMapper = prettyWriter.objectMapper - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - descriptor: { - "tenant": "test_tenant", - "database": "test_database" - } - expected: |- - { - "tenant": "test_tenant", - "database": "test_database", - "active": true, - "comment": "", - "revision": -1, - "createdAt": -1, - "createdBy": "", - "updatedAt": -1, - "updatedBy": "" - } - """, - ) - fun `serializes to JSON`( - descriptor: DatabaseDescriptor, - expected: String, - ) { - assertEquals(expected, prettyWriter.writeValueAsString(descriptor)) - } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - input: '{"tenant":"test_tenant","database":"test_database"}' - expected: { - "tenant": "test_tenant", - "database": "test_database" - } - """, - ) - fun `deserializes from JSON`( - input: String, - expected: DatabaseDescriptor, - ) { - assertEquals(expected, objectMapper.readValue(input)) - } -} diff --git a/core/src/test/kotlin/com/kakao/actionbase/core/metadata/TableSerializationTest.kt b/core/src/test/kotlin/com/kakao/actionbase/core/metadata/TableSerializationTest.kt deleted file mode 100644 index d089a281c..000000000 --- a/core/src/test/kotlin/com/kakao/actionbase/core/metadata/TableSerializationTest.kt +++ /dev/null @@ -1,251 +0,0 @@ -package com.kakao.actionbase.core.metadata - -import com.kakao.actionbase.test.documentations.params.ObjectSource -import com.kakao.actionbase.test.documentations.params.ObjectSourceParameterizedTest -import com.kakao.actionbase.test.json.PrettyObjectWriter - -import kotlin.test.assertEquals - -import com.fasterxml.jackson.module.kotlin.readValue - -class TableSerializationTest { - val prettyWriter = PrettyObjectWriter.DEFAULT - - val objectMapper = prettyWriter.objectMapper - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - descriptor: { - "type": "edge", - "tenant": "test_tenant", - "database": "test_database", - "table": "test_table", - "schema": { - "type": "edge", - "source": {"type": "long", "comment": "Source node ID"}, - "target": {"type": "long", "comment": "Target node ID"}, - "properties": [ - {"name": "id", "type": "long", "comment": "Identifier", "nullable": false}, - {"name": "name", "type": "string", "comment": "name", "nullable": false} - ], - "direction": "BOTH", - "groups": [ - { - "group": "by_day", - "type": "COUNT", - "fields": [ - { - "name": "version", - "bucket": { - "type": "date", - "name": "date_id", - "unit": "MILLISECOND", - "timezone": "+09:00", - "format": "yyyy-MM-dd" - } - } - ], - "comment": "group by day" - } - ], - "indexes": [ - { - "index": "updated_at_desc", - "fields": [{"field": "version", "order": "DESC"}], - "comment": "recent updates" - } - ] - }, - "mode": "SYNC", - "storage": "datastore://test_namespace/test_tenant:test_table" - } - expected: |- - { - "type": "edge", - "tenant": "test_tenant", - "database": "test_database", - "table": "test_table", - "schema": { - "type": "edge", - "source": {"type": "long", "comment": "Source node ID"}, - "target": {"type": "long", "comment": "Target node ID"}, - "properties": [ - { - "name": "id", - "type": "long", - "comment": "Identifier", - "nullable": false - }, - { - "name": "name", - "type": "string", - "comment": "name", - "nullable": false - } - ], - "direction": "BOTH", - "indexes": [ - { - "index": "updated_at_desc", - "fields": [{"field": "version", "order": "DESC"}], - "comment": "recent updates", - "primary": -1, - "batch": 0 - } - ], - "groups": [ - { - "group": "by_day", - "type": "COUNT", - "fields": [ - { - "name": "version", - "bucket": { - "type": "date", - "name": "date_id", - "unit": "MILLISECOND", - "timezone": "+09:00", - "format": "yyyy-MM-dd" - } - } - ], - "valueField": "-", - "comment": "group by day", - "directionType": "BOTH", - "ttl": 691200000, - "aggregations": {"topk": []} - } - ], - "caches": [] - }, - "mode": "SYNC", - "storage": "datastore://test_namespace/test_tenant:test_table", - "active": true, - "comment": "", - "revision": -1, - "createdAt": -1, - "createdBy": "", - "updatedAt": -1, - "updatedBy": "" - } - """, - ) - fun `serializes edge table to JSON`( - descriptor: TableDescriptor<*>, - expected: String, - ) { - assertEquals(expected, prettyWriter.writeValueAsString(descriptor)) - } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - input: |- - { - "type": "edge", - "tenant": "test_tenant", - "database": "test_database", - "table": "test_table", - "schema": { - "type": "edge", - "source": {"type": "long", "comment": "Source node ID"}, - "target": {"type": "long", "comment": "Target node ID"}, - "properties": [ - { - "name": "id", - "type": "long", - "comment": "Identifier", - "nullable": false - }, - { - "name": "name", - "type": "string", - "comment": "name", - "nullable": false - } - ], - "direction": "BOTH", - "indexes": [ - { - "index": "updated_at_desc", - "fields": [{"field": "version", "order": "DESC"}], - "comment": "recent updates" - } - ], - "groups": [ - { - "group": "by_day", - "type": "COUNT", - "fields": [ - { - "name": "version", - "bucket": { - "type": "date", - "name": "date_id", - "unit": "MILLISECOND", - "timezone": "+09:00", - "format": "yyyy-MM-dd" - } - } - ], - "comment": "group by day" - } - ] - }, - "mode": "SYNC", - "storage": "datastore://test_namespace/test_tenant:test_table" - } - expected: { - "type": "edge", - "tenant": "test_tenant", - "database": "test_database", - "table": "test_table", - "schema": { - "type": "edge", - "source": {"type": "long", "comment": "Source node ID"}, - "target": {"type": "long", "comment": "Target node ID"}, - "properties": [ - {"name": "id", "type": "long", "comment": "Identifier", "nullable": false}, - {"name": "name", "type": "string", "comment": "name", "nullable": false} - ], - "direction": "BOTH", - "groups": [ - { - "group": "by_day", - "type": "COUNT", - "fields": [ - { - "name": "version", - "bucket": { - "type": "date", - "name": "date_id", - "unit": "MILLISECOND", - "timezone": "+09:00", - "format": "yyyy-MM-dd" - } - } - ], - "comment": "group by day" - } - ], - "indexes": [ - { - "index": "updated_at_desc", - "fields": [{"field": "version", "order": "DESC"}], - "comment": "recent updates" - } - ] - }, - "mode": "SYNC", - "storage": "datastore://test_namespace/test_tenant:test_table" - } - """, - ) - fun `deserializes edge table from JSON`( - input: String, - expected: TableDescriptor<*>, - ) { - assertEquals(expected, objectMapper.readValue>(input)) - } -} diff --git a/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasSerializationTest.kt b/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasSerializationTest.kt index 3adc24c25..e4500a587 100644 --- a/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasSerializationTest.kt +++ b/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2AliasSerializationTest.kt @@ -1,13 +1,9 @@ package com.kakao.actionbase.core.v2.metadata -import com.kakao.actionbase.core.metadata.AliasDescriptor as V3AliasDescriptor - import com.kakao.actionbase.test.documentations.params.ObjectSource import com.kakao.actionbase.test.documentations.params.ObjectSourceParameterizedTest import com.kakao.actionbase.test.json.PrettyObjectWriter -import kotlin.test.Ignore -import kotlin.test.Test import kotlin.test.assertEquals import com.fasterxml.jackson.module.kotlin.readValue @@ -71,67 +67,4 @@ class V2AliasSerializationTest { ) { assertEquals(expected, objectMapper.readValue(input)) } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - tenant: test_tenant - descriptor: { - "name": "gift.gift_like_product_v1", - "desc": "Gift Wish", - "active": true, - "target": "gift.gift_like_product_v1_20240605_102816" - } - expected: { - "tenant": "test_tenant", - "database": "gift", - "alias": "gift_like_product_v1", - "table": "gift_like_product_v1_20240605_102816", - "comment": "Gift Wish" - } - """, - ) - fun `converts to V3 object`( - tenant: String, - descriptor: V2AliasDescriptor, - expected: V3AliasDescriptor, - ) { - assertEquals(expected, descriptor.toV3(tenant)) - } - - @Ignore - @Test - fun `test service to database json string`() { - // given - val v2AliasDescriptor = - V2AliasDescriptor( - name = "gift.gift_like_product_v1", - desc = "Gift Wish", - active = true, - target = "gift.gift_like_product_v1_20240605_102816", - ) - - // when - val v3 = v2AliasDescriptor.toV3("test_tenant") - val actual = prettyWriter.writeValueAsString(v3) - - // then - val expected = - """ - { - "tenant": "test_tenant", - "database": "gift", - "alias": "gift_like_product_v1", - "table": "gift_like_product_v1_20240605_102816", - "active": true, - "comment": "Gift Wish", - "revision": -1, - "createdAt": -1, - "createdBy": "", - "updatedAt": -1, - "updatedBy": "" - } - """.trimIndent() - assertEquals(expected, actual) - } } diff --git a/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceSerializationTest.kt b/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceSerializationTest.kt index 038fe2d7f..a4e1fa2b0 100644 --- a/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceSerializationTest.kt +++ b/core/src/test/kotlin/com/kakao/actionbase/core/v2/metadata/V2ServiceSerializationTest.kt @@ -1,8 +1,5 @@ package com.kakao.actionbase.core.v2.metadata -import com.kakao.actionbase.core.metadata.DatabaseDescriptor -import com.kakao.actionbase.core.metadata.payload.DatabaseCreateRequest -import com.kakao.actionbase.core.metadata.payload.DatabaseUpdateRequest import com.kakao.actionbase.test.documentations.params.ObjectSource import com.kakao.actionbase.test.documentations.params.ObjectSourceParameterizedTest import com.kakao.actionbase.test.json.PrettyObjectWriter @@ -45,85 +42,4 @@ class V2ServiceSerializationTest { ) { assertEquals(expected, objectMapper.readValue(input)) } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - tenant: test_tenant - descriptor: {"name": "gift", "desc": "Gift", "active": true} - expected: {"tenant": "test_tenant", "database": "gift", "comment": "Gift", "active": true} - """, - ) - fun `converts to V3 database object`( - tenant: String, - descriptor: V2ServiceDescriptor, - expected: DatabaseDescriptor, - ) { - assertEquals(expected, descriptor.toV3(tenant)) - } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - tenant: test_tenant - descriptor: {"name": "gift", "desc": "Gift", "active": true} - expected: |- - { - "tenant": "test_tenant", - "database": "gift", - "active": true, - "comment": "Gift", - "revision": -1, - "createdAt": -1, - "createdBy": "", - "updatedAt": -1, - "updatedBy": "" - } - """, - ) - fun `serializes V3 database object to JSON`( - tenant: String, - descriptor: V2ServiceDescriptor, - expected: String, - ) { - assertEquals(expected, prettyWriter.writeValueAsString(descriptor.toV3(tenant))) - } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - tenant: test_tenant - descriptor: {"name": "gift", "desc": "Gift", "active": true} - expected: {"database": "gift", "comment": "Gift"} - """, - ) - fun `produces V3 create request`( - tenant: String, - descriptor: V2ServiceDescriptor, - expected: DatabaseCreateRequest, - ) { - // version is generated from currentTimeMillis(); normalize before comparing - val actual = descriptor.toV3(tenant).toCreateRequest() - val sameVersion = 0L - assertEquals(expected.copy(version = sameVersion), actual.copy(version = sameVersion)) - } - - @ObjectSourceParameterizedTest - @ObjectSource( - """ - - tenant: test_tenant - descriptor: {"name": "gift", "desc": "Gift", "active": true} - expected: {"active": true, "comment": "Gift"} - """, - ) - fun `produces V3 update request`( - tenant: String, - descriptor: V2ServiceDescriptor, - expected: DatabaseUpdateRequest, - ) { - // version is generated from currentTimeMillis(); normalize before comparing - val actual = descriptor.toV3(tenant).toUpdateRequest() - val sameVersion = 0L - assertEquals(expected.copy(version = sameVersion), actual.copy(version = sameVersion)) - } } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasController.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasController.kt index e2072c87b..f015e6430 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasController.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasController.kt @@ -1,6 +1,8 @@ package com.kakao.actionbase.server.api.graph.v3.metadata -import com.kakao.actionbase.core.metadata.AliasDescriptor +import com.kakao.actionbase.server.util.mapToResponseEntity +import com.kakao.actionbase.v2.engine.service.ddl.DdlPage +import com.kakao.actionbase.v2.engine.service.ddl.DdlStatus import org.springframework.http.ResponseEntity import org.springframework.validation.annotation.Validated @@ -27,53 +29,36 @@ class AliasController( fun listAliases( @PathVariable database: String, @RequestParam(required = false, defaultValue = "ACTIVE") status: MetadataStatus, - ): Mono>> = - v3CompatService - .getAliases(database, status) - .map { ResponseEntity.ok(it) } + ): Mono>> = v3CompatService.getAliases(database, status).mapToResponseEntity() @GetMapping("/graph/v3/databases/{database}/aliases/{alias}") fun getAlias( @PathVariable database: String, @PathVariable alias: String, - ): Mono> = - v3CompatService - .getAlias(database, alias) - .map { ResponseEntity.ok(it) } - .defaultIfEmpty(ResponseEntity.notFound().build()) + ): Mono> = v3CompatService.getAlias(database, alias).mapToResponseEntity() @PostMapping("/graph/v3/databases/{database}/aliases") fun createAlias( @PathVariable database: String, @Valid @RequestBody request: AliasCreateRequest, - ): Mono> = + ): Mono>> = v3CompatService .createAlias( V3NameValidator.validateDatabase(database), V3NameValidator.validateAlias(request.alias), request, - ).map { ResponseEntity.ok(it) } + ).mapToResponseEntity() @PutMapping("/graph/v3/databases/{database}/aliases/{alias}") fun updateAlias( @PathVariable database: String, @PathVariable alias: String, @Valid @RequestBody request: AliasUpdateRequest, - ): Mono> = - v3CompatService - .updateAlias( - database, - alias, - request, - ).map { ResponseEntity.ok(it) } - .defaultIfEmpty(ResponseEntity.notFound().build()) + ): Mono>> = v3CompatService.updateAlias(database, alias, request).mapToResponseEntity() @DeleteMapping("/graph/v3/databases/{database}/aliases/{alias}") fun deleteAlias( @PathVariable database: String, @PathVariable alias: String, - ): Mono> = - v3CompatService - .deleteAlias(database, alias) - .then(Mono.just(ResponseEntity.noContent().build())) + ): Mono>> = v3CompatService.deleteAlias(database, alias).mapToResponseEntity() } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasResponse.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasResponse.kt new file mode 100644 index 000000000..bdbae0e40 --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasResponse.kt @@ -0,0 +1,15 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +/** + * The v3 name for a v2 alias. + * + * Same shape as the v2 `AliasEntity` wire form, with `name` split into `database` and `alias`, + * `target` narrowed to the table name within that database, and `desc` renamed to `comment`. + */ +data class AliasResponse( + val active: Boolean, + val database: String, + val alias: String, + val table: String, + val comment: String, +) diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseController.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseController.kt index d209ad215..447cdd1ee 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseController.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseController.kt @@ -1,8 +1,8 @@ package com.kakao.actionbase.server.api.graph.v3.metadata -import com.kakao.actionbase.core.metadata.DatabaseDescriptor -import com.kakao.actionbase.core.metadata.payload.DatabaseCreateRequest -import com.kakao.actionbase.core.metadata.payload.DatabaseUpdateRequest +import com.kakao.actionbase.server.util.mapToResponseEntity +import com.kakao.actionbase.v2.engine.service.ddl.DdlPage +import com.kakao.actionbase.v2.engine.service.ddl.DdlStatus import org.springframework.http.ResponseEntity import org.springframework.validation.annotation.Validated @@ -28,43 +28,29 @@ class DatabaseController( @GetMapping("/graph/v3/databases") fun listDatabases( @RequestParam(required = false, defaultValue = "ACTIVE") status: MetadataStatus, - ): Mono>> = - v3CompatService - .getDatabases(status) - .map { ResponseEntity.ok(it) } + ): Mono>> = v3CompatService.getDatabases(status).mapToResponseEntity() @GetMapping("/graph/v3/databases/{database}") fun getDatabase( @PathVariable database: String, - ): Mono> = - v3CompatService - .getDatabase(database) - .map { ResponseEntity.ok(it) } - .defaultIfEmpty(ResponseEntity.notFound().build()) + ): Mono> = v3CompatService.getDatabase(database).mapToResponseEntity() @PostMapping("/graph/v3/databases") fun createDatabase( @Valid @RequestBody request: DatabaseCreateRequest, - ): Mono> = + ): Mono>> = v3CompatService .createDatabase(V3NameValidator.validateDatabase(request.database), request) - .map { ResponseEntity.ok(it) } + .mapToResponseEntity() @PutMapping("/graph/v3/databases/{database}") fun updateDatabase( @PathVariable database: String, @Valid @RequestBody request: DatabaseUpdateRequest, - ): Mono> = - v3CompatService - .updateDatabase(database, request) - .map { ResponseEntity.ok(it) } - .defaultIfEmpty(ResponseEntity.notFound().build()) + ): Mono>> = v3CompatService.updateDatabase(database, request).mapToResponseEntity() @DeleteMapping("/graph/v3/databases/{database}") fun deleteDatabase( @PathVariable database: String, - ): Mono> = - v3CompatService - .deleteDatabase(database) - .then(Mono.just(ResponseEntity.noContent().build())) + ): Mono>> = v3CompatService.deleteDatabase(database).mapToResponseEntity() } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseCreateRequest.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseCreateRequest.kt new file mode 100644 index 000000000..eb441e46e --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseCreateRequest.kt @@ -0,0 +1,15 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +import com.kakao.actionbase.core.Constants + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size + +data class DatabaseCreateRequest( + @field:NotBlank(message = "database is required") + @field:Pattern(regexp = Constants.Name.PATTERN, message = Constants.Name.MESSAGE) + val database: String, + @field:Size(max = Constants.Name.COMMENT_MAX_LENGTH, message = Constants.Name.COMMENT_SIZE_MESSAGE) + val comment: String, +) diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseResponse.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseResponse.kt new file mode 100644 index 000000000..0c0441477 --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseResponse.kt @@ -0,0 +1,13 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +/** + * The v3 name for what v2 calls a service. + * + * Field-for-field the same shape as the v2 `ServiceEntity` wire form, with `name` split into + * `database` and `desc` renamed to `comment`. + */ +data class DatabaseResponse( + val active: Boolean, + val database: String, + val comment: String, +) diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseUpdateRequest.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseUpdateRequest.kt new file mode 100644 index 000000000..157b4e56b --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseUpdateRequest.kt @@ -0,0 +1,11 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +import com.kakao.actionbase.core.Constants + +import jakarta.validation.constraints.Size + +data class DatabaseUpdateRequest( + val active: Boolean? = null, + @field:Size(max = Constants.Name.COMMENT_MAX_LENGTH, message = Constants.Name.COMMENT_SIZE_MESSAGE) + val comment: String? = null, +) diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DdlEnvelopes.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DdlEnvelopes.kt new file mode 100644 index 000000000..39e625b13 --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DdlEnvelopes.kt @@ -0,0 +1,19 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +import com.kakao.actionbase.v2.engine.service.ddl.DdlPage +import com.kakao.actionbase.v2.engine.service.ddl.DdlStatus + +/** + * The v3 metadata API reuses the v2 response envelopes verbatim: a listing is `{count, content}` + * and a mutation is `{status, result, message}`. These map the v2 entity inside an envelope to its + * v3-named response without reshaping the envelope itself. + */ +fun DdlStatus.mapResult(transform: (E) -> R): DdlStatus = DdlStatus(status, result?.let(transform), message) + +fun List.toPage(transform: (E) -> R): DdlPage = DdlPage(count = size.toLong(), content = map(transform)) + +/** + * A successful DDL mutation always carries its entity; callers that build a different response from + * it need the entity, not the envelope. + */ +fun DdlStatus.requireResult(): R = requireNotNull(result) { "DDL returned status=$status without a result: $message" } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableController.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableController.kt index 4623852b0..4129dd654 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableController.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableController.kt @@ -1,6 +1,8 @@ package com.kakao.actionbase.server.api.graph.v3.metadata -import com.kakao.actionbase.core.metadata.TableDescriptor +import com.kakao.actionbase.server.util.mapToResponseEntity +import com.kakao.actionbase.v2.engine.service.ddl.DdlPage +import com.kakao.actionbase.v2.engine.service.ddl.DdlStatus import org.springframework.http.ResponseEntity import org.springframework.validation.annotation.Validated @@ -27,53 +29,36 @@ class TableController( fun listTables( @PathVariable database: String, @RequestParam(required = false, defaultValue = "ACTIVE") status: MetadataStatus, - ): Mono>>> = - v3CompatService - .getTables(database, status) - .map { ResponseEntity.ok(it) } + ): Mono>> = v3CompatService.getTables(database, status).mapToResponseEntity() @GetMapping("/graph/v3/databases/{database}/tables/{table}") fun getTable( @PathVariable database: String, @PathVariable table: String, - ): Mono>> = - v3CompatService - .getTable(database, table) - .map { ResponseEntity.ok(it) } - .defaultIfEmpty(ResponseEntity.notFound().build()) + ): Mono> = v3CompatService.getTable(database, table).mapToResponseEntity() @PostMapping("/graph/v3/databases/{database}/tables") fun createTable( @PathVariable database: String, @Valid @RequestBody request: TableCreateRequest, - ): Mono>> = + ): Mono>> = v3CompatService .createTable( V3NameValidator.validateDatabase(database), V3NameValidator.validateTable(request.table), request, - ).map { ResponseEntity.ok(it) } + ).mapToResponseEntity() @PutMapping("/graph/v3/databases/{database}/tables/{table}") fun updateTable( @PathVariable database: String, @PathVariable table: String, @Valid @RequestBody request: TableUpdateRequest, - ): Mono>> = - v3CompatService - .updateTable( - database, - table, - request, - ).map { ResponseEntity.ok(it) } - .defaultIfEmpty(ResponseEntity.notFound().build()) + ): Mono>> = v3CompatService.updateTable(database, table, request).mapToResponseEntity() @DeleteMapping("/graph/v3/databases/{database}/tables/{table}") fun deleteTable( @PathVariable database: String, @PathVariable table: String, - ): Mono> = - v3CompatService - .deleteTable(database, table) - .then(Mono.just(ResponseEntity.noContent().build())) + ): Mono>> = v3CompatService.deleteTable(database, table).mapToResponseEntity() } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableCreateRequest.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableCreateRequest.kt index 10b87bc45..d93069613 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableCreateRequest.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableCreateRequest.kt @@ -1,20 +1,11 @@ package com.kakao.actionbase.server.api.graph.v3.metadata -import com.kakao.actionbase.v2.core.code.Index as V2Index -import com.kakao.actionbase.v2.core.metadata.DirectionType as V2DirectionType -import com.kakao.actionbase.v2.core.types.Field as V2Field - import com.kakao.actionbase.core.Constants -import com.kakao.actionbase.core.Constants.VERTEX_TARGET_COMMENT -import com.kakao.actionbase.core.metadata.common.ModelSchema +import com.kakao.actionbase.core.metadata.common.Cache +import com.kakao.actionbase.core.metadata.common.DirectionType +import com.kakao.actionbase.core.metadata.common.Group +import com.kakao.actionbase.core.metadata.common.Index import com.kakao.actionbase.core.metadata.common.MutationMode -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2DataType -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2DirectionType -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2Index -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2VertexType -import com.kakao.actionbase.v2.core.metadata.LabelType -import com.kakao.actionbase.v2.core.types.EdgeSchema -import com.kakao.actionbase.v2.core.types.VertexField import jakarta.validation.Valid import jakarta.validation.constraints.NotBlank @@ -26,76 +17,32 @@ data class TableCreateRequest( @field:NotBlank(message = "table is required") @field:Pattern(regexp = Constants.Name.PATTERN, message = Constants.Name.MESSAGE) val table: String, + @field:NotNull(message = "type is required") + val type: TableType, @field:NotNull(message = "schema is required") @field:Valid - val schema: ModelSchema, + val schema: TableSchema, + @field:NotNull(message = "direction is required") + val direction: DirectionType, @field:NotBlank(message = "storage is required") @field:Pattern(regexp = Constants.Name.STORAGE_URI_PATTERN, message = Constants.Name.STORAGE_URI_MESSAGE) val storage: String, + val indexes: List = emptyList(), + val groups: List = emptyList(), + val caches: List = emptyList(), val mode: MutationMode = MutationMode.SYNC, @field:Size(max = Constants.Name.COMMENT_MAX_LENGTH, message = Constants.Name.COMMENT_SIZE_MESSAGE) val comment: String, ) { - fun toV2EdgeSchema(): EdgeSchema = - when (schema) { - is ModelSchema.Edge -> - EdgeSchema( - VertexField(schema.source.type.toV2VertexType(), schema.source.comment), - VertexField(schema.target.type.toV2VertexType(), schema.target.comment), - schema.properties.map { - V2Field(it.name, it.type.toV2DataType(), it.nullable, it.comment) - }, - ) - is ModelSchema.ImmutableEdge -> - EdgeSchema( - VertexField(schema.source.type.toV2VertexType(), schema.source.comment), - VertexField(schema.target.type.toV2VertexType(), schema.target.comment), - schema.properties.map { - V2Field(it.name, it.type.toV2DataType(), it.nullable, it.comment) - }, - ) - is ModelSchema.MultiEdge -> { - val idField = V2Field("_id", schema.id.type.toV2DataType(), false, schema.id.comment) - EdgeSchema( - VertexField(schema.source.type.toV2VertexType(), schema.source.comment), - VertexField(schema.target.type.toV2VertexType(), schema.target.comment), - listOf(idField) + - schema.properties.map { - V2Field(it.name, it.type.toV2DataType(), it.nullable, it.comment) - }, - ) + init { + // The v2 metastore does not know about immutable edges, so the surface holds these invariants. + if (type == TableType.IMMUTABLE_EDGE) { + require(indexes.size <= 1) { + "immutable edge allows at most one index (scan-and-delete evicts by scanning one), got ${indexes.map { it.index }}" + } + require(direction != DirectionType.BOTH) { + "immutable edge must be single-direction OUT or IN (scan-and-delete evicts one direction), got BOTH" } - is ModelSchema.Vertex -> - EdgeSchema( - VertexField(schema.id.type.toV2VertexType(), schema.id.comment), - VertexField(com.kakao.actionbase.v2.core.types.VertexType.STRING, VERTEX_TARGET_COMMENT), - schema.properties.map { - V2Field(it.name, it.type.toV2DataType(), it.nullable, it.comment) - }, - ) - } - - fun toV2DirectionType(): V2DirectionType = - when (schema) { - is ModelSchema.Edge -> schema.direction.toV2DirectionType() - is ModelSchema.ImmutableEdge -> schema.direction.toV2DirectionType() - is ModelSchema.MultiEdge -> schema.direction.toV2DirectionType() - is ModelSchema.Vertex -> V2DirectionType.OUT - } - - fun toV2Indices(): List = - when (schema) { - is ModelSchema.Edge -> schema.indexes.map { it.toV2Index() } - is ModelSchema.ImmutableEdge -> schema.indexes.map { it.toV2Index() } - is ModelSchema.MultiEdge -> schema.indexes.map { it.toV2Index() } - is ModelSchema.Vertex -> emptyList() - } - - fun labelType(): LabelType = - when (schema) { - is ModelSchema.Edge -> LabelType.INDEXED - is ModelSchema.ImmutableEdge -> LabelType.IMMUTABLE_INDEXED - is ModelSchema.MultiEdge -> LabelType.MULTI_EDGE - is ModelSchema.Vertex -> LabelType.VERTEX } + } } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableResponse.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableResponse.kt new file mode 100644 index 000000000..ceda97e20 --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableResponse.kt @@ -0,0 +1,29 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +import com.kakao.actionbase.core.metadata.common.Cache +import com.kakao.actionbase.core.metadata.common.DirectionType +import com.kakao.actionbase.core.metadata.common.Group +import com.kakao.actionbase.core.metadata.common.Index +import com.kakao.actionbase.core.metadata.common.MutationMode + +/** + * The v3 name for what v2 calls a label. + * + * Field for field the v2 `LabelEntity` wire form: `name` split into `database` and `table`, + * `desc` renamed to `comment`, `dirType` to `direction`, `indices` to `indexes`, and the v2-only + * `event` / `readOnly` flags dropped (the server derives them). + */ +data class TableResponse( + val active: Boolean, + val database: String, + val table: String, + val comment: String, + val type: TableType, + val schema: TableSchema, + val direction: DirectionType, + val storage: String, + val indexes: List = emptyList(), + val groups: List = emptyList(), + val caches: List = emptyList(), + val mode: MutationMode = MutationMode.SYNC, +) diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableSchema.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableSchema.kt new file mode 100644 index 000000000..64a7991ef --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableSchema.kt @@ -0,0 +1,44 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +import com.kakao.actionbase.v2.core.types.DataType +import com.kakao.actionbase.v2.core.types.VertexType + +import com.fasterxml.jackson.annotation.JsonProperty + +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull + +/** + * The v3 name for a v2 edge schema: `src`/`tgt`/`fields` renamed to `source`/`target`/`properties`, + * and each `desc` renamed to `comment`. The structure and the type values are v2's, unchanged. + * + * A vertex table carries its id in [source] and a placeholder in [target]; a multi-edge table + * carries its id as the `_id` property. Both follow v2 exactly. + */ +data class TableSchema( + @field:NotNull(message = "schema.source is required") + @field:Valid + val source: Key, + @field:NotNull(message = "schema.target is required") + @field:Valid + val target: Key, + @field:Valid + val properties: List = emptyList(), +) { + data class Key( + @field:NotNull(message = "type is required") + val type: VertexType, + val comment: String = "", + ) + + data class Property( + @field:NotBlank(message = "name is required") + val name: String, + @field:NotNull(message = "type is required") + val type: DataType, + @JsonProperty("nullable") + val nullable: Boolean = false, + val comment: String = "", + ) +} diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableType.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableType.kt new file mode 100644 index 000000000..d3aa6142c --- /dev/null +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableType.kt @@ -0,0 +1,14 @@ +package com.kakao.actionbase.server.api.graph.v3.metadata + +/** + * The v3 name for what v2 calls a label type. + * + * One value per v2 [com.kakao.actionbase.v2.core.metadata.LabelType] that the v3 surface exposes. + * v2's `HASH` and `NIL` have no v3 name; they read back as [EDGE] and cannot be created. + */ +enum class TableType { + EDGE, + IMMUTABLE_EDGE, + MULTI_EDGE, + VERTEX, +} diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableUpdateRequest.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableUpdateRequest.kt index ec9e1ab8f..0228c5afb 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableUpdateRequest.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableUpdateRequest.kt @@ -1,93 +1,26 @@ package com.kakao.actionbase.server.api.graph.v3.metadata -import com.kakao.actionbase.v2.core.code.Index as V2Index -import com.kakao.actionbase.v2.core.types.Field as V2Field - import com.kakao.actionbase.core.Constants import com.kakao.actionbase.core.metadata.common.Cache import com.kakao.actionbase.core.metadata.common.Group -import com.kakao.actionbase.core.metadata.common.ModelSchema +import com.kakao.actionbase.core.metadata.common.Index import com.kakao.actionbase.core.metadata.common.MutationMode -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2DataType -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2Index -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2VertexType -import com.kakao.actionbase.v2.core.types.EdgeSchema -import com.kakao.actionbase.v2.core.types.VertexField import jakarta.validation.Valid import jakarta.validation.constraints.Size +/** + * Mirrors the updatable set of the v2 `LabelUpdateRequest`. A table's `type` and `direction` are + * fixed at creation, so neither appears here; omitting a field leaves it untouched. + */ data class TableUpdateRequest( val active: Boolean? = null, @field:Valid - val schema: ModelSchema? = null, + val schema: TableSchema? = null, + val indexes: List? = null, + val groups: List? = null, + val caches: List? = null, val mode: MutationMode? = null, @field:Size(max = Constants.Name.COMMENT_MAX_LENGTH, message = Constants.Name.COMMENT_SIZE_MESSAGE) val comment: String? = null, -) { - fun toV2EdgeSchema(): EdgeSchema? = - schema?.let { - when (it) { - is ModelSchema.Edge -> - EdgeSchema( - VertexField(it.source.type.toV2VertexType(), it.source.comment), - VertexField(it.target.type.toV2VertexType(), it.target.comment), - it.properties.map { prop -> - V2Field(prop.name, prop.type.toV2DataType(), prop.nullable, prop.comment) - }, - ) - is ModelSchema.ImmutableEdge -> - EdgeSchema( - VertexField(it.source.type.toV2VertexType(), it.source.comment), - VertexField(it.target.type.toV2VertexType(), it.target.comment), - it.properties.map { prop -> - V2Field(prop.name, prop.type.toV2DataType(), prop.nullable, prop.comment) - }, - ) - is ModelSchema.MultiEdge -> { - val idField = V2Field("_id", it.id.type.toV2DataType(), false, it.id.comment) - EdgeSchema( - VertexField(it.source.type.toV2VertexType(), it.source.comment), - VertexField(it.target.type.toV2VertexType(), it.target.comment), - listOf(idField) + - it.properties.map { prop -> - V2Field(prop.name, prop.type.toV2DataType(), prop.nullable, prop.comment) - }, - ) - } - is ModelSchema.Vertex -> null - } - } - - fun toV2Indices(): List? = - schema?.let { - when (it) { - is ModelSchema.Edge -> it.indexes.map { idx -> idx.toV2Index() } - is ModelSchema.ImmutableEdge -> it.indexes.map { idx -> idx.toV2Index() } - is ModelSchema.MultiEdge -> it.indexes.map { idx -> idx.toV2Index() } - // Vertex has no indices; null keeps the update a no-op instead of overwriting with `[]`. - is ModelSchema.Vertex -> null - } - } - - fun toV2Groups(): List? = - schema?.let { - when (it) { - is ModelSchema.Edge -> it.groups - is ModelSchema.ImmutableEdge -> it.groups - is ModelSchema.MultiEdge -> it.groups - is ModelSchema.Vertex -> null - } - } - - fun toV2Caches(): List? = - schema?.let { - when (it) { - is ModelSchema.Edge -> it.caches - // Immutable edges have no caches; null keeps the update a no-op for this field. - is ModelSchema.ImmutableEdge -> null - is ModelSchema.MultiEdge -> it.caches - is ModelSchema.Vertex -> null - } - } -} +) diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3CompatService.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3CompatService.kt index cdfbf3ff4..a68333296 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3CompatService.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3CompatService.kt @@ -10,18 +10,19 @@ import com.kakao.actionbase.v2.engine.service.ddl.ServiceCreateRequest as V2Serv import com.kakao.actionbase.v2.engine.service.ddl.ServiceDeleteRequest as V2ServiceDeleteRequest import com.kakao.actionbase.v2.engine.service.ddl.ServiceUpdateRequest as V2ServiceUpdateRequest -import com.kakao.actionbase.core.metadata.AliasDescriptor -import com.kakao.actionbase.core.metadata.DatabaseDescriptor -import com.kakao.actionbase.core.metadata.TableDescriptor -import com.kakao.actionbase.core.metadata.common.ModelSchema -import com.kakao.actionbase.core.metadata.payload.DatabaseCreateRequest -import com.kakao.actionbase.core.metadata.payload.DatabaseUpdateRequest +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.isReadOnlyInV2 +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toAliasResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toDatabaseResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toLabelType +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toTableResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2DirectionType +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2EdgeSchema +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2Index import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2MutationMode -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3AliasDescriptor -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3DatabaseDescriptor -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3TableDescriptor import com.kakao.actionbase.v2.engine.Graph import com.kakao.actionbase.v2.engine.entity.EntityName +import com.kakao.actionbase.v2.engine.service.ddl.DdlPage +import com.kakao.actionbase.v2.engine.service.ddl.DdlStatus import org.springframework.stereotype.Service @@ -31,39 +32,32 @@ import reactor.core.publisher.Mono class V3CompatService( private val graph: Graph, ) { - private val tenant: String - get() = EntityName.tenant - // region Database CRUD (using V2 serviceDdl) - fun getDatabase(database: String): Mono = + fun getDatabase(database: String): Mono = graph.serviceDdl .getSingle(EntityName.fromOrigin(database)) - .map { it.toV3DatabaseDescriptor(tenant) } + .map { it.toDatabaseResponse() } - fun getDatabases(status: MetadataStatus = MetadataStatus.ACTIVE): Mono> = + fun getDatabases(status: MetadataStatus = MetadataStatus.ACTIVE): Mono> = graph.serviceDdl .getAll(EntityName.origin) - .map { page -> - page.content - .filter { status.matches(it.active) } - .map { it.toV3DatabaseDescriptor(tenant) } - } + .map { page -> page.content.filter { status.matches(it.active) }.toPage { it.toDatabaseResponse() } } fun createDatabase( database: String, request: DatabaseCreateRequest, - ): Mono { + ): Mono> { val v2Request = V2ServiceCreateRequest(desc = request.comment) return graph.serviceDdl .create(EntityName.fromOrigin(database), v2Request) - .handle { status, sink -> status.result?.let { sink.next(it.toV3DatabaseDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toDatabaseResponse() } } } fun updateDatabase( database: String, request: DatabaseUpdateRequest, - ): Mono { + ): Mono> { val v2Request = V2ServiceUpdateRequest( active = request.active, @@ -71,13 +65,13 @@ class V3CompatService( ) return graph.serviceDdl .update(EntityName.fromOrigin(database), v2Request) - .handle { status, sink -> status.result?.let { sink.next(it.toV3DatabaseDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toDatabaseResponse() } } } - fun deleteDatabase(database: String): Mono = + fun deleteDatabase(database: String): Mono> = graph.serviceDdl .delete(EntityName.fromOrigin(database), V2ServiceDeleteRequest()) - .handle { status, sink -> status.result?.let { sink.next(it.toV3DatabaseDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toDatabaseResponse() } } // endregion @@ -86,91 +80,72 @@ class V3CompatService( fun getTable( database: String, table: String, - ): Mono> = + ): Mono = graph.labelDdl .getSingle(EntityName(database, table)) - .map { it.toV3TableDescriptor(tenant) } + .map { it.toTableResponse() } fun getTables( database: String, status: MetadataStatus = MetadataStatus.ACTIVE, - ): Mono>> = + ): Mono> = graph.labelDdl .getAll(EntityName(database)) - .map { page -> - page.content - .filter { status.matches(it.active) } - .map { it.toV3TableDescriptor(tenant) } - } + .map { page -> page.content.filter { status.matches(it.active) }.toPage { it.toTableResponse() } } fun createTable( database: String, table: String, request: TableCreateRequest, - ): Mono> { - val isMultiEdge = request.schema is ModelSchema.MultiEdge - val groups = - when (val s = request.schema) { - is ModelSchema.Edge -> s.groups - is ModelSchema.ImmutableEdge -> s.groups - is ModelSchema.MultiEdge -> s.groups - is ModelSchema.Vertex -> emptyList() - } - val caches = - when (val s = request.schema) { - is ModelSchema.Edge -> s.caches - is ModelSchema.ImmutableEdge -> emptyList() - is ModelSchema.MultiEdge -> s.caches - is ModelSchema.Vertex -> emptyList() - } + ): Mono> { val v2Request = V2LabelCreateRequest( desc = request.comment, - type = request.labelType(), - schema = request.toV2EdgeSchema(), - dirType = request.toV2DirectionType(), + type = request.type.toLabelType(), + schema = request.schema.toV2EdgeSchema(), + dirType = request.direction.toV2DirectionType(), storage = request.storage, - groups = groups, - indices = request.toV2Indices(), - caches = caches, + groups = request.groups, + indices = request.indexes.map { it.toV2Index() }, + caches = request.caches, event = false, - readOnly = isMultiEdge, + readOnly = request.type.isReadOnlyInV2(), mode = request.mode.toV2MutationMode(), ) return graph.labelDdl .create(EntityName(database, table), v2Request) - .handle { status, sink -> status.result?.let { sink.next(it.toV3TableDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toTableResponse() } } } fun updateTable( database: String, table: String, request: TableUpdateRequest, - ): Mono> { + ): Mono> { val v2Request = V2LabelUpdateRequest( active = request.active, desc = request.comment, type = null, - schema = request.toV2EdgeSchema(), - groups = request.toV2Groups(), - indices = request.toV2Indices(), + schema = request.schema?.toV2EdgeSchema(), + groups = request.groups, + indices = request.indexes?.map { it.toV2Index() }, readOnly = null, mode = request.mode?.toV2MutationMode(), - caches = request.toV2Caches(), + caches = request.caches, ) return graph.labelDdl .update(EntityName(database, table), v2Request) - .handle { status, sink -> status.result?.let { sink.next(it.toV3TableDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toTableResponse() } } } fun deleteTable( database: String, table: String, - ): Mono> = + ): Mono> = graph.labelDdl .delete(EntityName(database, table), V2LabelDeleteRequest()) - .handle { status, sink -> status.result?.let { sink.next(it.toV3TableDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toTableResponse() } } // endregion @@ -179,28 +154,24 @@ class V3CompatService( fun getAlias( database: String, alias: String, - ): Mono = + ): Mono = graph.aliasDdl .getSingle(EntityName(database, alias)) - .map { it.toV3AliasDescriptor(tenant) } + .map { it.toAliasResponse() } fun getAliases( database: String, status: MetadataStatus = MetadataStatus.ACTIVE, - ): Mono> = + ): Mono> = graph.aliasDdl .getAll(EntityName(database)) - .map { page -> - page.content - .filter { status.matches(it.active) } - .map { it.toV3AliasDescriptor(tenant) } - } + .map { page -> page.content.filter { status.matches(it.active) }.toPage { it.toAliasResponse() } } fun createAlias( database: String, alias: String, request: AliasCreateRequest, - ): Mono { + ): Mono> { val v2Request = V2AliasCreateRequest( desc = request.comment, @@ -208,14 +179,14 @@ class V3CompatService( ) return graph.aliasDdl .create(EntityName(database, alias), v2Request) - .handle { status, sink -> status.result?.let { sink.next(it.toV3AliasDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toAliasResponse() } } } fun updateAlias( database: String, alias: String, request: AliasUpdateRequest, - ): Mono { + ): Mono> { val v2Request = V2AliasUpdateRequest( active = request.active, @@ -224,16 +195,16 @@ class V3CompatService( ) return graph.aliasDdl .update(EntityName(database, alias), v2Request) - .handle { status, sink -> status.result?.let { sink.next(it.toV3AliasDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toAliasResponse() } } } fun deleteAlias( database: String, alias: String, - ): Mono = + ): Mono> = graph.aliasDdl .delete(EntityName(database, alias), V2AliasDeleteRequest()) - .handle { status, sink -> status.result?.let { sink.next(it.toV3AliasDescriptor(tenant)) } } + .map { status -> status.mapResult { it.toAliasResponse() } } // endregion } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverter.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverter.kt index 70aa1eb2d..e61e49bf6 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverter.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverter.kt @@ -2,46 +2,40 @@ package com.kakao.actionbase.server.api.graph.v3.metadata import com.kakao.actionbase.core.java.codec.common.hbase.Order as V3Order import com.kakao.actionbase.core.metadata.common.DirectionType as V3DirectionType -import com.kakao.actionbase.core.metadata.common.Field as V3Field import com.kakao.actionbase.core.metadata.common.Index as V3Index import com.kakao.actionbase.core.metadata.common.MutationMode as V3MutationMode import com.kakao.actionbase.v2.core.code.Index as V2Index import com.kakao.actionbase.v2.core.code.hbase.Order as V2Order import com.kakao.actionbase.v2.core.metadata.DirectionType as V2DirectionType import com.kakao.actionbase.v2.core.metadata.MutationMode as V2MutationMode -import com.kakao.actionbase.v2.core.types.DataType as V2DataType import com.kakao.actionbase.v2.core.types.Field as V2Field -import com.kakao.actionbase.core.metadata.AliasDescriptor -import com.kakao.actionbase.core.metadata.DatabaseDescriptor -import com.kakao.actionbase.core.metadata.TableDescriptor -import com.kakao.actionbase.core.metadata.common.Cache -import com.kakao.actionbase.core.metadata.common.Group import com.kakao.actionbase.core.metadata.common.IndexField -import com.kakao.actionbase.core.metadata.common.ModelSchema -import com.kakao.actionbase.core.metadata.common.StructField -import com.kakao.actionbase.core.types.PrimitiveType import com.kakao.actionbase.v2.core.metadata.LabelType import com.kakao.actionbase.v2.core.types.EdgeSchema import com.kakao.actionbase.v2.core.types.VertexField -import com.kakao.actionbase.v2.core.types.VertexType import com.kakao.actionbase.v2.engine.entity.AliasEntity import com.kakao.actionbase.v2.engine.entity.EntityName import com.kakao.actionbase.v2.engine.entity.LabelEntity import com.kakao.actionbase.v2.engine.entity.ServiceEntity +/** + * Translates between the v2 metastore entities and the v3 metadata surface. + * + * The two carry the same information in the same structure; only the names differ. Every function + * here is a rename, never a reshape. + */ object V3MetadataConverter { - // region Database (V3 DatabaseDescriptor <-> V2 ServiceEntity) + // region Database (V3 DatabaseResponse <-> V2 ServiceEntity) - fun ServiceEntity.toV3DatabaseDescriptor(tenant: String): DatabaseDescriptor = - DatabaseDescriptor( - tenant = tenant, - database = name.nameNotNull, + fun ServiceEntity.toDatabaseResponse(): DatabaseResponse = + DatabaseResponse( active = active, + database = name.nameNotNull, comment = desc, ) - fun DatabaseDescriptor.toV2ServiceEntity(): ServiceEntity = + fun DatabaseResponse.toV2ServiceEntity(): ServiceEntity = ServiceEntity( active = active, name = EntityName.fromOrigin(database), @@ -50,127 +44,55 @@ object V3MetadataConverter { // endregion - // region Table (V3 TableDescriptor <-> V2 LabelEntity) + // region Table (V3 TableResponse <-> V2 LabelEntity) - fun LabelEntity.toV3TableDescriptorEdge(tenant: String): TableDescriptor.Edge = - TableDescriptor.Edge( - tenant = tenant, - database = name.service, - table = name.nameNotNull, - schema = schema.toV3ModelSchemaEdge(dirType.toV3DirectionType(), indices, groups, caches), - mode = mode.toV3MutationMode(), - storage = storage, + fun LabelEntity.toTableResponse(): TableResponse = + TableResponse( active = active, - comment = desc, - ) - - fun LabelEntity.toV3TableDescriptorImmutableEdge(tenant: String): TableDescriptor.ImmutableEdge = - TableDescriptor.ImmutableEdge( - tenant = tenant, database = name.service, table = name.nameNotNull, - schema = schema.toV3ModelSchemaImmutableEdge(dirType.toV3DirectionType(), indices, groups), - mode = mode.toV3MutationMode(), - storage = storage, - active = active, comment = desc, - ) - - fun LabelEntity.toV3TableDescriptorMultiEdge(tenant: String): TableDescriptor.MultiEdge = - TableDescriptor.MultiEdge( - tenant = tenant, - database = name.service, - table = name.nameNotNull, - schema = schema.toV3ModelSchemaMultiEdge(dirType.toV3DirectionType(), indices, groups, caches), - mode = mode.toV3MutationMode(), + type = type.toTableType(), + schema = schema.toTableSchema(), + direction = dirType.toV3DirectionType(), storage = storage, - active = active, - comment = desc, - ) - - fun LabelEntity.toV3TableDescriptorVertex(tenant: String): TableDescriptor.Vertex = - TableDescriptor.Vertex( - tenant = tenant, - database = name.service, - table = name.nameNotNull, - schema = schema.toV3ModelSchemaVertex(), + indexes = indices.map { it.toV3Index() }, + groups = groups, + caches = caches, mode = mode.toV3MutationMode(), - storage = storage, - active = active, - comment = desc, - ) - - fun LabelEntity.toV3TableDescriptor(tenant: String): TableDescriptor<*> = - when (type) { - LabelType.IMMUTABLE_INDEXED -> toV3TableDescriptorImmutableEdge(tenant) - LabelType.MULTI_EDGE -> toV3TableDescriptorMultiEdge(tenant) - LabelType.VERTEX -> toV3TableDescriptorVertex(tenant) - else -> toV3TableDescriptorEdge(tenant) - } - - fun TableDescriptor.Edge.toV2LabelEntity(): LabelEntity = - LabelEntity( - active = active, - name = EntityName(database, table), - desc = comment, - type = LabelType.INDEXED, - schema = schema.toV2EdgeSchema(), - dirType = schema.direction.toV2DirectionType(), - storage = storage, - indices = schema.indexes.map { it.toV2Index() }, - groups = schema.groups, - event = false, - readOnly = false, - mode = mode.toV2MutationMode(), ) - fun TableDescriptor.ImmutableEdge.toV2LabelEntity(): LabelEntity = + fun TableResponse.toV2LabelEntity(): LabelEntity = LabelEntity( active = active, name = EntityName(database, table), desc = comment, - type = LabelType.IMMUTABLE_INDEXED, + type = type.toLabelType(), schema = schema.toV2EdgeSchema(), - dirType = schema.direction.toV2DirectionType(), + dirType = direction.toV2DirectionType(), storage = storage, - indices = schema.indexes.map { it.toV2Index() }, - groups = schema.groups, - event = false, - readOnly = false, - mode = mode.toV2MutationMode(), - ) - - fun TableDescriptor.MultiEdge.toV2LabelEntity(): LabelEntity = - LabelEntity( - active = active, - name = EntityName(database, table), - desc = comment, - type = LabelType.MULTI_EDGE, - schema = schema.toV2EdgeSchema(), - dirType = schema.direction.toV2DirectionType(), - storage = storage, - indices = schema.indexes.map { it.toV2Index() }, - groups = schema.groups, + indices = indexes.map { it.toV2Index() }, + groups = groups, + caches = caches, event = false, - readOnly = true, + readOnly = type.isReadOnlyInV2(), mode = mode.toV2MutationMode(), ) // endregion - // region Alias (V3 AliasDescriptor <-> V2 AliasEntity) + // region Alias (V3 AliasResponse <-> V2 AliasEntity) - fun AliasEntity.toV3AliasDescriptor(tenant: String): AliasDescriptor = - AliasDescriptor( - tenant = tenant, + fun AliasEntity.toAliasResponse(): AliasResponse = + AliasResponse( + active = active, database = name.service, alias = name.nameNotNull, table = target.nameNotNull, - active = active, comment = desc, ) - fun AliasDescriptor.toV2AliasEntity(): AliasEntity = + fun AliasResponse.toV2AliasEntity(): AliasEntity = AliasEntity( active = active, name = EntityName(database, alias), @@ -180,6 +102,33 @@ object V3MetadataConverter { // endregion + // region TableType conversion + + /** + * v2's `HASH` and `NIL` have no v3 name. They read back as [TableType.EDGE], which is what the + * v3 runtime already treats them as; neither can be created through the v3 surface. + */ + fun LabelType.toTableType(): TableType = + when (this) { + LabelType.IMMUTABLE_INDEXED -> TableType.IMMUTABLE_EDGE + LabelType.MULTI_EDGE -> TableType.MULTI_EDGE + LabelType.VERTEX -> TableType.VERTEX + LabelType.INDEXED, LabelType.HASH, LabelType.NIL -> TableType.EDGE + } + + fun TableType.toLabelType(): LabelType = + when (this) { + TableType.EDGE -> LabelType.INDEXED + TableType.IMMUTABLE_EDGE -> LabelType.IMMUTABLE_INDEXED + TableType.MULTI_EDGE -> LabelType.MULTI_EDGE + TableType.VERTEX -> LabelType.VERTEX + } + + /** The v2 engine refuses to mutate a multi-edge label, so it must be stored as read-only. */ + fun TableType.isReadOnlyInV2(): Boolean = this == TableType.MULTI_EDGE + + // endregion + // region MutationMode conversion fun V2MutationMode.toV3MutationMode(): V3MutationMode = @@ -217,135 +166,35 @@ object V3MetadataConverter { // endregion - // region Schema conversion (V3 ModelSchema <-> V2 EdgeSchema) - - fun EdgeSchema.toV3ModelSchemaVertex(): ModelSchema.Vertex = - ModelSchema.Vertex( - id = V3Field(type = src.type.toV3PrimitiveType(), comment = src.desc), - properties = fields.map { it.toV3StructField() }, - ) - - fun EdgeSchema.toV3ModelSchemaEdge( - direction: V3DirectionType, - indices: List, - groups: List, - caches: List, - ): ModelSchema.Edge = - ModelSchema.Edge( - source = - V3Field( - type = src.type.toV3PrimitiveType(), - comment = src.desc, - ), - target = - V3Field( - type = tgt.type.toV3PrimitiveType(), - comment = tgt.desc, - ), - properties = fields.map { it.toV3StructField() }, - direction = direction, - indexes = indices.map { it.toV3Index() }, - groups = groups, - caches = caches, - ) - - fun EdgeSchema.toV3ModelSchemaImmutableEdge( - direction: V3DirectionType, - indices: List, - groups: List, - ): ModelSchema.ImmutableEdge = - ModelSchema.ImmutableEdge( - source = - V3Field( - type = src.type.toV3PrimitiveType(), - comment = src.desc, - ), - target = - V3Field( - type = tgt.type.toV3PrimitiveType(), - comment = tgt.desc, - ), - properties = fields.map { it.toV3StructField() }, - direction = direction, - indexes = indices.map { it.toV3Index() }, - groups = groups, - ) - - fun EdgeSchema.toV3ModelSchemaMultiEdge( - direction: V3DirectionType, - indices: List, - groups: List, - caches: List, - ): ModelSchema.MultiEdge { - val idField = - fields.find { it.name == "_id" } - ?: throw IllegalArgumentException("MultiEdge schema must have _id field") - return ModelSchema.MultiEdge( - id = - V3Field( - type = idField.type.toV3PrimitiveType(), - comment = idField.desc, - ), - source = - V3Field( - type = src.type.toV3PrimitiveType(), - comment = src.desc, - ), - target = - V3Field( - type = tgt.type.toV3PrimitiveType(), - comment = tgt.desc, - ), - properties = fields.filterNot { it.name == "_id" }.map { it.toV3StructField() }, - direction = direction, - indexes = indices.map { it.toV3Index() }, - groups = groups, - caches = caches, - ) - } + // region Schema conversion (V3 TableSchema <-> V2 EdgeSchema) - fun ModelSchema.Edge.toV2EdgeSchema(): EdgeSchema = - EdgeSchema( - VertexField(source.type.toV2VertexType(), source.comment), - VertexField(target.type.toV2VertexType(), target.comment), - properties.map { it.toV2Field() }, + fun EdgeSchema.toTableSchema(): TableSchema = + TableSchema( + source = TableSchema.Key(type = src.type, comment = src.desc), + target = TableSchema.Key(type = tgt.type, comment = tgt.desc), + properties = fields.map { it.toTableSchemaProperty() }, ) - fun ModelSchema.ImmutableEdge.toV2EdgeSchema(): EdgeSchema = + fun TableSchema.toV2EdgeSchema(): EdgeSchema = EdgeSchema( - VertexField(source.type.toV2VertexType(), source.comment), - VertexField(target.type.toV2VertexType(), target.comment), + VertexField(source.type, source.comment), + VertexField(target.type, target.comment), properties.map { it.toV2Field() }, ) - fun ModelSchema.MultiEdge.toV2EdgeSchema(): EdgeSchema { - val idField = V2Field("_id", id.type.toV2DataType(), false, id.comment) - return EdgeSchema( - VertexField(source.type.toV2VertexType(), source.comment), - VertexField(target.type.toV2VertexType(), target.comment), - listOf(idField) + properties.map { it.toV2Field() }, - ) - } - // endregion // region Field conversion - fun V2Field.toV3StructField(): StructField = - StructField( + fun V2Field.toTableSchemaProperty(): TableSchema.Property = + TableSchema.Property( name = name, - type = type.toV3PrimitiveType(), - comment = desc, + type = type, nullable = isNullable, + comment = desc, ) - fun StructField.toV2Field(): V2Field = - V2Field( - name, - type.toV2DataType(), - nullable, - comment, - ) + fun TableSchema.Property.toV2Field(): V2Field = V2Field(name, type, nullable, comment) // endregion @@ -382,47 +231,4 @@ object V3MetadataConverter { } // endregion - - // region Type conversion - - fun VertexType.toV3PrimitiveType(): PrimitiveType = - when (this) { - VertexType.LONG -> PrimitiveType.LONG - VertexType.STRING -> PrimitiveType.STRING - } - - fun PrimitiveType.toV2VertexType(): VertexType = - when (this) { - PrimitiveType.LONG -> VertexType.LONG - PrimitiveType.STRING -> VertexType.STRING - else -> throw IllegalArgumentException("Cannot convert $this to VertexType") - } - - fun V2DataType.toV3PrimitiveType(): PrimitiveType = - when (this) { - V2DataType.BOOLEAN -> PrimitiveType.BOOLEAN - V2DataType.BYTE -> PrimitiveType.BYTE - V2DataType.SHORT -> PrimitiveType.SHORT - V2DataType.INT -> PrimitiveType.INT - V2DataType.LONG -> PrimitiveType.LONG - V2DataType.FLOAT -> PrimitiveType.FLOAT - V2DataType.DOUBLE -> PrimitiveType.DOUBLE - V2DataType.STRING -> PrimitiveType.STRING - V2DataType.DECIMAL, V2DataType.JSON -> PrimitiveType.OBJECT - } - - fun PrimitiveType.toV2DataType(): V2DataType = - when (this) { - PrimitiveType.BOOLEAN -> V2DataType.BOOLEAN - PrimitiveType.BYTE -> V2DataType.BYTE - PrimitiveType.SHORT -> V2DataType.SHORT - PrimitiveType.INT -> V2DataType.INT - PrimitiveType.LONG -> V2DataType.LONG - PrimitiveType.FLOAT -> V2DataType.FLOAT - PrimitiveType.DOUBLE -> V2DataType.DOUBLE - PrimitiveType.STRING -> V2DataType.STRING - PrimitiveType.OBJECT -> V2DataType.JSON - } - - // endregion } diff --git a/server/src/main/kotlin/com/kakao/actionbase/server/api/queue/v1/metadata/QueueMetadataService.kt b/server/src/main/kotlin/com/kakao/actionbase/server/api/queue/v1/metadata/QueueMetadataService.kt index beeb7e4ea..e7701e334 100644 --- a/server/src/main/kotlin/com/kakao/actionbase/server/api/queue/v1/metadata/QueueMetadataService.kt +++ b/server/src/main/kotlin/com/kakao/actionbase/server/api/queue/v1/metadata/QueueMetadataService.kt @@ -1,21 +1,22 @@ package com.kakao.actionbase.server.api.queue.v1.metadata import com.kakao.actionbase.core.java.codec.common.hbase.Order -import com.kakao.actionbase.core.metadata.TableDescriptor import com.kakao.actionbase.core.metadata.common.DirectionType -import com.kakao.actionbase.core.metadata.common.Field import com.kakao.actionbase.core.metadata.common.Index import com.kakao.actionbase.core.metadata.common.IndexField -import com.kakao.actionbase.core.metadata.common.ModelSchema import com.kakao.actionbase.core.metadata.common.MutationMode -import com.kakao.actionbase.core.metadata.common.StructField -import com.kakao.actionbase.core.types.PrimitiveType import com.kakao.actionbase.engine.queue.QueueMetadata import com.kakao.actionbase.engine.queue.QueueMetadataCodec import com.kakao.actionbase.engine.queue.QueueSchema import com.kakao.actionbase.server.api.graph.v3.metadata.TableCreateRequest +import com.kakao.actionbase.server.api.graph.v3.metadata.TableResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.TableSchema +import com.kakao.actionbase.server.api.graph.v3.metadata.TableType import com.kakao.actionbase.server.api.graph.v3.metadata.TableUpdateRequest import com.kakao.actionbase.server.api.graph.v3.metadata.V3CompatService +import com.kakao.actionbase.server.api.graph.v3.metadata.requireResult +import com.kakao.actionbase.v2.core.types.DataType +import com.kakao.actionbase.v2.core.types.VertexType import com.kakao.actionbase.v2.engine.Graph import org.springframework.http.HttpStatus @@ -35,22 +36,23 @@ class QueueMetadataService( request: QueueCreateRequest, ): Mono { val schema = - ModelSchema.ImmutableEdge( - source = Field(type = PrimitiveType.LONG, comment = "partition"), - target = Field(type = PrimitiveType.STRING, comment = "message id (ULID)"), + TableSchema( + source = TableSchema.Key(type = VertexType.LONG, comment = "partition"), + target = TableSchema.Key(type = VertexType.STRING, comment = "message id (ULID)"), properties = listOf( - StructField(QueueSchema.SEQ, PrimitiveType.LONG, "order / due time", false), - StructField(QueueSchema.VALUE, PrimitiveType.STRING, "opaque value (json)", true), + TableSchema.Property(QueueSchema.SEQ, DataType.LONG, false, "order / due time"), + TableSchema.Property(QueueSchema.VALUE, DataType.STRING, true, "opaque value (json)"), ), - direction = DirectionType.OUT, - indexes = listOf(Index(QueueSchema.SEQ, listOf(IndexField(QueueSchema.SEQ, Order.ASC)))), ) val tableRequest = TableCreateRequest( table = request.queue, + type = TableType.IMMUTABLE_EDGE, schema = schema, + direction = DirectionType.OUT, storage = request.storage, + indexes = listOf(Index(QueueSchema.SEQ, listOf(IndexField(QueueSchema.SEQ, Order.ASC)))), mode = MutationMode.SYNC, comment = QueueMetadataCodec.encode(QueueMetadata(request.partitions)), ) @@ -58,8 +60,8 @@ class QueueMetadataService( .createTable(namespace, request.queue, tableRequest) // Force a label-registry sync so the runtime (Graph.getLabel) sees the new queue at once, // rather than waiting for the next metastore reload (which may be disabled locally). - .flatMap { descriptor -> graph.updateLabels().thenReturn(descriptor) } - .map { it.toQueueResponse() } + .flatMap { status -> graph.updateLabels().thenReturn(status) } + .map { it.requireResult().toQueueResponse() } } fun getQueue( @@ -71,15 +73,18 @@ class QueueMetadataService( namespace: String, queue: String, active: Boolean, - ): Mono = compat.updateTable(namespace, queue, TableUpdateRequest(active = active)).map { it.toQueueResponse() } + ): Mono = + compat + .updateTable(namespace, queue, TableUpdateRequest(active = active)) + .map { it.requireResult().toQueueResponse() } /** Deletion is guarded: a queue must be deactivated (see [setActive]) before it can be removed. */ fun deleteQueue( namespace: String, queue: String, ): Mono = - compat.getTable(namespace, queue).flatMap { descriptor -> - if (descriptor.active) { + compat.getTable(namespace, queue).flatMap { table -> + if (table.active) { Mono.error( ResponseStatusException( HttpStatus.CONFLICT, @@ -91,7 +96,7 @@ class QueueMetadataService( } } - private fun TableDescriptor<*>.toQueueResponse(): QueueDescriptorResponse { + private fun TableResponse.toQueueResponse(): QueueDescriptorResponse { val meta = QueueMetadataCodec.decode(comment) ?: throw IllegalArgumentException("`$database.$table` is not a queue/v1 table") diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/admin/HBaseAdminTeardownGuardE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/admin/HBaseAdminTeardownGuardE2ETest.kt index 334afab42..638513b13 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/admin/HBaseAdminTeardownGuardE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/admin/HBaseAdminTeardownGuardE2ETest.kt @@ -35,15 +35,15 @@ class HBaseAdminTeardownGuardE2ETest : E2ETestBase() { "storage": "datastore://v2_ns/v2_bound", "mode": "SYNC", "comment": "v2 guard e2e", + "type": "EDGE", "schema": { - "type": "EDGE", - "direction": "OUT", - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "indexes": [], - "groups": [] - } + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [] + }, + "direction": "OUT", + "indexes": [], + "groups": [] } """.trimIndent(), ).exchange() diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/edge/V2EdgeStorageOpsSpec.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/edge/V2EdgeStorageOpsSpec.kt index d1003c5c4..dec2cee93 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/edge/V2EdgeStorageOpsSpec.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v2/edge/V2EdgeStorageOpsSpec.kt @@ -33,17 +33,17 @@ class V2EdgeStorageOpsSpec : E2ETestBase() { """ { "table": "$label", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "long", "comment": "score"} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "score", "type": "LONG", "comment": "score"} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v2_storage_ops_label", "mode": "SYNC", "comment": "v2 storage ops label" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ActionbaseQueryE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ActionbaseQueryE2ETest.kt index 7c39e1c89..e163e8a91 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ActionbaseQueryE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ActionbaseQueryE2ETest.kt @@ -51,19 +51,19 @@ class ActionbaseQueryE2ETest : E2ETestBase() { """ { "table": "$hop1Table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "source"}, - "target": {"type": "long", "comment": "target"}, + "source": {"type": "LONG", "comment": "source"}, + "target": {"type": "LONG", "comment": "target"}, "properties": [ - {"name": "createdAt", "type": "long", "comment": "ts", "nullable": true} - ], - "direction": "BOTH", - "indexes": [ - {"index": "created_at_desc", "fields": [{"field": "createdAt", "order": "DESC"}]} - ], - "groups": [] + {"name": "createdAt", "type": "LONG", "comment": "ts", "nullable": true} + ] }, + "direction": "BOTH", + "indexes": [ + {"index": "created_at_desc", "fields": [{"field": "createdAt", "order": "DESC"}]} + ], + "groups": [], "storage": "datastore://multihop_ns/follows", "mode": "SYNC", "comment": "hop1" @@ -84,35 +84,35 @@ class ActionbaseQueryE2ETest : E2ETestBase() { """ { "table": "$hop2Table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "source"}, - "target": {"type": "long", "comment": "target"}, + "source": {"type": "LONG", "comment": "source"}, + "target": {"type": "LONG", "comment": "target"}, "properties": [ - {"name": "createdAt", "type": "long", "comment": "ts", "nullable": true}, - {"name": "permission", "type": "string", "comment": "perm", "nullable": true} - ], - "direction": "BOTH", - "indexes": [ - {"index": "created_at_desc", "fields": [{"field": "createdAt", "order": "DESC"}]} - ], - "groups": [], - "caches": [ - { - "cache": "recent_wishlist", - "fields": [{"field": "createdAt", "order": "DESC"}], - "limit": 100 - }, - { - "cache": "public_recent_wishlist", - "fields": [ - {"field": "permission", "order": "ASC", "dimension": ["others"]}, - {"field": "createdAt", "order": "DESC"} - ], - "limit": 100 - } + {"name": "createdAt", "type": "LONG", "comment": "ts", "nullable": true}, + {"name": "permission", "type": "STRING", "comment": "perm", "nullable": true} ] }, + "direction": "BOTH", + "indexes": [ + {"index": "created_at_desc", "fields": [{"field": "createdAt", "order": "DESC"}]} + ], + "groups": [], + "caches": [ + { + "cache": "recent_wishlist", + "fields": [{"field": "createdAt", "order": "DESC"}], + "limit": 100 + }, + { + "cache": "public_recent_wishlist", + "fields": [ + {"field": "permission", "order": "ASC", "dimension": ["others"]}, + {"field": "createdAt", "order": "DESC"} + ], + "limit": 100 + } + ], "storage": "datastore://multihop_ns/wishlist", "mode": "SYNC", "comment": "hop2 with cache" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeAggCountSentinelE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeAggCountSentinelE2ETest.kt index 9be92cc2b..0cc1a3457 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeAggCountSentinelE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeAggCountSentinelE2ETest.kt @@ -37,24 +37,24 @@ class EdgeAggCountSentinelE2ETest : E2ETestBase() { """ { "table": "$table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, "properties": [ - {"name": "category", "type": "string", "comment": "cat", "nullable": true} - ], - "direction": "BOTH", - "indexes": [], - "groups": [ - { - "group": "by_category", - "type": "SUM", - "fields": [{"name": "category"}] - } - ], - "caches": [] + {"name": "category", "type": "STRING", "comment": "cat", "nullable": true} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [ + { + "group": "by_category", + "type": "SUM", + "fields": [{"name": "category"}] + } + ], + "caches": [], "storage": "datastore://test_namespace/$table", "mode": "SYNC", "comment": "test" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeCacheQueryE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeCacheQueryE2ETest.kt index 5ef630f88..feab9d4a3 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeCacheQueryE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeCacheQueryE2ETest.kt @@ -36,41 +36,41 @@ class EdgeCacheQueryE2ETest : E2ETestBase() { """ { "table": "$edgeTable", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, "properties": [ - {"name": "createdAt", "type": "long", "comment": "ts", "nullable": true}, - {"name": "permission", "type": "string", "comment": "perm", "nullable": true} - ], - "direction": "BOTH", - "indexes": [], - "groups": [], - "caches": [ - { - "cache": "recent_wishlist", - "fields": [{"field": "createdAt", "order": "DESC"}], - "limit": 100 - }, - { - "cache": "permission_created_at_desc", - "fields": [ - {"field": "permission", "order": "ASC"}, - {"field": "createdAt", "order": "DESC"} - ], - "limit": 100 - }, - { - "cache": "whitelisted_permission", - "fields": [ - {"field": "permission", "order": "ASC", "dimension": ["me"]}, - {"field": "createdAt", "order": "DESC"} - ], - "limit": 100 - } + {"name": "createdAt", "type": "LONG", "comment": "ts", "nullable": true}, + {"name": "permission", "type": "STRING", "comment": "perm", "nullable": true} ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], + "caches": [ + { + "cache": "recent_wishlist", + "fields": [{"field": "createdAt", "order": "DESC"}], + "limit": 100 + }, + { + "cache": "permission_created_at_desc", + "fields": [ + {"field": "permission", "order": "ASC"}, + {"field": "createdAt", "order": "DESC"} + ], + "limit": 100 + }, + { + "cache": "whitelisted_permission", + "fields": [ + {"field": "permission", "order": "ASC", "dimension": ["me"]}, + {"field": "createdAt", "order": "DESC"} + ], + "limit": 100 + } + ], "storage": "datastore://test_namespace/wishlist", "mode": "SYNC", "comment": "edge with cache" @@ -131,25 +131,25 @@ class EdgeCacheQueryE2ETest : E2ETestBase() { """ { "table": "$multiEdgeTable", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "long", "comment": "buyer"}, - "target": {"type": "long", "comment": "seller"}, + "source": {"type": "LONG", "comment": "buyer"}, + "target": {"type": "LONG", "comment": "seller"}, "properties": [ - {"name": "paidAt", "type": "long", "comment": "payment time", "nullable": false} - ], - "direction": "BOTH", - "indexes": [], - "groups": [], - "caches": [ - { - "cache": "paid_at_desc", - "fields": [{"field": "paidAt", "order": "DESC"}], - "limit": 100 - } + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false}, + {"name": "paidAt", "type": "LONG", "comment": "payment time", "nullable": false} ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], + "caches": [ + { + "cache": "paid_at_desc", + "fields": [{"field": "paidAt", "order": "DESC"}], + "limit": 100 + } + ], "storage": "datastore://test_namespace/orders", "mode": "SYNC", "comment": "multi edge with cache" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationForceSyncTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationForceSyncTest.kt index 2389f7816..a605c9a90 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationForceSyncTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationForceSyncTest.kt @@ -34,17 +34,17 @@ class EdgeMutationForceSyncTest : E2ETestBase() { """ { "table": "$table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "long", "comment": "score"} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "score", "type": "LONG", "comment": "score"} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/force_sync_edge", "mode": "SYNC", "comment": "sync edge for force sync test" @@ -62,18 +62,18 @@ class EdgeMutationForceSyncTest : E2ETestBase() { """ { "table": "$multiEdgeTable", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "long", "comment": "score"} - ], - "direction": "BOTH", - "indexes": [], - "groups": [] + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false}, + {"name": "score", "type": "LONG", "comment": "score"} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/force_sync_multi_edge", "mode": "SYNC", "comment": "sync multi edge for force sync test" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationInsertMergeTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationInsertMergeTest.kt index bdda14e9d..cb9f42648 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationInsertMergeTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationInsertMergeTest.kt @@ -33,18 +33,18 @@ class EdgeMutationInsertMergeTest : E2ETestBase() { ) = """ { "table": "$table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "required", "type": "long", "comment": "required", "nullable": false}, - {"name": "optional", "type": "string", "comment": "optional", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "required", "type": "LONG", "comment": "required", "nullable": false}, + {"name": "optional", "type": "STRING", "comment": "optional", "nullable": true} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "$storage", "mode": "$mode", "comment": "$mode edge for insert-merge test" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationStorageOpsSpec.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationStorageOpsSpec.kt index 25678ad8c..c24d72249 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationStorageOpsSpec.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationStorageOpsSpec.kt @@ -33,17 +33,17 @@ class EdgeMutationStorageOpsSpec : E2ETestBase() { """ { "table": "$edgeTable", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "long", "comment": "score"} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "score", "type": "LONG", "comment": "score"} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/storage_ops_edge", "mode": "SYNC", "comment": "edge for storage-ops test" @@ -61,18 +61,18 @@ class EdgeMutationStorageOpsSpec : E2ETestBase() { """ { "table": "$multiEdgeTable", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "long", "comment": "score"} - ], - "direction": "BOTH", - "indexes": [], - "groups": [] + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false}, + {"name": "score", "type": "LONG", "comment": "score"} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/storage_ops_multi", "mode": "SYNC", "comment": "multi edge for storage-ops test" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationValidationTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationValidationTest.kt index 088e3c930..69785934f 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationValidationTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/EdgeMutationValidationTest.kt @@ -26,18 +26,18 @@ class EdgeMutationValidationTest : E2ETestBase() { ) = """ { "table": "$table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "required", "type": "long", "comment": "required", "nullable": false}, - {"name": "optional", "type": "string", "comment": "optional", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "required", "type": "LONG", "comment": "required", "nullable": false}, + {"name": "optional", "type": "STRING", "comment": "optional", "nullable": true} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "$storage", "mode": "$mode", "comment": "$mode edge for validation test" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ImmutableEdgeE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ImmutableEdgeE2ETest.kt index c52002423..4025bcb62 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ImmutableEdgeE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/ImmutableEdgeE2ETest.kt @@ -37,18 +37,18 @@ class ImmutableEdgeE2ETest : E2ETestBase() { """ { "table": "$table", + "type": "IMMUTABLE_EDGE", "schema": { - "type": "IMMUTABLE_EDGE", - "source": {"type": "long", "comment": "partition"}, - "target": {"type": "string", "comment": "message id"}, + "source": {"type": "LONG", "comment": "partition"}, + "target": {"type": "STRING", "comment": "message id"}, "properties": [ - {"name": "seq", "type": "long", "comment": "sequence", "nullable": false}, - {"name": "payload", "type": "string", "comment": "payload", "nullable": true} - ], - "direction": "OUT", - "indexes": [{"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]}], - "groups": [] + {"name": "seq", "type": "LONG", "comment": "sequence", "nullable": false}, + {"name": "payload", "type": "STRING", "comment": "payload", "nullable": true} + ] }, + "direction": "OUT", + "indexes": [{"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]}], + "groups": [], "storage": "datastore://immutable_ns/immutable_log", "mode": "SYNC", "comment": "append-only log" @@ -172,20 +172,20 @@ class ImmutableEdgeE2ETest : E2ETestBase() { """ { "table": "two_index_log", + "type": "IMMUTABLE_EDGE", "schema": { - "type": "IMMUTABLE_EDGE", - "source": {"type": "long", "comment": "partition"}, - "target": {"type": "string", "comment": "message id"}, + "source": {"type": "LONG", "comment": "partition"}, + "target": {"type": "STRING", "comment": "message id"}, "properties": [ - {"name": "seq", "type": "long", "comment": "sequence", "nullable": false} - ], - "direction": "OUT", - "indexes": [ - {"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]}, - {"index": "seq_desc", "fields": [{"field": "seq", "order": "DESC"}]} - ], - "groups": [] + {"name": "seq", "type": "LONG", "comment": "sequence", "nullable": false} + ] }, + "direction": "OUT", + "indexes": [ + {"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]}, + {"index": "seq_desc", "fields": [{"field": "seq", "order": "DESC"}]} + ], + "groups": [], "storage": "datastore://immutable_ns/two_index_log", "mode": "SYNC", "comment": "two indexes should be rejected" @@ -206,17 +206,17 @@ class ImmutableEdgeE2ETest : E2ETestBase() { """ { "table": "both_dir_log", + "type": "IMMUTABLE_EDGE", "schema": { - "type": "IMMUTABLE_EDGE", - "source": {"type": "long", "comment": "partition"}, - "target": {"type": "string", "comment": "message id"}, + "source": {"type": "LONG", "comment": "partition"}, + "target": {"type": "STRING", "comment": "message id"}, "properties": [ - {"name": "seq", "type": "long", "comment": "sequence", "nullable": false} - ], - "direction": "BOTH", - "indexes": [{"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]}], - "groups": [] + {"name": "seq", "type": "LONG", "comment": "sequence", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [{"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]}], + "groups": [], "storage": "datastore://immutable_ns/both_dir_log", "mode": "SYNC", "comment": "BOTH direction should be rejected" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/MutationContextOptInE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/MutationContextOptInE2ETest.kt index be9b7179d..b41fa27a0 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/MutationContextOptInE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/MutationContextOptInE2ETest.kt @@ -33,15 +33,15 @@ class MutationContextOptInE2ETest : E2ETestBase() { """ { "table": "$edgeTable", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/ctx_optin_edge", "mode": "SYNC", "comment": "edge for context opt-in test" @@ -59,16 +59,17 @@ class MutationContextOptInE2ETest : E2ETestBase() { """ { "table": "$multiEdgeTable", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/ctx_optin_multi_edge", "mode": "SYNC", "comment": "multi-edge for context opt-in test" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/VertexIntegrationTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/VertexIntegrationTest.kt index 432d1c852..14a3ef405 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/VertexIntegrationTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/VertexIntegrationTest.kt @@ -34,14 +34,16 @@ class VertexIntegrationTest : E2ETestBase() { """ { "table": "$vertexTable", + "type": "VERTEX", "schema": { - "type": "VERTEX", - "id": {"type": "string", "comment": "user unique key"}, + "source": {"type": "STRING", "comment": "user unique key"}, + "target": {"type": "STRING", "comment": ""}, "properties": [ - {"name": "name", "type": "string", "comment": "user name"}, - {"name": "age", "type": "long", "comment": "user age", "nullable": true} + {"name": "name", "type": "STRING", "comment": "user name"}, + {"name": "age", "type": "LONG", "comment": "user age", "nullable": true} ] }, + "direction": "OUT", "storage": "datastore://vertex_ns/users", "mode": "SYNC", "comment": "users vertex table" @@ -60,13 +62,15 @@ class VertexIntegrationTest : E2ETestBase() { """ { "table": "$longIdTable", + "type": "VERTEX", "schema": { - "type": "VERTEX", - "id": {"type": "long", "comment": "numeric user id"}, + "source": {"type": "LONG", "comment": "numeric user id"}, + "target": {"type": "STRING", "comment": ""}, "properties": [ - {"name": "name", "type": "string", "comment": "user name"} + {"name": "name", "type": "STRING", "comment": "user name"} ] }, + "direction": "OUT", "storage": "datastore://vertex_ns/users_long", "mode": "SYNC", "comment": "users vertex table with numeric id" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/datastore/hbase/DatastoreTableReferencesE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/datastore/hbase/DatastoreTableReferencesE2ETest.kt index 5f65f3fe1..14ace7b1b 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/datastore/hbase/DatastoreTableReferencesE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/datastore/hbase/DatastoreTableReferencesE2ETest.kt @@ -32,15 +32,15 @@ class DatastoreTableReferencesE2ETest : HBaseDatastoreE2ETestBase() { "storage": "datastore://$TEST_NAMESPACE/$suffix", "mode": "SYNC", "comment": "references e2e", + "type": "EDGE", "schema": { - "type": "EDGE", - "direction": "OUT", - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "indexes": [], - "groups": [] - } + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [] + }, + "direction": "OUT", + "indexes": [], + "groups": [] } """.trimIndent(), ).exchange() diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasControllerTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasControllerTest.kt index b6b46786f..fa65372d9 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasControllerTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasControllerTest.kt @@ -35,15 +35,15 @@ class AliasControllerTest : E2ETestBase() { """ { "table": "$table", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/alias_test_hbase_table", "mode": "SYNC", "comment": "target table" @@ -91,7 +91,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "CREATED", "result": $expected}""") client .get() @@ -154,7 +154,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -206,7 +206,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -265,7 +265,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -315,7 +315,9 @@ class AliasControllerTest : E2ETestBase() { .uri("$baseUri/$name") .exchange() .expectStatus() - .isNoContent + .isOk + .expectBody() + .json("""{"status": "DELETED"}""") } } @@ -354,7 +356,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.alias == '$aliasName')]") + .jsonPath("$.content[?(@.alias == '$aliasName')]") .doesNotExist() } @@ -367,7 +369,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.alias == '$aliasName')]") + .jsonPath("$.content[?(@.alias == '$aliasName')]") .doesNotExist() } @@ -380,7 +382,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.alias == '$aliasName')]") + .jsonPath("$.content[?(@.alias == '$aliasName')]") .exists() } @@ -393,7 +395,7 @@ class AliasControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.alias == '$aliasName')]") + .jsonPath("$.content[?(@.alias == '$aliasName')]") .exists() } } diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseControllerTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseControllerTest.kt index af839a8c9..093c32c06 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseControllerTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseControllerTest.kt @@ -49,7 +49,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "CREATED", "result": $expected}""") client .get() @@ -112,7 +112,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -159,7 +159,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -218,7 +218,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -265,7 +265,9 @@ class DatabaseControllerTest : E2ETestBase() { .uri("/graph/v3/databases/$name") .exchange() .expectStatus() - .isNoContent + .isOk + .expectBody() + .json("""{"status": "DELETED"}""") } } @@ -305,7 +307,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.database == '$dbName')]") + .jsonPath("$.content[?(@.database == '$dbName')]") .doesNotExist() } @@ -318,7 +320,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.database == '$dbName')]") + .jsonPath("$.content[?(@.database == '$dbName')]") .doesNotExist() } @@ -331,7 +333,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.database == '$dbName')]") + .jsonPath("$.content[?(@.database == '$dbName')]") .exists() } @@ -344,7 +346,7 @@ class DatabaseControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.database == '$dbName')]") + .jsonPath("$.content[?(@.database == '$dbName')]") .exists() } } diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggControllerE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggControllerE2ETest.kt index d8eefebb8..4e6dc5d00 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggControllerE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggControllerE2ETest.kt @@ -70,37 +70,37 @@ class MetadataAggControllerE2ETest : E2ETestBase() { """ { "table": "$table", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "purchase id"}, - "source": {"type": "string", "comment": "user"}, - "target": {"type": "string", "comment": "item"}, + "source": {"type": "STRING", "comment": "user"}, + "target": {"type": "STRING", "comment": "item"}, "properties": [ - {"name": "purchasedAt", "type": "long", "comment": "purchase time ms", "nullable": false} - ], - "direction": "BOTH", - "indexes": [], - "groups": [{ - "group": "purchased_count", - "type": "COUNT", - "fields": [ - {"name": "_target"}, - {"name": "purchasedAt", "bucket": {"type": "date", "name": "purchasedAt", "unit": "MILLISECOND", "timezone": "UTC", "format": "yyyy-MM-dd"}} - ], - "directionType": "OUT", - "aggregations": { - "topk": [{ - "topk": "top_purchased", - "entity": "source", - "ranges": "_target:eq:{_target};purchasedAt:bt:now-365d,now", - "dimension": "target", - "refreshAfterMillis": $refreshAfter, - "rank": "$rankFqn" - }] - } - }], - "caches": [] + {"name": "_id", "type": "LONG", "comment": "purchase id", "nullable": false}, + {"name": "purchasedAt", "type": "LONG", "comment": "purchase time ms", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [{ + "group": "purchased_count", + "type": "COUNT", + "fields": [ + {"name": "_target"}, + {"name": "purchasedAt", "bucket": {"type": "date", "name": "purchasedAt", "unit": "MILLISECOND", "timezone": "UTC", "format": "yyyy-MM-dd"}} + ], + "directionType": "OUT", + "aggregations": { + "topk": [{ + "topk": "top_purchased", + "entity": "source", + "ranges": "_target:eq:{_target};purchasedAt:bt:now-365d,now", + "dimension": "target", + "refreshAfterMillis": $refreshAfter, + "rank": "$rankFqn" + }] + } + }], + "caches": [], "storage": "datastore://test_namespace/$table", "mode": "SYNC", "comment": "user-to-item purchase edges" @@ -196,20 +196,20 @@ class MetadataAggControllerE2ETest : E2ETestBase() { """ { "table": "$sweepRank", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "topk|entity|dimensionValues"}, - "target": {"type": "string", "comment": "topkDimensionValue"}, + "source": {"type": "STRING", "comment": "topk|entity|dimensionValues"}, + "target": {"type": "STRING", "comment": "topkDimensionValue"}, "properties": [ - {"name": "metric", "type": "long", "comment": "metric", "nullable": false} - ], - "direction": "OUT", - "indexes": [ - {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} - ], - "groups": [], - "caches": [] + {"name": "metric", "type": "LONG", "comment": "metric", "nullable": false} + ] }, + "direction": "OUT", + "indexes": [ + {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} + ], + "groups": [], + "caches": [], "storage": "datastore://test_namespace/$sweepRank", "mode": "SYNC", "comment": "topk rank rows" @@ -227,31 +227,32 @@ class MetadataAggControllerE2ETest : E2ETestBase() { """ { "table": "$sweepTable", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "string", "comment": "user"}, - "target": {"type": "string", "comment": "item"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [{ - "group": "purchased_count", - "type": "COUNT", - "fields": [{"name": "_target"}], - "directionType": "OUT", - "aggregations": { - "topk": [{ - "topk": "$topkName", - "entity": "source", - "ranges": "_target:eq:{_target}", - "dimension": "target", - "rank": "$sweepRankFqn" - }] - } - }], - "caches": [] + "source": {"type": "STRING", "comment": "user"}, + "target": {"type": "STRING", "comment": "item"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [{ + "group": "purchased_count", + "type": "COUNT", + "fields": [{"name": "_target"}], + "directionType": "OUT", + "aggregations": { + "topk": [{ + "topk": "$topkName", + "entity": "source", + "ranges": "_target:eq:{_target}", + "dimension": "target", + "rank": "$sweepRankFqn" + }] + } + }], + "caches": [], "storage": "datastore://test_namespace/$sweepTable", "mode": "SYNC", "comment": "orders" @@ -439,20 +440,20 @@ class MetadataAggControllerE2ETest : E2ETestBase() { """ { "table": "$rankTable", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "topk|entity|dimensionValues"}, - "target": {"type": "string", "comment": "topkDimensionValue"}, + "source": {"type": "STRING", "comment": "topk|entity|dimensionValues"}, + "target": {"type": "STRING", "comment": "topkDimensionValue"}, "properties": [ - {"name": "metric", "type": "long", "comment": "aggregated metric", "nullable": false} - ], - "direction": "OUT", - "indexes": [ - {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} - ], - "groups": [], - "caches": [] + {"name": "metric", "type": "LONG", "comment": "aggregated metric", "nullable": false} + ] }, + "direction": "OUT", + "indexes": [ + {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} + ], + "groups": [], + "caches": [], "storage": "datastore://test_namespace/$rankTable", "mode": "SYNC", "comment": "topk rank rows" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggQueryControllerE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggQueryControllerE2ETest.kt index 1a1fb00fe..a7485d8b2 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggQueryControllerE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/MetadataAggQueryControllerE2ETest.kt @@ -39,21 +39,21 @@ class MetadataAggQueryControllerE2ETest : E2ETestBase() { """ { "table": "$rank", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "database|table|topk|entity|dimensionValues"}, - "target": {"type": "string", "comment": "topkDimensionValue"}, + "source": {"type": "STRING", "comment": "database|table|topk|entity|dimensionValues"}, + "target": {"type": "STRING", "comment": "topkDimensionValue"}, "properties": [ - {"name": "metric", "type": "long", "comment": "aggregated metric", "nullable": false}, - {"name": "additionalProperties", "type": "string", "comment": "carried properties as JSON", "nullable": true} - ], - "direction": "OUT", - "indexes": [ - {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} - ], - "groups": [], - "caches": [] + {"name": "metric", "type": "LONG", "comment": "aggregated metric", "nullable": false}, + {"name": "additionalProperties", "type": "STRING", "comment": "carried properties as JSON", "nullable": true} + ] }, + "direction": "OUT", + "indexes": [ + {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} + ], + "groups": [], + "caches": [], "storage": "datastore://test_namespace/$rank", "mode": "SYNC", "comment": "topk rank rows" @@ -71,39 +71,39 @@ class MetadataAggQueryControllerE2ETest : E2ETestBase() { """ { "table": "$table", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "string", "comment": "user"}, - "target": {"type": "string", "comment": "item"}, + "source": {"type": "STRING", "comment": "user"}, + "target": {"type": "STRING", "comment": "item"}, "properties": [ - {"name": "category", "type": "string", "comment": "item category", "nullable": false}, - {"name": "purchasedAt", "type": "long", "comment": "purchase time ms", "nullable": false} - ], - "direction": "OUT", - "indexes": [], - "groups": [{ - "group": "purchased_count", - "type": "COUNT", - "fields": [ - {"name": "_target"}, - {"name": "category"}, - {"name": "purchasedAt", "bucket": {"type": "date", "name": "day", "unit": "MILLISECOND", "timezone": "UTC", "format": "yyyy-MM-dd"}} - ], - "directionType": "OUT", - "aggregations": { - "topk": [{ - "topk": "$topkName", - "entity": "source", - "ranges": "_target:eq:{_target};category:eq:{category};day:bt:2023-11-14,2024-11-13", - "dimension": "target", - "rank": "$rankFqn", - "additionalProperties": ["category"] - }] - } - }], - "caches": [] + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false}, + {"name": "category", "type": "STRING", "comment": "item category", "nullable": false}, + {"name": "purchasedAt", "type": "LONG", "comment": "purchase time ms", "nullable": false} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [{ + "group": "purchased_count", + "type": "COUNT", + "fields": [ + {"name": "_target"}, + {"name": "category"}, + {"name": "purchasedAt", "bucket": {"type": "date", "name": "day", "unit": "MILLISECOND", "timezone": "UTC", "format": "yyyy-MM-dd"}} + ], + "directionType": "OUT", + "aggregations": { + "topk": [{ + "topk": "$topkName", + "entity": "source", + "ranges": "_target:eq:{_target};category:eq:{category};day:bt:2023-11-14,2024-11-13", + "dimension": "target", + "rank": "$rankFqn", + "additionalProperties": ["category"] + }] + } + }], + "caches": [], "storage": "datastore://test_namespace/$table", "mode": "SYNC", "comment": "user-to-item purchase edges" diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableControllerTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableControllerTest.kt index 89c980507..442fc0d80 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableControllerTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableControllerTest.kt @@ -38,36 +38,35 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_crud", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "int", "comment": "score", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "score", "type": "INT", "comment": "score", "nullable": true} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_edge_crud", "mode": "SYNC", "comment": "edge table" } expected: | { - "type": "edge", "table": "v3_edge_crud", "database": "v3_table_test_db", "comment": "edge table", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, "properties": [ - {"name": "score", "type": "int", "comment": "score", "nullable": true} - ], - "direction": "OUT" + {"name": "score", "type": "INT", "comment": "score", "nullable": true} + ] }, + "direction": "OUT", "storage": "datastore://test_namespace/v3_edge_crud", "active": true } @@ -77,34 +76,35 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_multiedge_crud", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "long", "comment": "sender"}, - "target": {"type": "long", "comment": "receiver"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "sender"}, + "target": {"type": "LONG", "comment": "receiver"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_multiedge_crud", "mode": "SYNC", "comment": "multiedge table" } expected: | { - "type": "multiEdge", "table": "v3_multiedge_crud", "database": "v3_table_test_db", "comment": "multiedge table", + "type": "MULTI_EDGE", "schema": { - "type": "multiEdge", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "long", "comment": "sender"}, - "target": {"type": "long", "comment": "receiver"}, - "properties": [], - "direction": "BOTH" + "source": {"type": "LONG", "comment": "sender"}, + "target": {"type": "LONG", "comment": "receiver"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false} + ] }, + "direction": "BOTH", "storage": "datastore://test_namespace/v3_multiedge_crud", "active": true } @@ -114,38 +114,37 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_full", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, "properties": [ - {"name": "rating", "type": "int", "comment": "rating", "nullable": true}, - {"name": "createdat", "type": "long", "comment": "time", "nullable": false} - ], - "direction": "BOTH", - "indexes": [], - "groups": [] + {"name": "rating", "type": "INT", "comment": "rating", "nullable": true}, + {"name": "createdat", "type": "LONG", "comment": "time", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_edge_full", "mode": "SYNC", "comment": "full edge table" } expected: | { - "type": "edge", "table": "v3_edge_full", "database": "v3_table_test_db", "comment": "full edge table", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, "properties": [ - {"name": "rating", "type": "int", "comment": "rating", "nullable": true}, - {"name": "createdat", "type": "long", "comment": "time", "nullable": false} - ], - "direction": "BOTH" + {"name": "rating", "type": "INT", "comment": "rating", "nullable": true}, + {"name": "createdat", "type": "LONG", "comment": "time", "nullable": false} + ] }, + "direction": "BOTH", "storage": "datastore://test_namespace/v3_edge_full", "active": true } @@ -155,40 +154,39 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_multiedge_full", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "txn id"}, - "source": {"type": "long", "comment": "buyer"}, - "target": {"type": "long", "comment": "product"}, + "source": {"type": "LONG", "comment": "buyer"}, + "target": {"type": "LONG", "comment": "product"}, "properties": [ - {"name": "amount", "type": "int", "comment": "purchase amount", "nullable": false}, - {"name": "timestamp", "type": "long", "comment": "txn time", "nullable": false} - ], - "direction": "BOTH", - "indexes": [], - "groups": [] + {"name": "_id", "type": "LONG", "comment": "txn id", "nullable": false}, + {"name": "amount", "type": "INT", "comment": "purchase amount", "nullable": false}, + {"name": "timestamp", "type": "LONG", "comment": "txn time", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_multiedge_full", "mode": "SYNC", "comment": "full multiedge table" } expected: | { - "type": "multiEdge", "table": "v3_multiedge_full", "database": "v3_table_test_db", "comment": "full multiedge table", + "type": "MULTI_EDGE", "schema": { - "type": "multiEdge", - "id": {"type": "long", "comment": "txn id"}, - "source": {"type": "long", "comment": "buyer"}, - "target": {"type": "long", "comment": "product"}, + "source": {"type": "LONG", "comment": "buyer"}, + "target": {"type": "LONG", "comment": "product"}, "properties": [ - {"name": "amount", "type": "int", "comment": "purchase amount", "nullable": false}, - {"name": "timestamp", "type": "long", "comment": "txn time", "nullable": false} - ], - "direction": "BOTH" + {"name": "_id", "type": "LONG", "comment": "txn id", "nullable": false}, + {"name": "amount", "type": "INT", "comment": "purchase amount", "nullable": false}, + {"name": "timestamp", "type": "LONG", "comment": "txn time", "nullable": false} + ] }, + "direction": "BOTH", "storage": "datastore://test_namespace/v3_multiedge_full", "active": true } @@ -208,7 +206,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "CREATED", "result": $expected}""") client .get() @@ -227,15 +225,15 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_upd", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_edge_upd", "mode": "SYNC", "comment": "edge table" @@ -248,16 +246,17 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_multiedge_upd", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_multiedge_upd", "mode": "SYNC", "comment": "multiedge table" @@ -293,7 +292,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -307,15 +306,15 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_deact", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_edge_deact", "mode": "SYNC", "comment": "edge table" @@ -326,16 +325,17 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_multiedge_deact", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_multiedge_deact", "mode": "SYNC", "comment": "multiedge table" @@ -369,7 +369,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -385,15 +385,15 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_react", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_edge_react", "mode": "SYNC", "comment": "edge table" @@ -404,16 +404,17 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_multiedge_react", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_multiedge_react", "mode": "SYNC", "comment": "multiedge table" @@ -457,7 +458,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") } @ObjectSourceParameterizedTest @@ -471,15 +472,15 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_del", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_edge_del", "mode": "SYNC", "comment": "edge table" @@ -488,16 +489,17 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_multiedge_del", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "id"}, - "source": {"type": "long", "comment": "src"}, - "target": {"type": "long", "comment": "tgt"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "src"}, + "target": {"type": "LONG", "comment": "tgt"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "id", "nullable": false} + ] }, + "direction": "BOTH", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_multiedge_del", "mode": "SYNC", "comment": "multiedge table" @@ -533,7 +535,9 @@ class TableControllerTest : E2ETestBase() { .uri("$baseUri/$name") .exchange() .expectStatus() - .isNoContent + .isOk + .expectBody() + .json("""{"status": "DELETED"}""") } } @@ -552,15 +556,15 @@ class TableControllerTest : E2ETestBase() { """ { "table": "$tableName", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", + "indexes": [], + "groups": [], "storage": "datastore://test_namespace/v3_tbl_status_filter", "mode": "SYNC", "comment": "status filter test" @@ -589,7 +593,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.table == '$tableName')]") + .jsonPath("$.content[?(@.table == '$tableName')]") .doesNotExist() } @@ -602,7 +606,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.table == '$tableName')]") + .jsonPath("$.content[?(@.table == '$tableName')]") .doesNotExist() } @@ -615,7 +619,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.table == '$tableName')]") + .jsonPath("$.content[?(@.table == '$tableName')]") .exists() } @@ -628,7 +632,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$[?(@.table == '$tableName')]") + .jsonPath("$.content[?(@.table == '$tableName')]") .exists() } } @@ -643,44 +647,41 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_cache_crud", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, "properties": [ - {"name": "score", "type": "int", "comment": "score", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [], - "caches": [ - { - "cache": "top_items", - "fields": [{"field": "score", "order": "DESC"}], - "limit": 50, - "comment": "top 50 items" - } + {"name": "score", "type": "INT", "comment": "score", "nullable": true} ] }, + "direction": "OUT", + "indexes": [], + "groups": [], + "caches": [ + { + "cache": "top_items", + "fields": [{"field": "score", "order": "DESC"}], + "limit": 50, + "comment": "top 50 items" + } + ], "storage": "datastore://test_namespace/v3_edge_cache_crud", "mode": "SYNC", "comment": "edge table with cache" } expected: | { - "type": "edge", "table": "v3_edge_cache_crud", - "schema": { - "type": "edge", - "caches": [ - { - "cache": "top_items", - "fields": [{"field": "score", "order": "DESC"}], - "limit": 50, - "comment": "top 50 items" - } - ] - }, + "type": "EDGE", + "caches": [ + { + "cache": "top_items", + "fields": [{"field": "score", "order": "DESC"}], + "limit": 50, + "comment": "top 50 items" + } + ], "active": true } """, @@ -699,7 +700,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "CREATED", "result": $expected}""") client .get() @@ -718,64 +719,62 @@ class TableControllerTest : E2ETestBase() { create: | { "table": "v3_edge_cache_upd", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, "properties": [ - {"name": "score", "type": "int", "comment": "score", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [], - "caches": [ - { - "cache": "old_cache", - "fields": [{"field": "score", "order": "ASC"}], - "limit": 10, - "comment": "old" - } + {"name": "score", "type": "INT", "comment": "score", "nullable": true} ] }, + "direction": "OUT", + "indexes": [], + "groups": [], + "caches": [ + { + "cache": "old_cache", + "fields": [{"field": "score", "order": "ASC"}], + "limit": 10, + "comment": "old" + } + ], "storage": "datastore://test_namespace/v3_edge_cache_upd", "mode": "SYNC", "comment": "edge table" } update: | { + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, "properties": [ - {"name": "score", "type": "int", "comment": "score", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [], - "caches": [ - { - "cache": "new_cache", - "fields": [{"field": "score", "order": "DESC"}], - "limit": 100, - "comment": "new" - } + {"name": "score", "type": "INT", "comment": "score", "nullable": true} ] - } + }, + "direction": "OUT", + "indexes": [], + "groups": [], + "caches": [ + { + "cache": "new_cache", + "fields": [{"field": "score", "order": "DESC"}], + "limit": 100, + "comment": "new" + } + ] } expected: | { "table": "v3_edge_cache_upd", - "schema": { - "caches": [ - { - "cache": "new_cache", - "fields": [{"field": "score", "order": "DESC"}], - "limit": 100, - "comment": "new" - } - ] - }, + "caches": [ + { + "cache": "new_cache", + "fields": [{"field": "score", "order": "DESC"}], + "limit": 100, + "comment": "new" + } + ], "active": true } """, @@ -806,7 +805,7 @@ class TableControllerTest : E2ETestBase() { .expectStatus() .isOk .expectBody() - .json(expected) + .json("""{"status": "UPDATED", "result": $expected}""") // verify persistence via get client diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V2V3CompatibilityTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V2V3CompatibilityTest.kt index 3494be885..b019ac3d7 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V2V3CompatibilityTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V2V3CompatibilityTest.kt @@ -10,15 +10,24 @@ import org.junit.jupiter.api.TestInstance import org.springframework.http.MediaType /** - * V2-V3 API Compatibility E2E Tests + * The contract between the two metadata dialects. * - * Verifies that the same data can be represented in V2/V3 JSON formats - * and that cross-version operations work correctly. + * v3 is v2's structure with v3's names, so a v2 payload becomes a v3 payload by renaming keys and + * nothing else. Each case writes through one dialect and reads back through the other. * - * Terminology: - * - V2 Service = V3 Database - * - V2 Label = V3 Table - * - V2 desc = V3 comment + * | v2 | v3 | + * |-------------------|----------------------| + * | `name: "a.b"` | `database` + `table` | + * | `desc` | `comment` | + * | `type: INDEXED` | `type: EDGE` | + * | `IMMUTABLE_INDEXED` | `IMMUTABLE_EDGE` | + * | `schema.src/tgt` | `schema.source/target` | + * | `schema.fields` | `schema.properties` | + * | `dirType` | `direction` | + * | `indices[].name` | `indexes[].index` | + * | `indices[].fields[].name` | `indexes[].fields[].field` | + * | `mode: IGNORE` | `mode: DROP` | + * | `event`/`readOnly`| derived by the server | */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class V2V3CompatibilityTest : E2ETestBase() { @@ -150,17 +159,18 @@ class V2V3CompatibilityTest : E2ETestBase() { } expected: | { + "active": true, + "database": "tbl_compat_db", "table": "tbl_v2v3_out", "comment": "direction out", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "source"}, - "target": {"type": "string", "comment": "target"}, - "properties": [], - "direction": "OUT" + "source": {"type": "STRING", "comment": "source"}, + "target": {"type": "STRING", "comment": "target"}, + "properties": [] }, - "storage": "datastore://test_namespace/tbl_v2v3_out", - "active": true + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_out" } # Direction: IN @@ -179,110 +189,178 @@ class V2V3CompatibilityTest : E2ETestBase() { } expected: | { + "active": true, + "database": "tbl_compat_db", "table": "tbl_v2v3_in", "comment": "direction in", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "source"}, - "target": {"type": "string", "comment": "target"}, - "properties": [], - "direction": "IN" + "source": {"type": "STRING", "comment": "source"}, + "target": {"type": "STRING", "comment": "target"}, + "properties": [] }, - "storage": "datastore://test_namespace/tbl_v2v3_in", - "active": true + "direction": "IN", + "storage": "datastore://test_namespace/tbl_v2v3_in" } - # Direction: BOTH - - name: tbl_v2v3_both + # Properties keep their v2 types verbatim + - name: tbl_v2v3_props create: | { - "desc": "direction both", + "desc": "with props", "type": "INDEXED", "schema": { - "src": {"type": "STRING", "desc": "source"}, - "tgt": {"type": "STRING", "desc": "target"}, - "fields": [] + "src": {"type": "STRING", "desc": "user"}, + "tgt": {"type": "STRING", "desc": "item"}, + "fields": [ + {"name": "rating", "type": "INT", "nullable": true, "desc": "rating"}, + {"name": "createdat", "type": "LONG", "nullable": false, "desc": "time"} + ] }, - "dirType": "BOTH", - "storage": "datastore://test_namespace/tbl_v2v3_both" + "dirType": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_props" } expected: | { - "table": "tbl_v2v3_both", - "comment": "direction both", + "active": true, + "database": "tbl_compat_db", + "table": "tbl_v2v3_props", + "comment": "with props", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "source"}, - "target": {"type": "string", "comment": "target"}, - "properties": [], - "direction": "BOTH" + "source": {"type": "STRING", "comment": "user"}, + "target": {"type": "STRING", "comment": "item"}, + "properties": [ + {"name": "rating", "type": "INT", "comment": "rating", "nullable": true}, + {"name": "createdat", "type": "LONG", "comment": "time", "nullable": false} + ] }, - "storage": "datastore://test_namespace/tbl_v2v3_both", - "active": true + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_props" } - # With properties - - name: tbl_v2v3_props + # indices -> indexes, and their inner name -> index / field + - name: tbl_v2v3_index create: | { - "desc": "with props", + "desc": "with index", "type": "INDEXED", "schema": { - "src": {"type": "STRING", "desc": "user"}, - "tgt": {"type": "STRING", "desc": "item"}, + "src": {"type": "LONG", "desc": "uid"}, + "tgt": {"type": "LONG", "desc": "iid"}, "fields": [ - {"name": "rating", "type": "INT", "nullable": true, "desc": "rating"}, - {"name": "createdat", "type": "LONG", "nullable": true, "desc": "time"} + {"name": "createdat", "type": "LONG", "nullable": false, "desc": "time"} ] }, "dirType": "OUT", - "storage": "datastore://test_namespace/tbl_v2v3_props" + "storage": "datastore://test_namespace/tbl_v2v3_index", + "indices": [ + { + "name": "createdat_desc", + "fields": [{"name": "createdat", "order": "DESC"}], + "desc": "newest first" + } + ], + "mode": "IGNORE" } expected: | { - "table": "tbl_v2v3_props", - "comment": "with props", + "active": true, + "database": "tbl_compat_db", + "table": "tbl_v2v3_index", + "comment": "with index", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "user"}, - "target": {"type": "string", "comment": "item"}, + "source": {"type": "LONG", "comment": "uid"}, + "target": {"type": "LONG", "comment": "iid"}, "properties": [ - {"name": "rating", "type": "int", "comment": "rating", "nullable": true}, - {"name": "createdat", "type": "long", "comment": "time", "nullable": true} - ], - "direction": "OUT" + {"name": "createdat", "type": "LONG", "comment": "time", "nullable": false} + ] }, - "storage": "datastore://test_namespace/tbl_v2v3_props", - "active": true + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_index", + "indexes": [ + { + "index": "createdat_desc", + "fields": [{"field": "createdat", "order": "DESC"}], + "comment": "newest first" + } + ], + "mode": "DROP" } - # LONG keys - - name: tbl_v2v3_long + # IMMUTABLE_INDEXED -> IMMUTABLE_EDGE + - name: tbl_v2v3_immutable create: | { - "desc": "long keys", - "type": "INDEXED", + "desc": "append only", + "type": "IMMUTABLE_INDEXED", "schema": { - "src": {"type": "LONG", "desc": "uid"}, - "tgt": {"type": "LONG", "desc": "iid"}, - "fields": [] + "src": {"type": "LONG", "desc": "partition"}, + "tgt": {"type": "STRING", "desc": "message id"}, + "fields": [ + {"name": "seq", "type": "LONG", "nullable": false, "desc": "sequence"} + ] }, "dirType": "OUT", - "storage": "datastore://test_namespace/tbl_v2v3_long" + "storage": "datastore://test_namespace/tbl_v2v3_immutable", + "indices": [ + {"name": "seq_asc", "fields": [{"name": "seq", "order": "ASC"}], "desc": ""} + ] } expected: | { - "table": "tbl_v2v3_long", - "comment": "long keys", + "active": true, + "database": "tbl_compat_db", + "table": "tbl_v2v3_immutable", + "comment": "append only", + "type": "IMMUTABLE_EDGE", "schema": { - "type": "edge", - "source": {"type": "long", "comment": "uid"}, - "target": {"type": "long", "comment": "iid"}, - "properties": [], - "direction": "OUT" + "source": {"type": "LONG", "comment": "partition"}, + "target": {"type": "STRING", "comment": "message id"}, + "properties": [ + {"name": "seq", "type": "LONG", "comment": "sequence", "nullable": false} + ] }, - "storage": "datastore://test_namespace/tbl_v2v3_long", - "active": true + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_immutable", + "indexes": [ + {"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]} + ] + } + + # VERTEX keeps the v2 layout: the id lives in src, tgt is a placeholder + - name: tbl_v2v3_vertex + create: | + { + "desc": "vertex table", + "type": "VERTEX", + "schema": { + "src": {"type": "STRING", "desc": "user key"}, + "tgt": {"type": "STRING", "desc": ""}, + "fields": [ + {"name": "nickname", "type": "STRING", "nullable": true, "desc": "nickname"} + ] + }, + "dirType": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_vertex" + } + expected: | + { + "active": true, + "database": "tbl_compat_db", + "table": "tbl_v2v3_vertex", + "comment": "vertex table", + "type": "VERTEX", + "schema": { + "source": {"type": "STRING", "comment": "user key"}, + "target": {"type": "STRING", "comment": ""}, + "properties": [ + {"name": "nickname", "type": "STRING", "comment": "nickname", "nullable": true} + ] + }, + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v2v3_vertex" } """, ) @@ -318,23 +396,23 @@ class V2V3CompatibilityTest : E2ETestBase() { create: | { "table": "tbl_v3v2_out", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "source"}, - "target": {"type": "string", "comment": "target"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "source"}, + "target": {"type": "STRING", "comment": "target"}, + "properties": [] }, + "direction": "OUT", "storage": "datastore://test_namespace/tbl_v3v2_out", "mode": "SYNC", "comment": "direction out" } expected: | { + "active": true, "name": "tbl_compat_db.tbl_v3v2_out", "desc": "direction out", + "type": "INDEXED", "schema": { "src": {"type": "STRING", "desc": "source"}, "tgt": {"type": "STRING", "desc": "target"}, @@ -342,7 +420,7 @@ class V2V3CompatibilityTest : E2ETestBase() { }, "dirType": "OUT", "storage": "datastore://test_namespace/tbl_v3v2_out", - "active": true + "mode": "SYNC" } # Direction: IN @@ -350,23 +428,23 @@ class V2V3CompatibilityTest : E2ETestBase() { create: | { "table": "tbl_v3v2_in", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "source"}, - "target": {"type": "string", "comment": "target"}, - "properties": [], - "direction": "IN", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "source"}, + "target": {"type": "STRING", "comment": "target"}, + "properties": [] }, + "direction": "IN", "storage": "datastore://test_namespace/tbl_v3v2_in", "mode": "SYNC", "comment": "direction in" } expected: | { + "active": true, "name": "tbl_compat_db.tbl_v3v2_in", "desc": "direction in", + "type": "INDEXED", "schema": { "src": {"type": "STRING", "desc": "source"}, "tgt": {"type": "STRING", "desc": "target"}, @@ -374,109 +452,173 @@ class V2V3CompatibilityTest : E2ETestBase() { }, "dirType": "IN", "storage": "datastore://test_namespace/tbl_v3v2_in", - "active": true + "mode": "SYNC" } - # Direction: BOTH - - name: tbl_v3v2_both + # Properties keep their v2 types verbatim + - name: tbl_v3v2_props create: | { - "table": "tbl_v3v2_both", + "table": "tbl_v3v2_props", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "source"}, - "target": {"type": "string", "comment": "target"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "user"}, + "target": {"type": "STRING", "comment": "item"}, + "properties": [ + {"name": "rating", "type": "INT", "comment": "rating", "nullable": true}, + {"name": "createdat", "type": "LONG", "comment": "time", "nullable": false} + ] }, - "storage": "datastore://test_namespace/tbl_v3v2_both", + "direction": "BOTH", + "storage": "datastore://test_namespace/tbl_v3v2_props", "mode": "SYNC", - "comment": "direction both" + "comment": "with props" } expected: | { - "name": "tbl_compat_db.tbl_v3v2_both", - "desc": "direction both", + "active": true, + "name": "tbl_compat_db.tbl_v3v2_props", + "desc": "with props", + "type": "INDEXED", "schema": { - "src": {"type": "STRING", "desc": "source"}, - "tgt": {"type": "STRING", "desc": "target"}, - "fields": [] + "src": {"type": "STRING", "desc": "user"}, + "tgt": {"type": "STRING", "desc": "item"}, + "fields": [ + {"name": "rating", "type": "INT", "nullable": true, "desc": "rating"}, + {"name": "createdat", "type": "LONG", "nullable": false, "desc": "time"} + ] }, "dirType": "BOTH", - "storage": "datastore://test_namespace/tbl_v3v2_both", - "active": true + "storage": "datastore://test_namespace/tbl_v3v2_props", + "mode": "SYNC" } - # With properties - - name: tbl_v3v2_props + # indexes -> indices, DROP -> IGNORE + - name: tbl_v3v2_index create: | { - "table": "tbl_v3v2_props", + "table": "tbl_v3v2_index", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "user"}, - "target": {"type": "string", "comment": "item"}, + "source": {"type": "LONG", "comment": "uid"}, + "target": {"type": "LONG", "comment": "iid"}, "properties": [ - {"name": "rating", "type": "int", "comment": "rating", "nullable": true}, - {"name": "createdat", "type": "long", "comment": "time", "nullable": true} - ], - "direction": "OUT", - "indexes": [], - "groups": [] + {"name": "createdat", "type": "LONG", "comment": "time", "nullable": false} + ] }, - "storage": "datastore://test_namespace/tbl_v3v2_props", + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v3v2_index", + "indexes": [ + { + "index": "createdat_desc", + "fields": [{"field": "createdat", "order": "DESC"}], + "comment": "newest first" + } + ], + "mode": "DROP", + "comment": "with index" + } + expected: | + { + "active": true, + "name": "tbl_compat_db.tbl_v3v2_index", + "desc": "with index", + "type": "INDEXED", + "schema": { + "src": {"type": "LONG", "desc": "uid"}, + "tgt": {"type": "LONG", "desc": "iid"}, + "fields": [ + {"name": "createdat", "type": "LONG", "nullable": false, "desc": "time"} + ] + }, + "dirType": "OUT", + "storage": "datastore://test_namespace/tbl_v3v2_index", + "indices": [ + { + "name": "createdat_desc", + "fields": [{"name": "createdat", "order": "DESC"}], + "desc": "newest first" + } + ], + "mode": "IGNORE" + } + + # IMMUTABLE_EDGE -> IMMUTABLE_INDEXED + - name: tbl_v3v2_immutable + create: | + { + "table": "tbl_v3v2_immutable", + "type": "IMMUTABLE_EDGE", + "schema": { + "source": {"type": "LONG", "comment": "partition"}, + "target": {"type": "STRING", "comment": "message id"}, + "properties": [ + {"name": "seq", "type": "LONG", "comment": "sequence", "nullable": false} + ] + }, + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v3v2_immutable", + "indexes": [ + {"index": "seq_asc", "fields": [{"field": "seq", "order": "ASC"}]} + ], "mode": "SYNC", - "comment": "with props" + "comment": "append only" } expected: | { - "name": "tbl_compat_db.tbl_v3v2_props", - "desc": "with props", + "active": true, + "name": "tbl_compat_db.tbl_v3v2_immutable", + "desc": "append only", + "type": "IMMUTABLE_INDEXED", "schema": { - "src": {"type": "STRING", "desc": "user"}, - "tgt": {"type": "STRING", "desc": "item"}, + "src": {"type": "LONG", "desc": "partition"}, + "tgt": {"type": "STRING", "desc": "message id"}, "fields": [ - {"name": "rating", "type": "INT", "nullable": true, "desc": "rating"}, - {"name": "createdat", "type": "LONG", "nullable": true, "desc": "time"} + {"name": "seq", "type": "LONG", "nullable": false, "desc": "sequence"} ] }, "dirType": "OUT", - "storage": "datastore://test_namespace/tbl_v3v2_props", - "active": true + "storage": "datastore://test_namespace/tbl_v3v2_immutable", + "indices": [ + {"name": "seq_asc", "fields": [{"name": "seq", "order": "ASC"}]} + ], + "mode": "SYNC" } - # LONG keys - - name: tbl_v3v2_long + # VERTEX keeps the v2 layout: the id lives in source, target is a placeholder + - name: tbl_v3v2_vertex create: | { - "table": "tbl_v3v2_long", + "table": "tbl_v3v2_vertex", + "type": "VERTEX", "schema": { - "type": "edge", - "source": {"type": "long", "comment": "uid"}, - "target": {"type": "long", "comment": "iid"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "user key"}, + "target": {"type": "STRING", "comment": ""}, + "properties": [ + {"name": "nickname", "type": "STRING", "comment": "nickname", "nullable": true} + ] }, - "storage": "datastore://test_namespace/tbl_v3v2_long", + "direction": "OUT", + "storage": "datastore://test_namespace/tbl_v3v2_vertex", "mode": "SYNC", - "comment": "long keys" + "comment": "vertex table" } expected: | { - "name": "tbl_compat_db.tbl_v3v2_long", - "desc": "long keys", + "active": true, + "name": "tbl_compat_db.tbl_v3v2_vertex", + "desc": "vertex table", + "type": "VERTEX", "schema": { - "src": {"type": "LONG", "desc": "uid"}, - "tgt": {"type": "LONG", "desc": "iid"}, - "fields": [] + "src": {"type": "STRING", "desc": "user key"}, + "tgt": {"type": "STRING", "desc": ""}, + "fields": [ + {"name": "nickname", "type": "STRING", "nullable": true, "desc": "nickname"} + ] }, "dirType": "OUT", - "storage": "datastore://test_namespace/tbl_v3v2_long", - "active": true + "storage": "datastore://test_namespace/tbl_v3v2_vertex", + "mode": "SYNC" } """, ) @@ -530,15 +672,13 @@ class V2V3CompatibilityTest : E2ETestBase() { """ { "table": "$table", + "type": "EDGE", "schema": { - "type": "edge", - "source": {"type": "string", "comment": "src"}, - "target": {"type": "string", "comment": "tgt"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": {"type": "STRING", "comment": "src"}, + "target": {"type": "STRING", "comment": "tgt"}, + "properties": [] }, + "direction": "OUT", "storage": "datastore://test_namespace/als_target_storage", "mode": "SYNC", "comment": "target table" @@ -658,7 +798,7 @@ class V2V3CompatibilityTest : E2ETestBase() { @ObjectSourceParameterizedTest @ObjectSource( """ - # Basic MultiEdge - direction BOTH + # The id stays where v2 puts it: the `_id` property - name: me_v2v3_basic create: | { @@ -677,19 +817,20 @@ class V2V3CompatibilityTest : E2ETestBase() { } expected: | { - "type": "multiEdge", + "active": true, + "database": "me_compat_db", "table": "me_v2v3_basic", "comment": "basic multiedge", + "type": "MULTI_EDGE", "schema": { - "type": "multiEdge", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "long", "comment": "sender"}, - "target": {"type": "long", "comment": "receiver"}, - "properties": [], - "direction": "BOTH" + "source": {"type": "LONG", "comment": "sender"}, + "target": {"type": "LONG", "comment": "receiver"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false} + ] }, - "storage": "datastore://test_namespace/me_v2v3_basic", - "active": true + "direction": "BOTH", + "storage": "datastore://test_namespace/me_v2v3_basic" } # MultiEdge with properties @@ -703,8 +844,7 @@ class V2V3CompatibilityTest : E2ETestBase() { "tgt": {"type": "LONG", "desc": "item"}, "fields": [ {"name": "_id", "type": "LONG", "nullable": false, "desc": "txn id"}, - {"name": "amount", "type": "INT", "nullable": false, "desc": "purchase amount"}, - {"name": "timestamp", "type": "LONG", "nullable": false, "desc": "txn time"} + {"name": "amount", "type": "INT", "nullable": false, "desc": "purchase amount"} ] }, "dirType": "BOTH", @@ -713,56 +853,21 @@ class V2V3CompatibilityTest : E2ETestBase() { } expected: | { - "type": "multiEdge", + "active": true, + "database": "me_compat_db", "table": "me_v2v3_props", "comment": "multiedge with props", - "schema": { - "type": "multiEdge", - "id": {"type": "long", "comment": "txn id"}, - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, - "properties": [ - {"name": "amount", "type": "int", "comment": "purchase amount", "nullable": false}, - {"name": "timestamp", "type": "long", "comment": "txn time", "nullable": false} - ], - "direction": "BOTH" - }, - "storage": "datastore://test_namespace/me_v2v3_props", - "active": true - } - - # MultiEdge with STRING keys - - name: me_v2v3_string - create: | - { - "desc": "string key multiedge", "type": "MULTI_EDGE", "schema": { - "src": {"type": "STRING", "desc": "from"}, - "tgt": {"type": "STRING", "desc": "to"}, - "fields": [ - {"name": "_id", "type": "LONG", "nullable": false, "desc": "msg id"} + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "txn id", "nullable": false}, + {"name": "amount", "type": "INT", "comment": "purchase amount", "nullable": false} ] }, - "dirType": "OUT", - "storage": "datastore://test_namespace/me_v2v3_string", - "readOnly": true - } - expected: | - { - "type": "multiEdge", - "table": "me_v2v3_string", - "comment": "string key multiedge", - "schema": { - "type": "multiEdge", - "id": {"type": "long", "comment": "msg id"}, - "source": {"type": "string", "comment": "from"}, - "target": {"type": "string", "comment": "to"}, - "properties": [], - "direction": "OUT" - }, - "storage": "datastore://test_namespace/me_v2v3_string", - "active": true + "direction": "BOTH", + "storage": "datastore://test_namespace/me_v2v3_props" } """, ) @@ -793,29 +898,32 @@ class V2V3CompatibilityTest : E2ETestBase() { @ObjectSourceParameterizedTest @ObjectSource( """ - # Basic MultiEdge - V3 create -> V2 get + # The server derives readOnly=true for a multi-edge table, as v2 requires - name: me_v3v2_basic create: | { "table": "me_v3v2_basic", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "long", "comment": "sender"}, - "target": {"type": "long", "comment": "receiver"}, - "properties": [], - "direction": "BOTH", - "indexes": [], - "groups": [] + "source": {"type": "LONG", "comment": "sender"}, + "target": {"type": "LONG", "comment": "receiver"}, + "properties": [ + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false} + ] }, + "direction": "BOTH", "storage": "datastore://test_namespace/me_v3v2_basic", "mode": "SYNC", "comment": "basic multiedge" } expected: | { + "active": true, "name": "me_compat_db.me_v3v2_basic", "desc": "basic multiedge", + "type": "MULTI_EDGE", + "readOnly": true, + "event": false, "schema": { "src": {"type": "LONG", "desc": "sender"}, "tgt": {"type": "LONG", "desc": "receiver"}, @@ -824,83 +932,45 @@ class V2V3CompatibilityTest : E2ETestBase() { ] }, "dirType": "BOTH", - "storage": "datastore://test_namespace/me_v3v2_basic", - "active": true + "storage": "datastore://test_namespace/me_v3v2_basic" } - # MultiEdge with properties - V3 create -> V2 get + # MultiEdge with properties - name: me_v3v2_props create: | { "table": "me_v3v2_props", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "txn id"}, - "source": {"type": "long", "comment": "user"}, - "target": {"type": "long", "comment": "item"}, + "source": {"type": "LONG", "comment": "user"}, + "target": {"type": "LONG", "comment": "item"}, "properties": [ - {"name": "amount", "type": "int", "comment": "purchase amount", "nullable": false}, - {"name": "timestamp", "type": "long", "comment": "txn time", "nullable": false} - ], - "direction": "BOTH", - "indexes": [], - "groups": [] + {"name": "_id", "type": "LONG", "comment": "txn id", "nullable": false}, + {"name": "amount", "type": "INT", "comment": "purchase amount", "nullable": false} + ] }, + "direction": "BOTH", "storage": "datastore://test_namespace/me_v3v2_props", "mode": "SYNC", "comment": "multiedge with props" } expected: | { + "active": true, "name": "me_compat_db.me_v3v2_props", "desc": "multiedge with props", + "type": "MULTI_EDGE", + "readOnly": true, "schema": { "src": {"type": "LONG", "desc": "user"}, "tgt": {"type": "LONG", "desc": "item"}, "fields": [ {"name": "_id", "type": "LONG", "nullable": false, "desc": "txn id"}, - {"name": "amount", "type": "INT", "nullable": false, "desc": "purchase amount"}, - {"name": "timestamp", "type": "LONG", "nullable": false, "desc": "txn time"} + {"name": "amount", "type": "INT", "nullable": false, "desc": "purchase amount"} ] }, "dirType": "BOTH", - "storage": "datastore://test_namespace/me_v3v2_props", - "active": true - } - - # MultiEdge with STRING keys - V3 create -> V2 get - - name: me_v3v2_string - create: | - { - "table": "me_v3v2_string", - "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "msg id"}, - "source": {"type": "string", "comment": "from"}, - "target": {"type": "string", "comment": "to"}, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] - }, - "storage": "datastore://test_namespace/me_v3v2_string", - "mode": "SYNC", - "comment": "string key multiedge" - } - expected: | - { - "name": "me_compat_db.me_v3v2_string", - "desc": "string key multiedge", - "schema": { - "src": {"type": "STRING", "desc": "from"}, - "tgt": {"type": "STRING", "desc": "to"}, - "fields": [ - {"name": "_id", "type": "LONG", "nullable": false, "desc": "msg id"} - ] - }, - "dirType": "OUT", - "storage": "datastore://test_namespace/me_v3v2_string", - "active": true + "storage": "datastore://test_namespace/me_v3v2_props" } """, ) diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverterTest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverterTest.kt index e51e05d55..6460d33ab 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverterTest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/V3MetadataConverterTest.kt @@ -5,24 +5,23 @@ import com.kakao.actionbase.v2.core.code.Index as V2Index import com.kakao.actionbase.v2.core.code.hbase.Order as V2Order import com.kakao.actionbase.v2.core.metadata.DirectionType as V2DirectionType import com.kakao.actionbase.v2.core.metadata.MutationMode as V2MutationMode +import com.kakao.actionbase.v2.core.types.Field as V2Field -import com.kakao.actionbase.core.metadata.AliasDescriptor -import com.kakao.actionbase.core.metadata.DatabaseDescriptor -import com.kakao.actionbase.core.metadata.TableDescriptor import com.kakao.actionbase.core.metadata.common.Cache import com.kakao.actionbase.core.metadata.common.CacheField import com.kakao.actionbase.core.metadata.common.DirectionType import com.kakao.actionbase.core.metadata.common.MutationMode -import com.kakao.actionbase.core.types.PrimitiveType +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toAliasResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toDatabaseResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toLabelType +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toTableResponse +import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toTableType import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2AliasEntity import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2DirectionType import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2MutationMode import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV2ServiceEntity -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3AliasDescriptor -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3DatabaseDescriptor import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3DirectionType import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3MutationMode -import com.kakao.actionbase.server.api.graph.v3.metadata.V3MetadataConverter.toV3TableDescriptor import com.kakao.actionbase.test.documentations.params.ObjectSource import com.kakao.actionbase.test.documentations.params.ObjectSourceParameterizedTest import com.kakao.actionbase.v2.core.metadata.LabelType @@ -40,7 +39,39 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Nested class V3MetadataConverterTest { - private val tenant = "test_tenant" + private fun labelEntity( + database: String, + table: String, + comment: String = "test table", + type: LabelType = LabelType.INDEXED, + schema: EdgeSchema = + EdgeSchema( + VertexField(VertexType.STRING, "source"), + VertexField(VertexType.STRING, "target"), + listOf(V2Field("score", DataType.INT, true, "score field")), + ), + dirType: V2DirectionType = V2DirectionType.OUT, + storage: String = "datastore://test_namespace/test_table", + indices: List = emptyList(), + caches: List = emptyList(), + readOnly: Boolean = false, + mode: V2MutationMode = V2MutationMode.SYNC, + ): LabelEntity = + LabelEntity( + active = true, + name = EntityName(database, table), + desc = comment, + type = type, + schema = schema, + dirType = dirType, + storage = storage, + indices = indices, + groups = emptyList(), + caches = caches, + event = false, + readOnly = readOnly, + mode = mode, + ) @Nested inner class DatabaseConversionTest { @@ -52,7 +83,7 @@ class V3MetadataConverterTest { comment: test database """, ) - fun `ServiceEntity to DatabaseDescriptor`( + fun `ServiceEntity to DatabaseResponse`( database: String, active: Boolean, comment: String, @@ -63,11 +94,10 @@ class V3MetadataConverterTest { name = EntityName.fromOrigin(database), desc = comment, ) - val v3Descriptor = v2Entity.toV3DatabaseDescriptor(tenant) - assertThat(v3Descriptor.tenant).isEqualTo(tenant) - assertThat(v3Descriptor.database).isEqualTo(database) - assertThat(v3Descriptor.active).isEqualTo(active) - assertThat(v3Descriptor.comment).isEqualTo(comment) + val response = v2Entity.toDatabaseResponse() + assertThat(response.database).isEqualTo(database) + assertThat(response.active).isEqualTo(active) + assertThat(response.comment).isEqualTo(comment) } @ObjectSourceParameterizedTest @@ -78,19 +108,18 @@ class V3MetadataConverterTest { comment: test database """, ) - fun `DatabaseDescriptor to ServiceEntity`( + fun `DatabaseResponse to ServiceEntity`( database: String, active: Boolean, comment: String, ) { - val v3Descriptor = - DatabaseDescriptor( - tenant = tenant, - database = database, + val response = + DatabaseResponse( active = active, + database = database, comment = comment, ) - val v2Entity = v3Descriptor.toV2ServiceEntity() + val v2Entity = response.toV2ServiceEntity() assertThat(v2Entity.name.nameNotNull).isEqualTo(database) assertThat(v2Entity.active).isEqualTo(active) assertThat(v2Entity.desc).isEqualTo(comment) @@ -191,6 +220,60 @@ class V3MetadataConverterTest { } } + @Nested + inner class TableTypeConversionTest { + @ObjectSourceParameterizedTest + @ObjectSource( + """ + - v2: INDEXED + v3: EDGE + - v2: IMMUTABLE_INDEXED + v3: IMMUTABLE_EDGE + - v2: MULTI_EDGE + v3: MULTI_EDGE + - v2: VERTEX + v3: VERTEX + """, + ) + fun `V2 LabelType to V3 TableType`( + v2: String, + v3: String, + ) { + assertThat(LabelType.valueOf(v2).toTableType()).isEqualTo(TableType.valueOf(v3)) + } + + @ObjectSourceParameterizedTest + @ObjectSource( + """ + - v3: EDGE + v2: INDEXED + - v3: IMMUTABLE_EDGE + v2: IMMUTABLE_INDEXED + - v3: MULTI_EDGE + v2: MULTI_EDGE + - v3: VERTEX + v2: VERTEX + """, + ) + fun `V3 TableType to V2 LabelType`( + v3: String, + v2: String, + ) { + assertThat(TableType.valueOf(v3).toLabelType()).isEqualTo(LabelType.valueOf(v2)) + } + + @ObjectSourceParameterizedTest + @ObjectSource( + """ + - v2: HASH + - v2: NIL + """, + ) + fun `V2 only LabelType reads back as EDGE`(v2: String) { + assertThat(LabelType.valueOf(v2).toTableType()).isEqualTo(TableType.EDGE) + } + } + @Nested inner class TableConversionTest { @ObjectSourceParameterizedTest @@ -198,7 +281,8 @@ class V3MetadataConverterTest { """ - database: mydb table: mytable - labelType: HASH + labelType: INDEXED + tableType: EDGE sourceType: STRING sourceComment: source targetType: STRING @@ -209,10 +293,11 @@ class V3MetadataConverterTest { comment: test table """, ) - fun `LabelEntity to TableDescriptor`( + fun `LabelEntity to TableResponse`( database: String, table: String, labelType: String, + tableType: String, sourceType: String, sourceComment: String, targetType: String, @@ -222,264 +307,151 @@ class V3MetadataConverterTest { storage: String, comment: String, ) { - val edgeSchema = - EdgeSchema( - VertexField(VertexType.valueOf(sourceType), sourceComment), - VertexField(VertexType.valueOf(targetType), targetComment), - listOf( - com.kakao.actionbase.v2.core.types - .Field("score", DataType.INT, true, "score field"), - ), - ) val v2Entity = - LabelEntity( - active = true, - name = EntityName(database, table), - desc = comment, + labelEntity( + database = database, + table = table, + comment = comment, type = LabelType.valueOf(labelType), - schema = edgeSchema, + schema = + EdgeSchema( + VertexField(VertexType.valueOf(sourceType), sourceComment), + VertexField(VertexType.valueOf(targetType), targetComment), + listOf(V2Field("score", DataType.INT, true, "score field")), + ), dirType = V2DirectionType.valueOf(direction), storage = storage, - indices = - listOf( - V2Index("idx1", listOf(V2Index.Field("score", V2Order.DESC)), "index desc"), - ), - groups = emptyList(), - event = false, - readOnly = false, + indices = listOf(V2Index("idx1", listOf(V2Index.Field("score", V2Order.DESC)), "index desc")), mode = V2MutationMode.valueOf(mode), ) - val v3Descriptor = v2Entity.toV3TableDescriptor(tenant) as TableDescriptor.Edge - assertThat(v3Descriptor.tenant).isEqualTo(tenant) - assertThat(v3Descriptor.database).isEqualTo(database) - assertThat(v3Descriptor.table).isEqualTo(table) - assertThat(v3Descriptor.active).isTrue() - assertThat(v3Descriptor.comment).isEqualTo(comment) - assertThat(v3Descriptor.mode).isEqualTo(MutationMode.valueOf(mode)) - assertThat(v3Descriptor.storage).isEqualTo(storage) + val response = v2Entity.toTableResponse() + assertThat(response.database).isEqualTo(database) + assertThat(response.table).isEqualTo(table) + assertThat(response.active).isTrue() + assertThat(response.comment).isEqualTo(comment) + assertThat(response.type).isEqualTo(TableType.valueOf(tableType)) + assertThat(response.mode).isEqualTo(MutationMode.valueOf(mode)) + assertThat(response.storage).isEqualTo(storage) + assertThat(response.direction).isEqualTo(DirectionType.valueOf(direction)) - val schema = v3Descriptor.schema - assertThat(schema.direction).isEqualTo(DirectionType.valueOf(direction)) - assertThat(schema.source.type).isEqualTo(PrimitiveType.valueOf(sourceType)) - assertThat(schema.target.type).isEqualTo(PrimitiveType.valueOf(targetType)) - assertThat(schema.properties).hasSize(1) - assertThat(schema.properties[0].name).isEqualTo("score") - assertThat(schema.properties[0].type).isEqualTo(PrimitiveType.INT) - assertThat(schema.properties[0].nullable).isTrue() + assertThat(response.schema.source.type).isEqualTo(VertexType.valueOf(sourceType)) + assertThat(response.schema.source.comment).isEqualTo(sourceComment) + assertThat(response.schema.target.type).isEqualTo(VertexType.valueOf(targetType)) + assertThat(response.schema.properties).hasSize(1) + assertThat(response.schema.properties[0].name).isEqualTo("score") + assertThat(response.schema.properties[0].type).isEqualTo(DataType.INT) + assertThat(response.schema.properties[0].nullable).isTrue() - assertThat(schema.indexes).hasSize(1) - assertThat(schema.indexes[0].index).isEqualTo("idx1") - assertThat(schema.indexes[0].fields[0].field).isEqualTo("score") - assertThat(schema.indexes[0].fields[0].order).isEqualTo(V3Order.DESC) + assertThat(response.indexes).hasSize(1) + assertThat(response.indexes[0].index).isEqualTo("idx1") + assertThat(response.indexes[0].fields[0].field).isEqualTo("score") + assertThat(response.indexes[0].fields[0].order).isEqualTo(V3Order.DESC) } - } - @Nested - inner class TableCacheConversionTest { @ObjectSourceParameterizedTest @ObjectSource( """ - database: mydb - table: mytable - cacheName: cache1 - cacheLimit: 50 - cacheComment: cache desc + table: mymultiedge """, ) - fun `LabelEntity caches are preserved when converting to TableDescriptor Edge`( + fun `MultiEdge keeps its id as the _id property, exactly as V2 stores it`( database: String, table: String, - cacheName: String, - cacheLimit: Int, - cacheComment: String, ) { - val edgeSchema = - EdgeSchema( - VertexField(VertexType.STRING, "source"), - VertexField(VertexType.STRING, "target"), - listOf( - com.kakao.actionbase.v2.core.types - .Field("score", DataType.INT, true, "score field"), - ), - ) val v2Entity = - LabelEntity( - active = true, - name = EntityName(database, table), - desc = "test table", - type = LabelType.INDEXED, - schema = edgeSchema, - dirType = V2DirectionType.OUT, - storage = "datastore://test_namespace/test_table", - indices = emptyList(), - groups = emptyList(), - caches = - listOf( - Cache( - cache = cacheName, - fields = listOf(CacheField("score", V3Order.DESC)), - limit = cacheLimit, - comment = cacheComment, + labelEntity( + database = database, + table = table, + type = LabelType.MULTI_EDGE, + readOnly = true, + schema = + EdgeSchema( + VertexField(VertexType.LONG, "sender"), + VertexField(VertexType.LONG, "receiver"), + listOf( + V2Field("_id", DataType.LONG, false, "order id"), + V2Field("amount", DataType.INT, true, "amount"), ), ), - event = false, - readOnly = false, - mode = V2MutationMode.SYNC, ) - val v3Descriptor = v2Entity.toV3TableDescriptor(tenant) as TableDescriptor.Edge - val schema = v3Descriptor.schema - assertThat(schema.caches).hasSize(1) - assertThat(schema.caches[0].cache).isEqualTo(cacheName) - assertThat(schema.caches[0].limit).isEqualTo(cacheLimit) - assertThat(schema.caches[0].comment).isEqualTo(cacheComment) - assertThat(schema.caches[0].fields).hasSize(1) - assertThat(schema.caches[0].fields[0].field).isEqualTo("score") - assertThat(schema.caches[0].fields[0].order).isEqualTo(V3Order.DESC) + val response = v2Entity.toTableResponse() + assertThat(response.type).isEqualTo(TableType.MULTI_EDGE) + assertThat(response.schema.properties.map { it.name }).containsExactly("_id", "amount") } @ObjectSourceParameterizedTest @ObjectSource( """ - database: mydb - table: mymultiedge - cacheName: cache1 - cacheLimit: 100 - cacheComment: multi-edge cache + table: users + idType: STRING + idComment: user id + - database: mydb + table: users_long + idType: LONG + idComment: numeric user id """, ) - fun `LabelEntity caches are preserved when converting to TableDescriptor MultiEdge`( + fun `Vertex keeps its id in source, exactly as V2 stores it`( database: String, table: String, - cacheName: String, - cacheLimit: Int, - cacheComment: String, + idType: String, + idComment: String, ) { - val edgeSchema = - EdgeSchema( - VertexField(VertexType.STRING, "source"), - VertexField(VertexType.STRING, "target"), - listOf( - com.kakao.actionbase.v2.core.types - .Field("_id", DataType.STRING, false, "id field"), - com.kakao.actionbase.v2.core.types - .Field("score", DataType.INT, true, "score field"), - ), - ) val v2Entity = - LabelEntity( - active = true, - name = EntityName(database, table), - desc = "test multi edge", - type = LabelType.MULTI_EDGE, - schema = edgeSchema, + labelEntity( + database = database, + table = table, + type = LabelType.VERTEX, dirType = V2DirectionType.OUT, - storage = "datastore://test_namespace/test_table", - indices = emptyList(), - groups = emptyList(), - caches = - listOf( - Cache( - cache = cacheName, - fields = listOf(CacheField("score", V3Order.DESC)), - limit = cacheLimit, - comment = cacheComment, - ), + schema = + EdgeSchema( + VertexField(VertexType.valueOf(idType), idComment), + VertexField(VertexType.STRING, ""), + listOf(V2Field("name", DataType.STRING, false, "user name")), ), - event = false, - readOnly = true, - mode = V2MutationMode.SYNC, ) - val v3Descriptor = v2Entity.toV3TableDescriptor(tenant) as TableDescriptor.MultiEdge - val schema = v3Descriptor.schema - assertThat(schema.caches).hasSize(1) - assertThat(schema.caches[0].cache).isEqualTo(cacheName) - assertThat(schema.caches[0].limit).isEqualTo(cacheLimit) - assertThat(schema.caches[0].comment).isEqualTo(cacheComment) - assertThat(schema.caches[0].fields).hasSize(1) - assertThat(schema.caches[0].fields[0].field).isEqualTo("score") - assertThat(schema.caches[0].fields[0].order).isEqualTo(V3Order.DESC) + val response = v2Entity.toTableResponse() + assertThat(response.type).isEqualTo(TableType.VERTEX) + assertThat(response.schema.source.type).isEqualTo(VertexType.valueOf(idType)) + assertThat(response.schema.source.comment).isEqualTo(idComment) + assertThat(response.schema.target.comment).isEqualTo("") + assertThat(response.schema.properties.map { it.name }).containsExactly("name") } @ObjectSourceParameterizedTest @ObjectSource( """ - database: mydb - table: users - idType: STRING - idComment: user id - propertyName: name - propertyType: STRING - propertyNullable: false - propertyComment: user name - storage: "datastore://test_namespace/users" - comment: vertex table - - database: mydb - table: users_long - idType: LONG - idComment: numeric user id - propertyName: score - propertyType: INT - propertyNullable: true - propertyComment: user score - storage: "datastore://test_namespace/users_long" - comment: vertex table with numeric id + table: mytable + cacheName: cache1 + cacheLimit: 50 + cacheComment: cache desc """, ) - fun `LabelType VERTEX LabelEntity is restored as V3TableDescriptor Vertex`( + fun `caches pass through untouched`( database: String, table: String, - idType: String, - idComment: String, - propertyName: String, - propertyType: String, - propertyNullable: Boolean, - propertyComment: String, - storage: String, - comment: String, + cacheName: String, + cacheLimit: Int, + cacheComment: String, ) { - val edgeSchema = - EdgeSchema( - VertexField(VertexType.valueOf(idType), idComment), - VertexField(VertexType.STRING, ""), - listOf( - com.kakao.actionbase.v2.core.types - .Field(propertyName, DataType.valueOf(propertyType), propertyNullable, propertyComment), + val caches = + listOf( + Cache( + cache = cacheName, + fields = listOf(CacheField("score", V3Order.DESC)), + limit = cacheLimit, + comment = cacheComment, ), ) - val v2Entity = - LabelEntity( - active = true, - name = EntityName(database, table), - desc = comment, - type = LabelType.VERTEX, - schema = edgeSchema, - dirType = V2DirectionType.OUT, - storage = storage, - indices = emptyList(), - groups = emptyList(), - event = false, - readOnly = false, - mode = V2MutationMode.SYNC, - ) + val v2Entity = labelEntity(database = database, table = table, caches = caches) - val v3Descriptor = v2Entity.toV3TableDescriptor(tenant) as TableDescriptor.Vertex - assertThat(v3Descriptor.tenant).isEqualTo(tenant) - assertThat(v3Descriptor.database).isEqualTo(database) - assertThat(v3Descriptor.table).isEqualTo(table) - assertThat(v3Descriptor.active).isTrue() - assertThat(v3Descriptor.comment).isEqualTo(comment) - assertThat(v3Descriptor.storage).isEqualTo(storage) - - val schema = v3Descriptor.schema - assertThat(schema.id.type).isEqualTo(PrimitiveType.valueOf(idType)) - assertThat(schema.id.comment).isEqualTo(idComment) - assertThat(schema.properties).hasSize(1) - assertThat(schema.properties[0].name).isEqualTo(propertyName) - assertThat(schema.properties[0].nullable).isEqualTo(propertyNullable) - assertThat(schema.properties[0].comment).isEqualTo(propertyComment) + assertThat(v2Entity.toTableResponse().caches).isEqualTo(caches) } } @@ -495,7 +467,7 @@ class V3MetadataConverterTest { comment: test alias """, ) - fun `AliasEntity to AliasDescriptor`( + fun `AliasEntity to AliasResponse`( database: String, alias: String, table: String, @@ -509,13 +481,12 @@ class V3MetadataConverterTest { desc = comment, target = EntityName(database, table), ) - val v3Descriptor = v2Entity.toV3AliasDescriptor(tenant) - assertThat(v3Descriptor.tenant).isEqualTo(tenant) - assertThat(v3Descriptor.database).isEqualTo(database) - assertThat(v3Descriptor.alias).isEqualTo(alias) - assertThat(v3Descriptor.table).isEqualTo(table) - assertThat(v3Descriptor.active).isEqualTo(active) - assertThat(v3Descriptor.comment).isEqualTo(comment) + val response = v2Entity.toAliasResponse() + assertThat(response.database).isEqualTo(database) + assertThat(response.alias).isEqualTo(alias) + assertThat(response.table).isEqualTo(table) + assertThat(response.active).isEqualTo(active) + assertThat(response.comment).isEqualTo(comment) } @ObjectSourceParameterizedTest @@ -528,23 +499,22 @@ class V3MetadataConverterTest { comment: test alias """, ) - fun `AliasDescriptor to AliasEntity`( + fun `AliasResponse to AliasEntity`( database: String, alias: String, table: String, active: Boolean, comment: String, ) { - val v3Descriptor = - AliasDescriptor( - tenant = tenant, + val response = + AliasResponse( + active = active, database = database, alias = alias, table = table, - active = active, comment = comment, ) - val v2Entity = v3Descriptor.toV2AliasEntity() + val v2Entity = response.toV2AliasEntity() assertThat(v2Entity.name.service).isEqualTo(database) assertThat(v2Entity.name.nameNotNull).isEqualTo(alias) assertThat(v2Entity.target.service).isEqualTo(database) diff --git a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/query/PreparedQueryE2ETest.kt b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/query/PreparedQueryE2ETest.kt index 1474c961f..4f9f6369c 100644 --- a/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/query/PreparedQueryE2ETest.kt +++ b/server/src/test/kotlin/com/kakao/actionbase/server/api/graph/v3/query/PreparedQueryE2ETest.kt @@ -40,21 +40,21 @@ class PreparedQueryE2ETest : E2ETestBase() { """ { "table": "$rank", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": {"type": "string", "comment": "database|table|topk|entity|dimensionValues"}, - "target": {"type": "string", "comment": "topkDimensionValue"}, + "source": {"type": "STRING", "comment": "database|table|topk|entity|dimensionValues"}, + "target": {"type": "STRING", "comment": "topkDimensionValue"}, "properties": [ - {"name": "metric", "type": "long", "comment": "aggregated metric", "nullable": false}, - {"name": "additionalProperties", "type": "string", "comment": "carried properties as JSON", "nullable": true} - ], - "direction": "OUT", - "indexes": [ - {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} - ], - "groups": [], - "caches": [] + {"name": "metric", "type": "LONG", "comment": "aggregated metric", "nullable": false}, + {"name": "additionalProperties", "type": "STRING", "comment": "carried properties as JSON", "nullable": true} + ] }, + "direction": "OUT", + "indexes": [ + {"index": "metric_desc", "fields": [{"field": "metric", "order": "DESC"}]} + ], + "groups": [], + "caches": [], "storage": "datastore://test_namespace/$rank", "mode": "SYNC", "comment": "topk rank rows" @@ -72,39 +72,39 @@ class PreparedQueryE2ETest : E2ETestBase() { """ { "table": "$table", + "type": "MULTI_EDGE", "schema": { - "type": "MULTI_EDGE", - "id": {"type": "long", "comment": "order id"}, - "source": {"type": "string", "comment": "user"}, - "target": {"type": "string", "comment": "item"}, + "source": {"type": "STRING", "comment": "user"}, + "target": {"type": "STRING", "comment": "item"}, "properties": [ - {"name": "category", "type": "string", "comment": "item category", "nullable": false}, - {"name": "purchasedAt", "type": "long", "comment": "purchase time ms", "nullable": false} - ], - "direction": "OUT", - "indexes": [], - "groups": [{ - "group": "purchased_count", - "type": "COUNT", - "fields": [ - {"name": "_target"}, - {"name": "category"}, - {"name": "purchasedAt", "bucket": {"type": "date", "name": "day", "unit": "MILLISECOND", "timezone": "UTC", "format": "yyyy-MM-dd"}} - ], - "directionType": "OUT", - "aggregations": { - "topk": [{ - "topk": "$topkName", - "entity": "source", - "ranges": "_target:eq:{_target};category:eq:{category};day:bt:2023-11-14,2024-11-13", - "dimension": "target", - "rank": "$rankFqn", - "additionalProperties": ["category"] - }] - } - }], - "caches": [] + {"name": "_id", "type": "LONG", "comment": "order id", "nullable": false}, + {"name": "category", "type": "STRING", "comment": "item category", "nullable": false}, + {"name": "purchasedAt", "type": "LONG", "comment": "purchase time ms", "nullable": false} + ] }, + "direction": "OUT", + "indexes": [], + "groups": [{ + "group": "purchased_count", + "type": "COUNT", + "fields": [ + {"name": "_target"}, + {"name": "category"}, + {"name": "purchasedAt", "bucket": {"type": "date", "name": "day", "unit": "MILLISECOND", "timezone": "UTC", "format": "yyyy-MM-dd"}} + ], + "directionType": "OUT", + "aggregations": { + "topk": [{ + "topk": "$topkName", + "entity": "source", + "ranges": "_target:eq:{_target};category:eq:{category};day:bt:2023-11-14,2024-11-13", + "dimension": "target", + "rank": "$rankFqn", + "additionalProperties": ["category"] + }] + } + }], + "caches": [], "storage": "datastore://test_namespace/$table", "mode": "SYNC", "comment": "user-to-item purchase edges" diff --git a/website/src/content/docs/api-references/metadata.mdx b/website/src/content/docs/api-references/metadata.mdx index aeff6046f..9d1309c10 100644 --- a/website/src/content/docs/api-references/metadata.mdx +++ b/website/src/content/docs/api-references/metadata.mdx @@ -33,7 +33,7 @@ GET /graph/v3/databases ### Response Type -`List<`[DatabaseDescriptor](#databasedescriptor)`>` - List of databases +[DdlPage](#ddlpage)`<`[DatabaseResponse](#databaseresponse)`>` - Paged list of databases ### Request Example @@ -46,19 +46,10 @@ curl -X GET \ ### Response Example ```json -[ - { - "tenant": "default", - "database": "mydb", - "active": true, - "comment": "My database", - "revision": 1, - "createdAt": 1700000000000, - "createdBy": "admin", - "updatedAt": 1700000000000, - "updatedBy": "admin" - } -] +{ + "count": 1, + "content": [{ "active": true, "database": "mydb", "comment": "My database" }] +} ``` ## 2. Get Database @@ -80,7 +71,7 @@ GET /graph/v3/databases/{database} ### Response Type -[DatabaseDescriptor](#databasedescriptor) - Database information +[DatabaseResponse](#databaseresponse) - Database information Returns 404 if not found. @@ -95,17 +86,7 @@ curl -X GET \ ### Response Example ```json -{ - "tenant": "default", - "database": "mydb", - "active": true, - "comment": "My database", - "revision": 1, - "createdAt": 1700000000000, - "createdBy": "admin", - "updatedAt": 1700000000000, - "updatedBy": "admin" -} +{ "active": true, "database": "mydb", "comment": "My database" } ``` ## 3. Create Database @@ -132,7 +113,7 @@ POST /graph/v3/databases ### Response Type -[DatabaseDescriptor](#databasedescriptor) - Created database information +[DdlStatus](#ddlstatus)`<`[DatabaseResponse](#databaseresponse)`>` - Created database ### Request Example @@ -150,17 +131,7 @@ curl -X POST \ ### Response Example ```json -{ - "tenant": "default", - "database": "mydb", - "active": true, - "comment": "My database", - "revision": 1, - "createdAt": 1700000000000, - "createdBy": "admin", - "updatedAt": 1700000000000, - "updatedBy": "admin" -} +{ "active": true, "database": "mydb", "comment": "My database" } ``` ## 4. Update Database @@ -188,7 +159,7 @@ PUT /graph/v3/databases/{database} ### Response Type -[DatabaseDescriptor](#databasedescriptor) - Updated database information +[DdlStatus](#ddlstatus)`<`[DatabaseResponse](#databaseresponse)`>` - Updated database ### Request Example @@ -222,7 +193,7 @@ DELETE /graph/v3/databases/{database} ### Response Type -204 No Content (no response body) +[DdlStatus](#ddlstatus) with `status: "DELETED"` ### Request Example @@ -251,7 +222,7 @@ GET /graph/v3/databases/{database}/tables ### Response Type -`List<`[TableDescriptor](#tabledescriptor)`>` - List of tables +[DdlPage](#ddlpage)`<`[TableResponse](#tableresponse)`>` - Paged list of tables ### Request Example @@ -281,7 +252,7 @@ GET /graph/v3/databases/{database}/tables/{table} ### Response Type -[TableDescriptor](#tabledescriptor) - Table information +[TableResponse](#tableresponse) - Table information Returns 404 if not found. @@ -297,28 +268,22 @@ curl -X GET \ ```json { - "type": "edge", - "tenant": "default", + "active": true, "database": "mydb", "table": "follows", + "comment": "User follows relationship", + "type": "EDGE", "schema": { - "type": "edge", - "source": { "type": "string", "comment": "user id" }, - "target": { "type": "string", "comment": "user id" }, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": { "type": "STRING", "comment": "user id" }, + "target": { "type": "STRING", "comment": "user id" }, + "properties": [] }, + "direction": "OUT", "storage": "datastore://hbase/follows_table", - "mode": "SYNC", - "active": true, - "comment": "User follows relationship", - "revision": 1, - "createdAt": 1700000000000, - "createdBy": "admin", - "updatedAt": 1700000000000, - "updatedBy": "admin" + "indexes": [], + "groups": [], + "caches": [], + "mode": "SYNC" } ``` @@ -339,8 +304,13 @@ POST /graph/v3/databases/{database}/tables | Header | Authorization | Optional | Authentication key (reserved for future use) | | Path | database | Required | Database name | | Body | table | Required | Table name | -| Body | schema | Required | Edge schema definition | +| Body | type | Required | Table type (EDGE, IMMUTABLE_EDGE, MULTI_EDGE, VERTEX) | +| Body | schema | Required | Source / target / properties | +| Body | direction | Required | Direction type (OUT, IN, BOTH) | | Body | storage | Required | Storage URI (`datastore:///`) | +| Body | indexes | Optional | Index definitions | +| Body | groups | Optional | Group definitions | +| Body | caches | Optional | Cache definitions | | Body | mode | Optional | Mutation mode (default: SYNC) | | Body | comment | Required | Table description | @@ -350,7 +320,7 @@ POST /graph/v3/databases/{database}/tables ### Response Type -[TableDescriptor](#tabledescriptor) - Created table information +[DdlStatus](#ddlstatus)`<`[TableResponse](#tableresponse)`>` - Created table ### Request Example @@ -361,16 +331,16 @@ curl -X POST \ -H "Content-Type: application/json" \ -d '{ "table": "follows", + "type": "EDGE", "schema": { - "type": "EDGE", - "source": { "type": "string", "comment": "user id" }, - "target": { "type": "string", "comment": "user id" }, - "properties": [], - "direction": "OUT", - "indexes": [], - "groups": [] + "source": { "type": "STRING", "comment": "user id" }, + "target": { "type": "STRING", "comment": "user id" }, + "properties": [] }, + "direction": "OUT", "storage": "datastore://hbase/follows_table", + "indexes": [], + "groups": [], "mode": "SYNC", "comment": "User follows relationship" }' @@ -394,7 +364,10 @@ PUT /graph/v3/databases/{database}/tables/{table} | Path | database | Required | Database name | | Path | table | Required | Table name | | Body | active | Optional | Whether the table is active | -| Body | schema | Optional | Edge schema definition | +| Body | schema | Optional | Source / target / properties | +| Body | indexes | Optional | Index definitions | +| Body | groups | Optional | Group definitions | +| Body | caches | Optional | Cache definitions | | Body | mode | Optional | Mutation mode | | Body | comment | Optional | Table description | @@ -404,7 +377,7 @@ PUT /graph/v3/databases/{database}/tables/{table} ### Response Type -[TableDescriptor](#tabledescriptor) - Updated table information +[DdlStatus](#ddlstatus)`<`[TableResponse](#tableresponse)`>` - Updated table ### Request Example @@ -439,7 +412,7 @@ DELETE /graph/v3/databases/{database}/tables/{table} ### Response Type -204 No Content (no response body) +[DdlStatus](#ddlstatus) with `status: "DELETED"` ### Request Example @@ -468,7 +441,7 @@ GET /graph/v3/databases/{database}/aliases ### Response Type -`List<`[AliasDescriptor](#aliasdescriptor)`>` - List of aliases +[DdlPage](#ddlpage)`<`[AliasResponse](#aliasresponse)`>` - Paged list of aliases ### Request Example @@ -498,7 +471,7 @@ GET /graph/v3/databases/{database}/aliases/{alias} ### Response Type -[AliasDescriptor](#aliasdescriptor) - Alias information +[AliasResponse](#aliasresponse) - Alias information Returns 404 if not found. @@ -514,17 +487,11 @@ curl -X GET \ ```json { - "tenant": "default", + "active": true, "database": "mydb", "alias": "friends", "table": "follows", - "active": true, - "comment": "Alias for follows", - "revision": 1, - "createdAt": 1700000000000, - "createdBy": "admin", - "updatedAt": 1700000000000, - "updatedBy": "admin" + "comment": "Alias for follows" } ``` @@ -554,7 +521,7 @@ POST /graph/v3/databases/{database}/aliases ### Response Type -[AliasDescriptor](#aliasdescriptor) - Created alias information +[DdlStatus](#ddlstatus)`<`[AliasResponse](#aliasresponse)`>` - Created alias ### Request Example @@ -597,7 +564,7 @@ PUT /graph/v3/databases/{database}/aliases/{alias} ### Response Type -[AliasDescriptor](#aliasdescriptor) - Updated alias information +[DdlStatus](#ddlstatus)`<`[AliasResponse](#aliasresponse)`>` - Updated alias ### Request Example @@ -634,7 +601,7 @@ DELETE /graph/v3/databases/{database}/aliases/{alias} ### Response Type -204 No Content (no response body) +[DdlStatus](#ddlstatus) with `status: "DELETED"` ### Request Example @@ -646,158 +613,116 @@ curl -X DELETE \ ## Data Model -### DatabaseDescriptor - -Database information payload. - -```kotlin -data class DatabaseDescriptor( - val tenant: String, // Tenant name - val database: String, // Database name - val active: Boolean, // Whether the database is active - val comment: String, // Database description - val revision: Long, // Revision number - val createdAt: Long, // Creation timestamp - val createdBy: String, // Creator - val updatedAt: Long, // Last update timestamp - val updatedBy: String, // Last updater -) -``` - -### TableDescriptor +The v3 metadata surface is the v2 metastore shape with v3 names. Every response is flat: the table +type, direction, indexes, groups and caches sit beside `schema`, not inside it. -Table information payload. Two types are supported: `edge` and `multiEdge`. +### DdlPage -#### TableDescriptor.Edge +Listing envelope, shared with the v2 API. ```kotlin -data class TableDescriptor.Edge( - val type: String = "edge", // Table type discriminator - val tenant: String, // Tenant name - val database: String, // Database name - val table: String, // Table name - val schema: ModelSchema.Edge, // Edge schema definition - val storage: String, // Storage URI (`datastore:///
`) - val mode: MutationMode, // Mutation mode (SYNC, ASYNC, DROP) - val active: Boolean, // Whether the table is active - val comment: String, // Table description - val revision: Long, // Revision number - val createdAt: Long, // Creation timestamp - val createdBy: String, // Creator - val updatedAt: Long, // Last update timestamp - val updatedBy: String, // Last updater +data class DdlPage( + val count: Long, // Number of entries in `content` + val content: List, // The entries ) ``` -#### TableDescriptor.MultiEdge +### DdlStatus + +Mutation envelope, shared with the v2 API. `message` is omitted when null. ```kotlin -data class TableDescriptor.MultiEdge( - val type: String = "multiEdge", // Table type discriminator - val tenant: String, // Tenant name - val database: String, // Database name - val table: String, // Table name - val schema: ModelSchema.MultiEdge, // MultiEdge schema definition - val storage: String, // Storage URI (`datastore:///
`) - val mode: MutationMode, // Mutation mode (SYNC, ASYNC, DROP) - val active: Boolean, // Whether the table is active - val comment: String, // Table description - val revision: Long, // Revision number - val createdAt: Long, // Creation timestamp - val createdBy: String, // Creator - val updatedAt: Long, // Last update timestamp - val updatedBy: String, // Last updater +data class DdlStatus( + val status: Status, // CREATED, UPDATED, DELETED, IDLE, ERROR, BAD_REQUEST + val result: T?, // The resulting entity + val message: String?, // Failure detail, if any ) ``` -### AliasDescriptor +### DatabaseResponse -Alias information payload. +Database information payload. ```kotlin -data class AliasDescriptor( - val tenant: String, // Tenant name +data class DatabaseResponse( + val active: Boolean, // Whether the database is active val database: String, // Database name - val alias: String, // Alias name - val table: String, // Target table name - val active: Boolean, // Whether the alias is active - val comment: String, // Alias description - val revision: Long, // Revision number - val createdAt: Long, // Creation timestamp - val createdBy: String, // Creator - val updatedAt: Long, // Last update timestamp - val updatedBy: String, // Last updater + val comment: String, // Database description ) ``` -### ModelSchema.Edge +### TableResponse -Edge schema definition. The `type` discriminator is `"EDGE"` in requests and `"edge"` in responses. +Table information payload. One shape for all four table types; `type` says which. ```kotlin -data class ModelSchema.Edge( - val type: String, // "EDGE" (request) / "edge" (response) - val source: Field, // Source vertex field - val target: Field, // Target vertex field - val properties: List = emptyList(),// Edge properties - val direction: DirectionType, // Direction type (OUT, IN, BOTH) - val indexes: List = emptyList(), // Index definitions - val groups: List = emptyList(), // Group definitions - val caches: List = emptyList(), // Cache definitions +data class TableResponse( + val active: Boolean, // Whether the table is active + val database: String, // Database name + val table: String, // Table name + val comment: String, // Table description + val type: TableType, // EDGE, IMMUTABLE_EDGE, MULTI_EDGE, VERTEX + val schema: TableSchema, // Source, target and properties + val direction: DirectionType, // OUT, IN, BOTH + val storage: String, // Storage URI (`datastore:///
`) + val indexes: List, // Index definitions + val groups: List, // Group definitions + val caches: List, // Cache definitions + val mode: MutationMode, // SYNC, ASYNC, DROP ) ``` -### ModelSchema.MultiEdge +### AliasResponse -MultiEdge schema definition. Supports multiple edges between the same source-target pair, distinguished by an `id` field. The `type` discriminator is `"MULTI_EDGE"` in requests and `"multiEdge"` in responses. +Alias information payload. ```kotlin -data class ModelSchema.MultiEdge( - val type: String, // "MULTI_EDGE" (request) / "multiEdge" (response) - val id: Field, // Edge identifier field (distinguishes multiple edges) - val source: Field, // Source vertex field - val target: Field, // Target vertex field - val properties: List = emptyList(),// Edge properties - val direction: DirectionType, // Direction type (OUT, IN, BOTH) - val indexes: List = emptyList(), // Index definitions - val groups: List = emptyList(), // Group definitions - val caches: List = emptyList(), // Cache definitions +data class AliasResponse( + val active: Boolean, // Whether the alias is active + val database: String, // Database name + val alias: String, // Alias name + val table: String, // Target table name + val comment: String, // Alias description ) ``` -### Field +### TableSchema -Vertex field definition. The `type` value is lowercase in JSON (e.g., `"string"`, `"long"`). +Schema definition, shared by every table type. ```kotlin -data class Field( - val type: PrimitiveType, // Data type (JSON: lowercase, e.g., "string", "long") - val comment: String, // Field description +data class TableSchema( + val source: Key, // Source key field + val target: Key, // Target key field + val properties: List, // Property fields ) -``` - -### StructField -Property field definition. The `type` value is lowercase in JSON (e.g., `"int"`, `"long"`). +data class Key( + val type: VertexType, // LONG or STRING + val comment: String, // Field description +) -```kotlin -data class StructField( - val name: String, // Field name - val type: PrimitiveType, // Data type (JSON: lowercase, e.g., "int", "long") - val comment: String, // Field description - val nullable: Boolean, // Whether the field is nullable +data class Property( + val name: String, // Field name + val type: DataType, // Data type + val nullable: Boolean, // Whether the field is nullable (default: false) + val comment: String, // Field description ) ``` +A **multi-edge** table carries its id as the `_id` property; a **vertex** table carries its id in +`source` and leaves `target` as a placeholder. Both follow the v2 layout exactly, so a v2 payload +converts by renaming keys and nothing else. + ### Index Index definition. ```kotlin data class Index( - val index: String, // Index name - val fields: List, // Indexed fields with sort order - val comment: String = "", // Index description + val index: String, // Index name + val fields: List, // Indexed fields with sort order + val comment: String = "", // Index description ) ``` @@ -841,7 +766,9 @@ data class Cache( ) ``` -> **Updating `indexes` / `groups` / `caches` via PUT**: these fields live inside `ModelSchema`, so a `PUT /graph/v3/databases/{database}/tables/{table}` body that omits `schema` does **not** change any of them. To change a cache, send the full `schema` payload with the desired `caches` array. This matches the existing semantics for `indexes` and `groups`. +> **Updating `indexes` / `groups` / `caches` via PUT**: each is a top-level field. Omitting one +> leaves it untouched; sending one replaces it wholesale. A table's `type` and `direction` are fixed +> at creation and cannot be changed. ### DatabaseCreateRequest @@ -872,13 +799,21 @@ Table creation request payload. ```kotlin data class TableCreateRequest( val table: String, // Table name - val schema: ModelSchema, // Schema definition (ModelSchema.Edge or ModelSchema.MultiEdge) + val type: TableType, // EDGE, IMMUTABLE_EDGE, MULTI_EDGE, VERTEX + val schema: TableSchema, // Source, target and properties + val direction: DirectionType, // OUT, IN, BOTH val storage: String, // Storage URI (`datastore:///
`) + val indexes: List, // Index definitions (default: empty) + val groups: List, // Group definitions (default: empty) + val caches: List, // Cache definitions (default: empty) val mode: MutationMode, // Mutation mode (default: SYNC) val comment: String, // Table description ) ``` +An `IMMUTABLE_EDGE` table accepts at most one index and cannot use `BOTH`; a `VERTEX` table must use +`OUT` and carry no index, group or cache. + ### TableUpdateRequest Table update request payload. @@ -886,7 +821,10 @@ Table update request payload. ```kotlin data class TableUpdateRequest( val active: Boolean?, // Whether the table is active (optional) - val schema: ModelSchema?, // Schema definition (optional, ModelSchema.Edge or ModelSchema.MultiEdge) + val schema: TableSchema?, // Source, target and properties (optional) + val indexes: List?, // Index definitions (optional) + val groups: List?, // Group definitions (optional) + val caches: List?, // Cache definitions (optional) val mode: MutationMode?, // Mutation mode (optional) val comment: String?, // Table description (optional) ) @@ -919,29 +857,33 @@ data class AliasUpdateRequest( ### Enums ```kotlin +enum class TableType { EDGE, IMMUTABLE_EDGE, MULTI_EDGE, VERTEX } enum class MutationMode { SYNC, ASYNC, DROP, DENY } enum class DirectionType { BOTH, OUT, IN } -enum class PrimitiveType { BOOLEAN, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, STRING, OBJECT } +enum class VertexType { LONG, STRING } +enum class DataType { BOOLEAN, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, DECIMAL, STRING, JSON } enum class Order { ASC, DESC } enum class GroupType { SUM, COUNT } ``` ### Validation Rules -| Field | Rule | -| ----------------------------- | -------------------------------------------------------------------- | -| Database / Table / Alias name | `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$` — starts with a letter, max 64 chars | -| Storage URI | `^datastore://[a-z_]+/[a-zA-Z0-9_]+$` | -| Comment | Max 1000 characters | +| Field | Rule | +| ----------------------------- | ------------------------------------------------------------------------------------------- | +| Database / Table / Alias name | `^[a-z][a-z0-9_]{0,63}$` — starts with a lowercase letter, lowercase alphanumeric/underscore | +| Storage URI | `^datastore://([a-z][a-z0-9_]*)?/[a-z][a-z0-9_]*$` (namespace optional) | +| Comment | Max 1000 characters | ## V2 Compatibility -V3 API wraps the existing V2 DDL services with a new interface. The table below shows the mapping between V2 and V3 terminology. +The v3 API is the v2 DDL surface with v3 names. Both dialects describe the same stored entity, so a +v2 payload becomes a v3 payload by renaming keys — the structure, the nesting and the enum values +are unchanged. `/graph/v2` stays available for backward compatibility. ### Terminology Mapping @@ -951,33 +893,64 @@ V3 API wraps the existing V2 DDL services with a new interface. The table below | Label | Table | Table with edge schema | | Alias | Alias | Table alias (unchanged) | -### Label Type Mapping - -| V2 LabelType | V3 Table Type | Conversion | -| ------------ | ------------- | ----------------------------------- | -| INDEXED | edge | Bidirectional | -| MULTI_EDGE | multiEdge | Bidirectional | -| HASH | edge | One-way (V2 to V3 only, deprecated) | +### Field Mapping + +| V2 field | V3 field | Note | +| ------------------------- | --------------------------- | ---------------------------------------- | +| `name: "service.label"` | `database` + `table` | Split into two fields | +| `desc` | `comment` | At every level | +| `schema.src` | `schema.source` | | +| `schema.tgt` | `schema.target` | | +| `schema.fields` | `schema.properties` | | +| `dirType` | `direction` | Stays top-level | +| `indices` | `indexes` | Stays top-level | +| `indices[].name` | `indexes[].index` | | +| `indices[].fields[].name` | `indexes[].fields[].field` | | +| `indices[].desc` | `indexes[].comment` | | +| `groups`, `caches` | `groups`, `caches` | Unchanged | +| `storage` | `storage` | Unchanged | +| `event`, `readOnly` | — | Derived by the server, not on the surface | + +### Table Type Mapping + +| V2 LabelType | V3 TableType | Conversion | +| ------------------- | ---------------- | ----------------------------------- | +| INDEXED | EDGE | Bidirectional | +| IMMUTABLE_INDEXED | IMMUTABLE_EDGE | Bidirectional | +| MULTI_EDGE | MULTI_EDGE | Bidirectional | +| VERTEX | VERTEX | Bidirectional | +| HASH, NIL | EDGE | Read-only (V2 to V3), not creatable | + +A multi-edge table keeps its id as the `_id` property and a vertex table keeps its id in `source`, +exactly as v2 stores them. + +### Response Envelopes + +| Operation | V2 and V3 response | +| --------- | ---------------------------- | +| List | `{count, content}` | +| Mutation | `{status, result, message}` | +| Get one | The entity itself | ### API Endpoint Mapping -| V2 Endpoint | V3 Endpoint | -| ----------------------------------------------------- | ------------------------------------------------------- | -| `GET /graph/v2/services` | `GET /graph/v3/databases` | -| `GET /graph/v2/services/{service}` | `GET /graph/v3/databases/{database}` | -| `POST /graph/v2/services/{service}` | `POST /graph/v3/databases` | -| `PUT /graph/v2/services/{service}` | `PUT /graph/v3/databases/{database}` | -| `DELETE /graph/v2/services/{service}` | `DELETE /graph/v3/databases/{database}` | -| `GET /graph/v2/services/{service}/labels` | `GET /graph/v3/databases/{database}/tables` | -| `GET /graph/v2/services/{service}/labels/{label}` | `GET /graph/v3/databases/{database}/tables/{table}` | -| `POST /graph/v2/services/{service}/labels/{label}` | `POST /graph/v3/databases/{database}/tables` | -| `PUT /graph/v2/services/{service}/labels/{label}` | `PUT /graph/v3/databases/{database}/tables/{table}` | -| `DELETE /graph/v2/services/{service}/labels/{label}` | `DELETE /graph/v3/databases/{database}/tables/{table}` | -| `GET /graph/v2/services/{service}/aliases` | `GET /graph/v3/databases/{database}/aliases` | -| `GET /graph/v2/services/{service}/aliases/{alias}` | `GET /graph/v3/databases/{database}/aliases/{alias}` | -| `POST /graph/v2/services/{service}/aliases/{alias}` | `POST /graph/v3/databases/{database}/aliases` | -| `PUT /graph/v2/services/{service}/aliases/{alias}` | `PUT /graph/v3/databases/{database}/aliases/{alias}` | -| `DELETE /graph/v2/services/{service}/aliases/{alias}` | `DELETE /graph/v3/databases/{database}/aliases/{alias}` | +| V2 Endpoint | V3 Endpoint | +| ---------------------------------------------------- | ------------------------------------------------------- | +| `GET /graph/v2/service` | `GET /graph/v3/databases` | +| `GET /graph/v2/service/{service}` | `GET /graph/v3/databases/{database}` | +| `POST /graph/v2/service/{service}` | `POST /graph/v3/databases` | +| `PUT /graph/v2/service/{service}` | `PUT /graph/v3/databases/{database}` | +| `DELETE /graph/v2/admin/service/{service}` | `DELETE /graph/v3/databases/{database}` | +| `GET /graph/v2/service/{service}/label` | `GET /graph/v3/databases/{database}/tables` | +| `GET /graph/v2/service/{service}/label/{label}` | `GET /graph/v3/databases/{database}/tables/{table}` | +| `POST /graph/v2/service/{service}/label/{label}` | `POST /graph/v3/databases/{database}/tables` | +| `PUT /graph/v2/service/{service}/label/{label}` | `PUT /graph/v3/databases/{database}/tables/{table}` | +| `DELETE /graph/v2/admin/service/{service}/label/{label}` | `DELETE /graph/v3/databases/{database}/tables/{table}` | +| `GET /graph/v2/service/{service}/alias` | `GET /graph/v3/databases/{database}/aliases` | +| `GET /graph/v2/service/{service}/alias/{alias}` | `GET /graph/v3/databases/{database}/aliases/{alias}` | +| `POST /graph/v2/service/{service}/alias/{alias}` | `POST /graph/v3/databases/{database}/aliases` | +| `PUT /graph/v2/service/{service}/alias/{alias}` | `PUT /graph/v3/databases/{database}/aliases/{alias}` | +| `DELETE /graph/v2/admin/service/{service}/alias/{alias}` | `DELETE /graph/v3/databases/{database}/aliases/{alias}` | ### MutationMode Mapping diff --git a/website/src/content/docs/design/glossary.mdx b/website/src/content/docs/design/glossary.mdx index 018daa17d..f6a4bc62a 100644 --- a/website/src/content/docs/design/glossary.mdx +++ b/website/src/content/docs/design/glossary.mdx @@ -9,9 +9,9 @@ Key terms used in Actionbase documentation. ## Schema Version Mapping -v2 and v3 map almost 1:1. +v3 is v2 with v3 names: the same structure and the same values, renamed key by key. -| v2 (Current) | v3 (Future) | Description | +| v2 | v3 | Description | | ------------ | ----------- | ----------------------------- | | service | database | Namespace for grouping tables | | label | table | Edge schema definition | @@ -23,7 +23,7 @@ v2 and v3 map almost 1:1. | indices | indexes | Query indexes | | desc | comment | Description | -- **Schema API**: v2 — migrating to v3 +- **Schema API**: both `/graph/v2` and `/graph/v3` — v3 is the same surface under v3 names - **Query/Mutation APIs**: v3 ## Data Model diff --git a/website/src/content/docs/design/schema.mdx b/website/src/content/docs/design/schema.mdx index fc5e70f81..dd8e17839 100644 --- a/website/src/content/docs/design/schema.mdx +++ b/website/src/content/docs/design/schema.mdx @@ -159,9 +159,9 @@ Useful for gradual migrations or domain-specific naming. ## Schema Versions -v2 and v3 map almost 1:1. +v3 is v2 with v3 names: the same structure and the same values, renamed key by key. -| v2 (Current) | v3 (Future) | +| v2 | v3 | | ------------ | ----------- | | service | database | | label | table |