-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Data subsetting & attribute-level column filtering #68
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,10 +48,15 @@ class MySQLConnector( | |
| } | ||
| } | ||
|
|
||
| override fun fetchData(tableName: String, limit: Int?, whereClause: String?): List<Map<String, Any?>> { | ||
| override fun fetchData(tableName: String, limit: Int?, whereClause: String?, selectedAttributes: List<String>?): List<Map<String, Any?>> { | ||
| getConnection().use { conn -> | ||
| val selectPart = if (!selectedAttributes.isNullOrEmpty()) { | ||
| selectedAttributes.joinToString(", ") { "`${it.replace("`", "``")}`" } | ||
| } else { | ||
| "*" | ||
| } | ||
| val sql = buildString { | ||
| append("SELECT * FROM `$tableName`") | ||
| append("SELECT $selectPart FROM `$tableName`") | ||
| if (whereClause != null) append(" WHERE $whereClause") | ||
|
Comment on lines
+53
to
60
|
||
| if (limit != null) append(" LIMIT $limit") | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -77,11 +77,29 @@ class DestinationSchemaService { | |||||||||||||||||||||||||||||||||||||||||||||||||
| sourceType: ConnectionType, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| destConnector: DatabaseConnector, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| destType: ConnectionType, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tableName: String | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tableName: String, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| selectedAttributes: List<String> = emptyList() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.info("Mirroring schema for table: $tableName ($sourceType -> $destType)") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val sourceColumns = sourceConnector.listColumns(tableName) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val destColumns = sourceColumns.map { col -> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val filteredColumns = if (selectedAttributes.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sourceColumns | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val sourceColumnNames = sourceColumns.map { it.name } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val sourceColumnNamesNormalized = sourceColumnNames.map { it.lowercase() }.toSet() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val normalizedSelected = selectedAttributes.map { it.lowercase() }.toSet() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val matched = sourceColumns.filter { it.name.lowercase() in normalizedSelected } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (matched.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| val unknownAttributes = selectedAttributes.filter { it.lowercase() !in sourceColumnNamesNormalized } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw IllegalArgumentException( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "No selected attributes matched source columns for table '$tableName'. " + | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "Unknown attributes: ${if (unknownAttributes.isEmpty()) selectedAttributes else unknownAttributes}. " + | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "Available columns: $sourceColumnNames" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| matched | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+85
to
+101
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| val filteredColumns = if (selectedAttributes.isEmpty()) { | |
| sourceColumns | |
| } else { | |
| val normalizedSelected = selectedAttributes.map { it.lowercase() }.toSet() | |
| sourceColumns.filter { it.name.lowercase() in normalizedSelected } | |
| } | |
| val sourceColumnNames = sourceColumns.map { it.name } | |
| val sourceColumnNamesNormalized = sourceColumnNames.map { it.lowercase() }.toSet() | |
| val normalizedSelected = selectedAttributes.map { it.lowercase() }.toSet() | |
| val filteredColumns = if (selectedAttributes.isEmpty()) { | |
| sourceColumns | |
| } else { | |
| sourceColumns.filter { it.name.lowercase() in normalizedSelected } | |
| } | |
| if (selectedAttributes.isNotEmpty() && filteredColumns.isEmpty()) { | |
| val unknownAttributes = selectedAttributes.filter { it.lowercase() !in sourceColumnNamesNormalized } | |
| throw IllegalArgumentException( | |
| "No selected attributes matched source columns for table '$tableName'. " + | |
| "Unknown attributes: ${if (unknownAttributes.isEmpty()) selectedAttributes else unknownAttributes}. " + | |
| "Available columns: $sourceColumnNames" | |
| ) | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,10 @@ class TableConfigurationService( | |
| rowLimit = request.rowLimit, | ||
| whereClause = request.whereClause | ||
| ) | ||
| config.selectedAttributes = request.selectedAttributes | ||
| ?.filter { it.isNotBlank() } | ||
| ?.takeIf { it.isNotEmpty() } | ||
| ?.joinToString(",") | ||
| return tableConfigurationRepository.save(config).toResponse() | ||
|
Comment on lines
26
to
33
|
||
| } | ||
|
|
||
|
|
@@ -51,6 +55,10 @@ class TableConfigurationService( | |
| config.mode = request.mode | ||
| config.rowLimit = request.rowLimit | ||
| config.whereClause = request.whereClause | ||
| config.selectedAttributes = request.selectedAttributes | ||
| ?.filter { it.isNotBlank() } | ||
| ?.takeIf { it.isNotEmpty() } | ||
| ?.joinToString(",") | ||
| return tableConfigurationRepository.save(config).toResponse() | ||
| } | ||
|
|
||
|
|
@@ -132,6 +140,7 @@ class TableConfigurationService( | |
| mode = mode, | ||
| rowLimit = rowLimit, | ||
| whereClause = whereClause, | ||
| selectedAttributes = selectedAttributes?.split(",")?.map { it.trim() }?.filter { it.isNotBlank() }, | ||
| createdAt = createdAt | ||
| ) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MongoDB projections that include specific fields will still return
_idby default. With the current projection document,fetchData()will likely include_ideven when it wasn't requested, which contradicts the "only requested columns" behavior used elsewhere. Consider explicitly excluding_id(set_id: 0) unless_idis present inselectedAttributes.