From b9406675d6a2e1a7254f240978dedf9e8e42b514 Mon Sep 17 00:00:00 2001 From: Minseok Kim Date: Sat, 12 Sep 2026 22:54:35 +0900 Subject: [PATCH 1/3] BEHAVIORAL: reshape the v3 metadata surface to v2's structure under v3 names The v3 metadata API did not just rename v2's vocabulary, it redesigned the payload: the table type became a Jackson discriminator on `schema`, direction / indexes / groups / caches moved inside `schema`, a multi-edge id was hoisted out of `_id` and a vertex id out of `src`, type values became lowercase, and the v2 response envelopes were dropped. A v2 payload therefore could not be carried to v3 by renaming keys, which left every ecosystem client (the Go CLI, the ops scripts, the console form, the DDL fixtures) maintaining two shapes. Make the surface v2's structure with v3's names, so the two dialects differ by key names alone: desc -> comment dirType -> direction schema.src -> schema.source indices -> indexes schema.tgt -> schema.target .name -> .index / .field schema.fields -> schema.properties INDEXED -> EDGE IMMUTABLE_INDEXED -> IMMUTABLE_EDGE mode IGNORE -> mode DROP name "a.b" -> database + table Type values stay uppercase because the surface now carries v2's own `DataType` and `VertexType`; `DECIMAL` no longer collapses into `OBJECT` on the way through. A multi-edge keeps its id as the `_id` property and a vertex keeps its id in `source`, exactly as v2 stores them. Listings return `{count, content}` and mutations `{status, result, message}`, the same envelopes v2 serves. The v2-only `event` and `readOnly` flags leave the surface; the server derives them. `V3MetadataConverter` is now a pure rename in both directions -- all the schema reshaping is gone. The engine is untouched: `ModelSchema`, `V3TableDescriptor`, `V2BackedTableBinding`, `PrimitiveType` and the `/graph/v3/.../edges` data plane are exactly as they were, and `/graph/v2` keeps serving its own dialect. The immutable-edge invariants (at most one index, never BOTH) lived in `ModelSchema.ImmutableEdge.init`, which the surface no longer builds, so they move to `TableCreateRequest` -- otherwise creation would succeed and the table would fail later, when the runtime projects it. Co-Authored-By: Claude Opus 5 (1M context) --- .../api/graph/v3/metadata/AliasController.kt | 33 +- .../api/graph/v3/metadata/AliasResponse.kt | 15 + .../graph/v3/metadata/DatabaseController.kt | 32 +- .../v3/metadata/DatabaseCreateRequest.kt | 15 + .../api/graph/v3/metadata/DatabaseResponse.kt | 13 + .../v3/metadata/DatabaseUpdateRequest.kt | 11 + .../api/graph/v3/metadata/DdlEnvelopes.kt | 19 + .../api/graph/v3/metadata/TableController.kt | 33 +- .../graph/v3/metadata/TableCreateRequest.kt | 95 +-- .../api/graph/v3/metadata/TableResponse.kt | 29 + .../api/graph/v3/metadata/TableSchema.kt | 44 ++ .../server/api/graph/v3/metadata/TableType.kt | 14 + .../graph/v3/metadata/TableUpdateRequest.kt | 87 +-- .../api/graph/v3/metadata/V3CompatService.kt | 131 ++-- .../graph/v3/metadata/V3MetadataConverter.kt | 340 +++------- .../queue/v1/metadata/QueueMetadataService.kt | 41 +- .../admin/HBaseAdminTeardownGuardE2ETest.kt | 16 +- .../api/graph/v2/edge/V2EdgeStorageOpsSpec.kt | 16 +- .../api/graph/v3/ActionbaseQueryE2ETest.kt | 70 +- .../graph/v3/EdgeAggCountSentinelE2ETest.kt | 30 +- .../api/graph/v3/EdgeCacheQueryE2ETest.kt | 92 +-- .../api/graph/v3/EdgeMutationForceSyncTest.kt | 34 +- .../graph/v3/EdgeMutationInsertMergeTest.kt | 18 +- .../graph/v3/EdgeMutationStorageOpsSpec.kt | 34 +- .../graph/v3/EdgeMutationValidationTest.kt | 18 +- .../api/graph/v3/ImmutableEdgeE2ETest.kt | 56 +- .../graph/v3/MutationContextOptInE2ETest.kt | 31 +- .../api/graph/v3/VertexIntegrationTest.kt | 18 +- .../hbase/DatastoreTableReferencesE2ETest.kt | 16 +- .../graph/v3/metadata/AliasControllerTest.kt | 34 +- .../v3/metadata/DatabaseControllerTest.kt | 20 +- .../metadata/MetadataAggControllerE2ETest.kt | 147 ++--- .../MetadataAggQueryControllerE2ETest.kt | 84 +-- .../graph/v3/metadata/TableControllerTest.kt | 423 ++++++------ .../v3/metadata/V2V3CompatibilityTest.kt | 618 ++++++++++-------- .../v3/metadata/V3MetadataConverterTest.kt | 448 ++++++------- .../graph/v3/query/PreparedQueryE2ETest.kt | 84 +-- 37 files changed, 1543 insertions(+), 1716 deletions(-) create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/AliasResponse.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseCreateRequest.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseResponse.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DatabaseUpdateRequest.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/DdlEnvelopes.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableResponse.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableSchema.kt create mode 100644 server/src/main/kotlin/com/kakao/actionbase/server/api/graph/v3/metadata/TableType.kt 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" From a8b244897530390b837d5efac562c2534f61fa81 Mon Sep 17 00:00:00 2001 From: Minseok Kim Date: Sat, 12 Sep 2026 22:54:44 +0900 Subject: [PATCH 2/3] STRUCTURAL: drop the descriptor types the v3 surface no longer builds `TableDescriptor`, `DatabaseDescriptor` and `AliasDescriptor` were only ever the v3 API's response types -- the engine reads `ModelSchema` and projects it through `v2.engine.v3.V3TableDescriptor`, never these. With the surface serving its own flat responses, nothing constructs them, and the `Id` hierarchy and `payload/Database*Request` under them have no callers either. `V2ServiceDescriptor.toV3` and `V2AliasDescriptor.toV3` converted to two of the removed types. Both live in `core.v2.metadata`, a parallel v2 model whose only callers are its own serialization tests, so they go with them. `Id` stays: those same `core.v2.metadata` identifiers still implement it. No behavior changes -- nothing reachable from an endpoint referenced any of this. Co-Authored-By: Claude Opus 5 (1M context) --- .../core/metadata/AliasDescriptor.kt | 22 -- .../kakao/actionbase/core/metadata/AliasId.kt | 7 - .../core/metadata/DatabaseDescriptor.kt | 34 --- .../actionbase/core/metadata/DatabaseId.kt | 6 - .../actionbase/core/metadata/DatastoreId.kt | 6 - .../core/metadata/TableDescriptor.kt | 113 -------- .../kakao/actionbase/core/metadata/TableId.kt | 7 - .../actionbase/core/metadata/TenantId.kt | 5 - .../actionbase/core/metadata/V3Descriptor.kt | 21 -- .../metadata/payload/DatabaseCreateRequest.kt | 8 - .../metadata/payload/DatabaseUpdateRequest.kt | 7 - .../core/v2/metadata/V2AliasDescriptor.kt | 11 - .../core/v2/metadata/V2ServiceDescriptor.kt | 10 - .../core/metadata/AliasSerializationTest.kt | 66 ----- .../metadata/DatabaseSerializationTest.kt | 60 ----- .../core/metadata/TableSerializationTest.kt | 251 ------------------ .../v2/metadata/V2AliasSerializationTest.kt | 67 ----- .../v2/metadata/V2ServiceSerializationTest.kt | 84 ------ 18 files changed, 785 deletions(-) delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasDescriptor.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/AliasId.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseDescriptor.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatabaseId.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/DatastoreId.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableDescriptor.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/TableId.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/TenantId.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/V3Descriptor.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseCreateRequest.kt delete mode 100644 core/src/main/kotlin/com/kakao/actionbase/core/metadata/payload/DatabaseUpdateRequest.kt delete mode 100644 core/src/test/kotlin/com/kakao/actionbase/core/metadata/AliasSerializationTest.kt delete mode 100644 core/src/test/kotlin/com/kakao/actionbase/core/metadata/DatabaseSerializationTest.kt delete mode 100644 core/src/test/kotlin/com/kakao/actionbase/core/metadata/TableSerializationTest.kt 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)) - } } From b597bbcaad0751d2a4b548759c865c5d8f20379c Mon Sep 17 00:00:00 2001 From: Minseok Kim Date: Sat, 12 Sep 2026 22:54:52 +0900 Subject: [PATCH 3/3] docs: describe the v3 metadata surface as v2 renamed Rewrite the metadata API reference against the flat surface: the data model, the request and response examples, the envelopes, and a field-by-field v2 mapping table in place of the old three-row terminology list. Two rules in the reference were already wrong and are corrected here: the name pattern is `^[a-z][a-z0-9_]{0,63}$`, not the mixed-case form, and the storage URI allows an empty namespace. The glossary and schema pages carried the mapping as "v2 (Current) -> v3 (Future)". The mapping is now what the API actually serves, so say so. Co-Authored-By: Claude Opus 5 (1M context) --- .../content/docs/api-references/metadata.mdx | 425 ++++++++---------- website/src/content/docs/design/glossary.mdx | 6 +- website/src/content/docs/design/schema.mdx | 4 +- 3 files changed, 204 insertions(+), 231 deletions(-) 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 |