-
Notifications
You must be signed in to change notification settings - Fork 77
feat(optimizer): [2/N] Optimizer REST Service and Controller #531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
140 commits
Select commit
Hold shift + click to select a range
2119555
feat(optimizer): add data model — schema, entities, DTOs, converters
mkuchenbecker 3c93d52
fix: address PR review feedback on optimizer data model
mkuchenbecker d419eb3
feat(optimizer): add repositories and repository tests
mkuchenbecker 7ff3b43
fix: consolidate repo methods — single find with optional filters
mkuchenbecker f7f6812
feat(optimizer): add REST service layer, controllers, and shared module
mkuchenbecker ef3260f
fix: update service impl to use consolidated find methods
mkuchenbecker ac1da01
feat(optimizer): add apps/optimizer shared module with find-only repos
mkuchenbecker be353ca
Merge mkuchenb/optimizer-1 into optimizer-2
mkuchenbecker 02a5ab3
fix: remove orphan fields from CompleteOperationRequest
mkuchenbecker 5c78c8f
Merge mkuchenb/optimizer-0 into optimizer-1
mkuchenbecker 2ddc445
Merge mkuchenb/optimizer-1 into optimizer-2
mkuchenbecker 01466c7
feat(optimizer): add service-layer integration tests
mkuchenbecker ff07fde
fix: assert stats history delta values in upsert test
mkuchenbecker 1cbe556
Merge branch 'main' into mkuchenb/optimizer-0
mkuchenbecker 231e1a1
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker ff448a0
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker f82d1b3
fix(optimizer): address PR #527 review feedback
mkuchenbecker e907a31
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker a109f02
fix(optimizer): propagate optimizer-0 renames into repos and tests
mkuchenbecker a1ef430
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker df01c26
fix(optimizer): propagate optimizer-0 renames into service + controller
mkuchenbecker 027fccd
fix(optimizer): add databaseName + tableName to apps/optimizer histor…
mkuchenbecker e0a49da
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 79753f1
fix(optimizer): index table_operations_history on (database_name, tab…
mkuchenbecker ae610ae
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 85a432f
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker dceef97
feat(optimizer): unify REST prefix to /v1/optimizer; add name-based h…
mkuchenbecker bf04488
fix(optimizer): align apps/optimizer entities with services schema
mkuchenbecker f2ac002
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 62f426a
feat(optimizer): add findLatestPerTable to history repo
mkuchenbecker e13a31b
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 3483b25
perf(optimizer): index table_operations_history for findLatestPerTable
mkuchenbecker 9748548
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 0293009
feat(optimizer): add findDistinctDatabaseNames to TableStatsRepository
mkuchenbecker 0efab45
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 6fa885d
refactor(optimizer): Optional<T> for optional filter params in servic…
mkuchenbecker eba1392
feat(optimizer): promote internal model types to shared apps/optimizer
mkuchenbecker 10ed1bb
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker e576593
refactor(optimizer): rename apps/optimizer entities + repos to plural…
mkuchenbecker 9f88a4a
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 6f98e1a
refactor(optimizer): consolidate entities/repos into apps/optimizer; …
mkuchenbecker d90c26f
refactor(optimizer): move apps/optimizer module into services/optimizer
mkuchenbecker 62f33b7
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 17e280f
refactor(optimizer): drop apps/optimizer-data dep; simplify history API
mkuchenbecker 9a129a8
refactor(optimizer): align data model — rename HistoryStatus; String …
mkuchenbecker a8978a0
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker dfb9102
refactor(optimizer): realign entity shapes with optimizer-0
mkuchenbecker e3bf9e1
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 681407e
feat(optimizer): add internal model layer
mkuchenbecker 2005bca
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker b689969
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker e3fb777
perf(optimizer): index table_operations_history for findLatestPerTable
mkuchenbecker f89889d
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker beaaf88
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker d3e1726
refactor(optimizer): enforce layer boundaries in api/ + model/
mkuchenbecker db9513a
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 1d469a7
refactor(optimizer): remove db-layer types from optimizer-0
mkuchenbecker eee8eca
refactor(optimizer): remove DB schema + schema-init properties
mkuchenbecker 0567753
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 328e5b9
refactor(optimizer): scrub MySQL / JPA / datasource references
mkuchenbecker f7a5d20
refactor(optimizer): drop UpsertTableOperationsRequest
mkuchenbecker 2a532b5
refactor(optimizer): drop JobResult from the wire and internal model
mkuchenbecker 2e3a231
feat(optimizer): add debug echo fields to CompleteOperationRequest
mkuchenbecker db5eb29
refactor(optimizer): move application.properties out of optimizer-0
mkuchenbecker bbcf84a
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker ac3abc0
feat(optimizer): introduce db/ layer with per-layer types
mkuchenbecker e79eec7
refactor(optimizer): split TableStats envelope into snapshot + delta …
mkuchenbecker f955ded
fix(optimizer): drop CommitDeltaMetrics from TableStatsRow
mkuchenbecker 13987c1
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 969949d
refactor(optimizer): rewire service layer onto api/model/db mappers
mkuchenbecker 861b584
feat(optimizer): extend model layer for service-only types
mkuchenbecker 41d4c6d
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 69d9e8f
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker b60a3bf
feat(optimizer): extend ModelDbMapper for service-only types
mkuchenbecker eb6e3be
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker b80b2e5
refactor(optimizer): service layer returns only model/ types
mkuchenbecker 25d98aa
feat(optimizer): restore batch CAS methods on TableOperationsRepository
mkuchenbecker 31fac5b
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 188713d
docs(optimizer): comment every field on opt-0 api/ and model/ types
mkuchenbecker f060b5e
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 1119699
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 8d64273
refactor(optimizer): remove clusterId from SnapshotMetrics
mkuchenbecker ee7bcab
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker c1ad246
refactor(optimizer): comment every db/ field; drop clusterId and version
mkuchenbecker 72b431c
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker c72aae8
refactor(optimizer): move api↔model conversion onto api types; delete…
mkuchenbecker 1fca287
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 8ae8777
refactor(optimizer): move model↔db conversion onto model types; delet…
mkuchenbecker b3aacff
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker bb8aa4d
refactor(optimizer): service + controllers use type to/from methods
mkuchenbecker af23d5e
fix(optimizer): make TableStats self-describing; route DTO conversion…
mkuchenbecker 3864e42
chore(optimizer): cascade self-describing TableStats from opt-0 to opt-1
mkuchenbecker 0a1125b
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker a6045b5
feat(optimizer): add TableStats↔TableStatsRow conversion on model
mkuchenbecker 4427de0
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker db5921e
refactor(optimizer): service stats methods take/return TableStats, no…
mkuchenbecker 3aebf64
chore(optimizer): enable toBuilder on model.Table and model.TableOper…
mkuchenbecker bf30f86
chore(optimizer): cascade toBuilder annotations from opt-0 to opt-1
mkuchenbecker faba6d7
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker b6c7f42
refactor(optimizer): drop fileCount enrichment from model.TableOperation
mkuchenbecker 177af95
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 487ac56
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 2b06c92
feat(repo): add findClaimedIds for transactional batch-claim verifica…
mkuchenbecker 5b5aae2
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 437a0ed
refactor(optimizer): add Dto suffix to all api/model classes (PR #527…
mkuchenbecker aabb51c
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker 928d537
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker eedf6d0
refactor(optimizer): update controllers for renamed api/model Dto types
mkuchenbecker 4f98c22
refactor(optimizer): rename api.model package to api.spec (PR #527 re…
mkuchenbecker 2c26872
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker b849b7d
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 231efde
refactor(optimizer): update controller imports for api.model -> api.s…
mkuchenbecker b31decf
refactor(optimizer): move Dto suffix from api/spec to model
mkuchenbecker caf3294
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker c6a64bf
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 91e89ef
refactor(optimizer): update controller + service refs after Dto suffi…
mkuchenbecker 4e86569
feat(optimizer): propagate jobId through model + api conversions
mkuchenbecker cc8aa80
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker efcceea
feat(optimizer): propagate jobId through model ↔ db conversions
mkuchenbecker f85edd5
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker c00f201
chore(optimizer): rename OPTIMIZER_DB_USERNAME → OPTIMIZER_DB_USER
mkuchenbecker 1fe71f0
refactor(optimizer): rename CompleteOperationRequest → UpdateOperatio…
mkuchenbecker fb5e726
Merge branch 'mkuchenb/optimizer-0' into mkuchenb/optimizer-1
mkuchenbecker ad0c0f1
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 947bedf
refactor(optimizer): rename completeOperation → updateOperation
mkuchenbecker b96c388
Merge remote-tracking branch 'linkedin/main' into mkuchenb/optimizer-1
mkuchenbecker d65b511
refactor(optimizer-repo): unify find/updateBatch with Optional params
mkuchenbecker 78de390
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 49e43bc
refactor(optimizer-service): use Optional repo API + configurable limit
mkuchenbecker b69e09a
test(optimizer-repo): truncate Instant to micros for CI precision
mkuchenbecker a028a98
Merge branch 'mkuchenb/optimizer-1' into mkuchenb/optimizer-2
mkuchenbecker 6eb6a1e
Merge remote-tracking branch 'linkedin/main' into mkuchenb/optimizer-2
mkuchenbecker a89e037
feat(optimizer): require limit on list-API endpoints
mkuchenbecker 1e361af
feat(optimizer): basic error-code handling across controllers
mkuchenbecker a37169d
refactor(optimizer): simplify error handling per PR review
mkuchenbecker 6416c9d
refactor(optimizer): drop GlobalExceptionHandler + ApiError; use Spri…
mkuchenbecker bbef386
refactor(optimizer): revert UpdateOperationRequest doc edits
mkuchenbecker 02bbc5c
(wip) feat(optimizer): basic error-code handling across controllers (…
mkuchenbecker 144da72
Merge remote-tracking branch 'linkedin/mkuchenb/optimizer-2' into mku…
mkuchenbecker 6ef7964
docs(optimizer): add @ApiResponses to controllers for OpenAPI spec
mkuchenbecker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
.../main/java/com/linkedin/openhouse/optimizer/api/controller/TableOperationsController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| package com.linkedin.openhouse.optimizer.api.controller; | ||
|
|
||
| import com.linkedin.openhouse.optimizer.api.spec.OperationStatus; | ||
| import com.linkedin.openhouse.optimizer.api.spec.OperationType; | ||
| import com.linkedin.openhouse.optimizer.api.spec.TableOperations; | ||
| import com.linkedin.openhouse.optimizer.api.spec.TableOperationsHistory; | ||
| import com.linkedin.openhouse.optimizer.api.spec.UpdateOperationRequest; | ||
| import com.linkedin.openhouse.optimizer.service.OptimizerDataService; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.util.StringUtils; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| /** REST controller for {@code table_operations}. */ | ||
| @RestController | ||
| @RequestMapping("/v1/optimizer/operations") | ||
| @RequiredArgsConstructor | ||
| public class TableOperationsController { | ||
|
|
||
| private final OptimizerDataService service; | ||
|
|
||
| /** | ||
| * Report an update to an operation. {@code id} comes from the URL; the body's {@code operationId} | ||
| * must match (the controller rejects mismatched requests with 400). The backend looks up the | ||
| * operation row, writes a history entry with the operation's table metadata, and returns 201 | ||
| * Created with the history row, or 404 if the operation does not exist. | ||
| */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "201", description = "Operation UPDATE: CREATED"), | ||
| @ApiResponse(responseCode = "400", description = "Operation UPDATE: BAD_REQUEST"), | ||
| @ApiResponse(responseCode = "404", description = "Operation UPDATE: NOT_FOUND") | ||
| }) | ||
| @PostMapping("/{id}/update") | ||
| public ResponseEntity<TableOperationsHistory> updateOperation( | ||
| @PathVariable String id, @RequestBody UpdateOperationRequest request) { | ||
| if (!StringUtils.hasText(request.getOperationId())) { | ||
| throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "operationId is required"); | ||
| } | ||
| if (!Objects.equals(id, request.getOperationId())) { | ||
| throw new ResponseStatusException( | ||
| HttpStatus.BAD_REQUEST, | ||
| String.format( | ||
| "operationId in body (%s) does not match path id (%s)", | ||
| request.getOperationId(), id)); | ||
| } | ||
| if (request.getStatus() == null) { | ||
| throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "status is required"); | ||
| } | ||
| return service | ||
| .updateOperation(id, request.getStatus().toModel()) | ||
| .map( | ||
| history -> | ||
| ResponseEntity.status(HttpStatus.CREATED) | ||
| .body(TableOperationsHistory.fromModel(history))) | ||
| .orElseThrow( | ||
| () -> | ||
| new ResponseStatusException( | ||
| HttpStatus.NOT_FOUND, String.format("no operation with id %s", id))); | ||
| } | ||
|
|
||
| /** Fetch a single operation row by its ID, regardless of status. Returns 404 if not found. */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "200", description = "Operation GET: OK"), | ||
| @ApiResponse(responseCode = "404", description = "Operation GET: NOT_FOUND") | ||
| }) | ||
| @GetMapping("/{id}") | ||
| public ResponseEntity<TableOperations> getTableOperation(@PathVariable String id) { | ||
| return service | ||
| .getTableOperation(id) | ||
| .map(TableOperations::fromModel) | ||
| .map(ResponseEntity::ok) | ||
| .orElseThrow( | ||
| () -> | ||
| new ResponseStatusException( | ||
| HttpStatus.NOT_FOUND, String.format("no operation with id %s", id))); | ||
| } | ||
|
|
||
| /** | ||
| * List operations matching the given filters, capped at {@code limit} rows. Every filter is | ||
| * optional; {@code limit} is required so callers always state how much they want back. | ||
| */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "200", description = "Operation SEARCH: OK"), | ||
| @ApiResponse(responseCode = "400", description = "Operation SEARCH: BAD_REQUEST") | ||
| }) | ||
| @GetMapping | ||
| public ResponseEntity<List<TableOperations>> listTableOperations( | ||
| @RequestParam(required = false) OperationType operationType, | ||
| @RequestParam(required = false) OperationStatus status, | ||
| @RequestParam(required = false) String databaseName, | ||
| @RequestParam(required = false) String tableName, | ||
| @RequestParam(required = false) String tableUuid, | ||
| @RequestParam int limit) { | ||
| List<TableOperations> result = | ||
| service | ||
| .listTableOperations( | ||
| Optional.ofNullable(operationType).map(OperationType::toModel), | ||
| Optional.ofNullable(status).map(OperationStatus::toModel), | ||
| Optional.ofNullable(databaseName), | ||
| Optional.ofNullable(tableName), | ||
| Optional.ofNullable(tableUuid), | ||
| limit) | ||
| .stream() | ||
| .map(TableOperations::fromModel) | ||
| .collect(Collectors.toList()); | ||
| return ResponseEntity.ok(result); | ||
| } | ||
| } | ||
58 changes: 58 additions & 0 deletions
58
...ava/com/linkedin/openhouse/optimizer/api/controller/TableOperationsHistoryController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.linkedin.openhouse.optimizer.api.controller; | ||
|
|
||
| import com.linkedin.openhouse.optimizer.api.spec.TableOperationsHistory; | ||
| import com.linkedin.openhouse.optimizer.service.OptimizerDataService; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| /** REST controller for {@code table_operations_history}. */ | ||
| @RestController | ||
| @RequestMapping("/v1/optimizer/operations-history") | ||
| @RequiredArgsConstructor | ||
| public class TableOperationsHistoryController { | ||
|
|
||
| private final OptimizerDataService service; | ||
|
|
||
| /** Append a completed-job result. Called by the SparkJob after each run (success or failure). */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "201", description = "OperationsHistory CREATE: CREATED") | ||
| }) | ||
| @PostMapping | ||
| public ResponseEntity<TableOperationsHistory> appendHistory( | ||
| @RequestBody TableOperationsHistory dto) { | ||
| return ResponseEntity.status(HttpStatus.CREATED) | ||
| .body(TableOperationsHistory.fromModel(service.appendHistory(dto.toModel()))); | ||
| } | ||
|
|
||
| /** | ||
| * Return the most recent history for a table, newest first, capped at {@code limit} rows. {@code | ||
| * limit} is required. | ||
| */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "200", description = "OperationsHistory GET: OK"), | ||
| @ApiResponse(responseCode = "400", description = "OperationsHistory GET: BAD_REQUEST") | ||
| }) | ||
| @GetMapping("/{tableUuid}") | ||
| public ResponseEntity<List<TableOperationsHistory>> getHistory( | ||
| @PathVariable String tableUuid, @RequestParam int limit) { | ||
| List<TableOperationsHistory> result = | ||
| service.getHistory(tableUuid, limit).stream() | ||
| .map(TableOperationsHistory::fromModel) | ||
| .collect(Collectors.toList()); | ||
| return ResponseEntity.ok(result); | ||
| } | ||
| } |
111 changes: 111 additions & 0 deletions
111
...r/src/main/java/com/linkedin/openhouse/optimizer/api/controller/TableStatsController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| package com.linkedin.openhouse.optimizer.api.controller; | ||
|
|
||
| import com.linkedin.openhouse.optimizer.api.spec.TableStats; | ||
| import com.linkedin.openhouse.optimizer.api.spec.TableStatsHistory; | ||
| import com.linkedin.openhouse.optimizer.api.spec.UpsertTableStatsRequest; | ||
| import com.linkedin.openhouse.optimizer.service.OptimizerDataService; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import java.time.Instant; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PutMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| /** REST controller for managing per-table stats in the optimizer DB. */ | ||
| @RestController | ||
| @RequestMapping("/v1/optimizer/stats") | ||
| @RequiredArgsConstructor | ||
| public class TableStatsController { | ||
|
|
||
| private final OptimizerDataService service; | ||
|
|
||
| /** | ||
| * Create or overwrite the stats row for {@code tableUuid}. Called by the Tables Service on every | ||
| * Iceberg commit. Idempotent. | ||
| */ | ||
| @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Stats PUT: OK")}) | ||
| @PutMapping("/{tableUuid}") | ||
| public ResponseEntity<TableStats> upsertTableStats( | ||
| @PathVariable String tableUuid, @RequestBody UpsertTableStatsRequest request) { | ||
|
mkuchenbecker marked this conversation as resolved.
|
||
| return ResponseEntity.ok( | ||
| TableStats.fromModel(service.upsertTableStats(request.toModel(tableUuid)))); | ||
| } | ||
|
|
||
| /** Fetch the stats row for {@code tableUuid}. Returns 404 if no stats have been written yet. */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "200", description = "Stats GET: OK"), | ||
| @ApiResponse(responseCode = "404", description = "Stats GET: NOT_FOUND") | ||
| }) | ||
| @GetMapping("/{tableUuid}") | ||
| public ResponseEntity<TableStats> getTableStats(@PathVariable String tableUuid) { | ||
| return service | ||
| .getTableStats(tableUuid) | ||
| .map(TableStats::fromModel) | ||
| .map(ResponseEntity::ok) | ||
| .orElseThrow( | ||
| () -> | ||
| new ResponseStatusException( | ||
| HttpStatus.NOT_FOUND, String.format("no stats for tableUuid %s", tableUuid))); | ||
| } | ||
|
|
||
| /** | ||
| * List stats rows matching the given filters, capped at {@code limit} rows. Every filter is | ||
| * optional; {@code limit} is required so callers always state how much they want back. | ||
| */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "200", description = "Stats SEARCH: OK"), | ||
| @ApiResponse(responseCode = "400", description = "Stats SEARCH: BAD_REQUEST") | ||
| }) | ||
| @GetMapping | ||
| public ResponseEntity<List<TableStats>> listTableStats( | ||
| @RequestParam(required = false) String databaseName, | ||
| @RequestParam(required = false) String tableName, | ||
| @RequestParam(required = false) String tableUuid, | ||
| @RequestParam int limit) { | ||
| List<TableStats> result = | ||
| service | ||
| .listTableStats( | ||
| Optional.ofNullable(databaseName), | ||
| Optional.ofNullable(tableName), | ||
| Optional.ofNullable(tableUuid), | ||
| limit) | ||
| .stream() | ||
| .map(TableStats::fromModel) | ||
| .collect(Collectors.toList()); | ||
| return ResponseEntity.ok(result); | ||
| } | ||
|
|
||
| /** | ||
| * Return per-commit stats history for {@code tableUuid}, newest first, capped at {@code limit} | ||
| * rows. Optional {@code since} filter (inclusive). {@code limit} is required. | ||
| */ | ||
| @ApiResponses( | ||
| value = { | ||
| @ApiResponse(responseCode = "200", description = "StatsHistory GET: OK"), | ||
| @ApiResponse(responseCode = "400", description = "StatsHistory GET: BAD_REQUEST") | ||
| }) | ||
| @GetMapping("/{tableUuid}/history") | ||
| public ResponseEntity<List<TableStatsHistory>> getStatsHistory( | ||
| @PathVariable String tableUuid, | ||
| @RequestParam(required = false) Instant since, | ||
| @RequestParam int limit) { | ||
| List<TableStatsHistory> result = | ||
| service.getStatsHistory(tableUuid, Optional.ofNullable(since), limit).stream() | ||
| .map(TableStatsHistory::fromModel) | ||
| .collect(Collectors.toList()); | ||
| return ResponseEntity.ok(result); | ||
| } | ||
| } | ||
94 changes: 94 additions & 0 deletions
94
...ptimizer/src/main/java/com/linkedin/openhouse/optimizer/service/OptimizerDataService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| package com.linkedin.openhouse.optimizer.service; | ||
|
|
||
| import com.linkedin.openhouse.optimizer.model.HistoryStatusDto; | ||
| import com.linkedin.openhouse.optimizer.model.OperationStatusDto; | ||
| import com.linkedin.openhouse.optimizer.model.OperationTypeDto; | ||
| import com.linkedin.openhouse.optimizer.model.TableOperationDto; | ||
| import com.linkedin.openhouse.optimizer.model.TableOperationsHistoryDto; | ||
| import com.linkedin.openhouse.optimizer.model.TableStatsDto; | ||
| import com.linkedin.openhouse.optimizer.model.TableStatsHistoryDto; | ||
| import java.time.Instant; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| /** | ||
| * Service interface for optimizer data operations. | ||
| * | ||
| * <p>The service is the boundary between the wire-API surface and the database. Inputs and outputs | ||
| * are <em>internal-model</em> types only — callers (controllers, future CLI, in-process consumers) | ||
| * convert at their own edge. No api/-package types appear here. | ||
| */ | ||
| public interface OptimizerDataService { | ||
|
|
||
| // --- TableOperations --- | ||
|
|
||
| /** | ||
| * List operations matching the given filters, capped at {@code limit} rows. Every filter | ||
| * parameter is optional — pass {@link Optional#empty()} to skip that filter. | ||
| */ | ||
| List<TableOperationDto> listTableOperations( | ||
| Optional<OperationTypeDto> operationType, | ||
| Optional<OperationStatusDto> status, | ||
| Optional<String> databaseName, | ||
| Optional<String> tableName, | ||
| Optional<String> tableUuid, | ||
| int limit); | ||
|
|
||
| /** | ||
| * Update an operation by writing a history entry. Looks up the operation row by {@code | ||
| * operationId}, copies its table metadata into a new history row with the supplied terminal | ||
| * {@code status}, and saves it. Returns the history record, or empty if the operation does not | ||
| * exist. | ||
| */ | ||
| Optional<TableOperationsHistoryDto> updateOperation(String operationId, HistoryStatusDto status); | ||
|
|
||
| /** | ||
| * Return the operation row for {@code id} regardless of status, or empty if it does not exist. | ||
| * Used to poll a specific operation (e.g. waiting for SUCCESS after a Spark job completes). | ||
| */ | ||
| Optional<TableOperationDto> getTableOperation(String id); | ||
|
|
||
| // --- TableStatsDto --- | ||
|
|
||
| /** | ||
| * Create or update the stats row for {@code stats.getTableUuid()}. Fully idempotent: the same | ||
| * call overwrites the previous snapshot with the latest commit values. The service stamps {@link | ||
| * TableStatsDto#getUpdatedAt()} server-side and returns the resulting {@link TableStatsDto}. | ||
| */ | ||
| TableStatsDto upsertTableStats(TableStatsDto stats); | ||
|
|
||
| /** Return the stats row for {@code tableUuid}, or empty if none exists. */ | ||
| Optional<TableStatsDto> getTableStats(String tableUuid); | ||
|
|
||
| /** | ||
| * List stats rows matching the given filters, capped at {@code limit} rows. Every filter | ||
| * parameter is optional — pass {@link Optional#empty()} to skip that filter. | ||
| */ | ||
| List<TableStatsDto> listTableStats( | ||
| Optional<String> databaseName, | ||
| Optional<String> tableName, | ||
| Optional<String> tableUuid, | ||
| int limit); | ||
|
|
||
| /** | ||
| * Return per-commit stats history for {@code tableUuid}, newest first. | ||
| * | ||
| * @param tableUuid the stable table UUID | ||
| * @param since if present, only return rows recorded at or after this instant | ||
| * @param limit maximum number of rows to return | ||
| */ | ||
| List<TableStatsHistoryDto> getStatsHistory(String tableUuid, Optional<Instant> since, int limit); | ||
|
|
||
| // --- TableOperationsHistoryDto --- | ||
|
|
||
| /** Append a completed-job result record. */ | ||
| TableOperationsHistoryDto appendHistory(TableOperationsHistoryDto history); | ||
|
|
||
| /** | ||
| * Return the most recent history rows for a table UUID, newest first. | ||
| * | ||
| * @param tableUuid the stable table UUID | ||
| * @param limit maximum number of rows to return | ||
| */ | ||
| List<TableOperationsHistoryDto> getHistory(String tableUuid, int limit); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.