fix(elasticsearch): preserve legacy type on delete
This commit is contained in:
parent
288cc0687a
commit
78a61334a5
|
|
@ -594,16 +594,21 @@ function documentRoutingFromDocument(doc: JsonRecord | undefined): string | unde
|
|||
return normalizeDocumentStoreRouting(doc?._routing);
|
||||
}
|
||||
|
||||
function documentTypeFromDocument(doc: JsonRecord | undefined): string | undefined {
|
||||
const documentType = doc?._type;
|
||||
return typeof documentType === "string" && documentType.trim() ? documentType.trim() : undefined;
|
||||
}
|
||||
|
||||
function documentRoutingFromGridRow(row: MongoInputValue[] | undefined, columns: string[]): string | undefined {
|
||||
const routingColIdx = columns.indexOf("_routing");
|
||||
return routingColIdx >= 0 ? normalizeDocumentStoreRouting(row?.[routingColIdx]) : undefined;
|
||||
}
|
||||
|
||||
function documentStoreWriteApis() {
|
||||
function documentStoreWriteApis(documentType?: string) {
|
||||
return {
|
||||
insert: (docJson: string, routing?: string) => api.documentInsertDocument(props.connectionId, props.database, props.collection, docJson, routing),
|
||||
update: (id: string, docJson: string, routing?: string) => api.documentUpdateDocument(props.connectionId, props.database, props.collection, id, docJson, routing),
|
||||
delete: (id: string, routing?: string) => api.documentDeleteDocument(props.connectionId, props.database, props.collection, id, routing),
|
||||
delete: (id: string, routing?: string) => api.documentDeleteDocument(props.connectionId, props.database, props.collection, id, routing, documentType),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -648,8 +653,9 @@ async function gridSave(changes: DocumentGridChanges) {
|
|||
if (id == null) continue;
|
||||
const document = documents.value[rowIdx];
|
||||
const routing = isEs ? documentRoutingFromDocument(document) : undefined;
|
||||
const documentType = isEs ? documentTypeFromDocument(document) : undefined;
|
||||
const documentId = isEs ? id : (document?._id ?? id);
|
||||
await api.documentDeleteDocument(props.connectionId, props.database, props.collection, isEs ? String(documentId) : serializeMongoDocumentId(documentId), routing);
|
||||
await api.documentDeleteDocument(props.connectionId, props.database, props.collection, isEs ? String(documentId) : serializeMongoDocumentId(documentId), routing, documentType);
|
||||
}
|
||||
|
||||
for (const newRow of changes.newRows) {
|
||||
|
|
@ -1336,10 +1342,10 @@ async function saveDoc() {
|
|||
const doc = buildDocumentFromEditor();
|
||||
if (!doc) return;
|
||||
|
||||
const apis = documentStoreWriteApis();
|
||||
const kind = documentStoreProvider.value.kind;
|
||||
|
||||
if (isNew.value) {
|
||||
const apis = documentStoreWriteApis();
|
||||
const explicitId = kind === "elasticsearch" ? documentIdFromGridValue(documentStoreValueForGrid(doc._id, "elasticsearch")) : null;
|
||||
await insertDocumentStoreDocumentCore({
|
||||
kind,
|
||||
|
|
@ -1362,6 +1368,7 @@ async function saveDoc() {
|
|||
return;
|
||||
}
|
||||
const currentRouting = documentRoutingFromDocument(current);
|
||||
const apis = documentStoreWriteApis(kind === "elasticsearch" ? documentTypeFromDocument(current) : undefined);
|
||||
const write = resolveWriteIdentityFromEditor(doc, currentId, currentRouting);
|
||||
if (!write) {
|
||||
error.value = t("mongo.jsonIdRequired");
|
||||
|
|
@ -1400,7 +1407,7 @@ async function applyDeleteDoc(idx: number) {
|
|||
if (!id) return;
|
||||
error.value = "";
|
||||
try {
|
||||
await api.documentDeleteDocument(props.connectionId, props.database, props.collection, serializeDocumentStoreId(id, documentStoreProvider.value.kind), documentRoutingFromDocument(doc));
|
||||
await api.documentDeleteDocument(props.connectionId, props.database, props.collection, serializeDocumentStoreId(id, documentStoreProvider.value.kind), documentRoutingFromDocument(doc), documentStoreProvider.value.kind === "elasticsearch" ? documentTypeFromDocument(doc) : undefined);
|
||||
if (selectedIdx.value === idx) {
|
||||
selectedIdx.value = null;
|
||||
editJson.value = "";
|
||||
|
|
|
|||
|
|
@ -3130,13 +3130,14 @@ export async function mongoDeleteDocument(connectionId: string, database: string
|
|||
return documentDeleteDocument(connectionId, database, collection, id, routing);
|
||||
}
|
||||
|
||||
export async function documentDeleteDocument(connectionId: string, database: string, collection: string, id: string, routing?: string): Promise<number> {
|
||||
export async function documentDeleteDocument(connectionId: string, database: string, collection: string, id: string, routing?: string, documentType?: string): Promise<number> {
|
||||
return post("/api/document-store/delete-document", {
|
||||
connectionId,
|
||||
database,
|
||||
collection,
|
||||
id,
|
||||
routing,
|
||||
documentType,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3072,13 +3072,14 @@ export async function mongoDeleteDocument(connectionId: string, database: string
|
|||
return documentDeleteDocument(connectionId, database, collection, id, routing);
|
||||
}
|
||||
|
||||
export async function documentDeleteDocument(connectionId: string, database: string, collection: string, id: string, routing?: string): Promise<number> {
|
||||
export async function documentDeleteDocument(connectionId: string, database: string, collection: string, id: string, routing?: string, documentType?: string): Promise<number> {
|
||||
return invoke("document_delete_document", {
|
||||
connectionId,
|
||||
database,
|
||||
collection,
|
||||
id,
|
||||
routing,
|
||||
documentType,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,9 +88,12 @@ pub async fn delete_document(
|
|||
client: &EasysearchClient,
|
||||
index: &str,
|
||||
id: &str,
|
||||
document_type: Option<&str>,
|
||||
routing: Option<&str>,
|
||||
) -> Result<u64, String> {
|
||||
elasticsearch_driver::delete_document(&client.inner, index, id, routing).await.map_err(easysearch_error)
|
||||
elasticsearch_driver::delete_document(&client.inner, index, id, document_type, routing)
|
||||
.await
|
||||
.map_err(easysearch_error)
|
||||
}
|
||||
|
||||
pub async fn execute_rest_query(client: &EasysearchClient, input: &str) -> Result<QueryResult, String> {
|
||||
|
|
|
|||
|
|
@ -319,17 +319,7 @@ fn elasticsearch_query_value(value: &str) -> String {
|
|||
utf8_percent_encode(value, ELASTICSEARCH_QUERY_VALUE_ENCODE_SET).to_string()
|
||||
}
|
||||
|
||||
fn elasticsearch_document_path(index: &str, id: &str, routing: Option<&str>) -> String {
|
||||
let base = format!("/{}/_doc/{}", elasticsearch_path_segment(index), elasticsearch_path_segment(id));
|
||||
elasticsearch_path_with_routing_refresh(base, routing)
|
||||
}
|
||||
|
||||
fn elasticsearch_update_document_path(
|
||||
index: &str,
|
||||
id: &str,
|
||||
document_type: Option<&str>,
|
||||
routing: Option<&str>,
|
||||
) -> String {
|
||||
fn elasticsearch_document_path(index: &str, id: &str, document_type: Option<&str>, routing: Option<&str>) -> String {
|
||||
let document_type = document_type.map(str::trim).filter(|value| !value.is_empty()).unwrap_or("_doc");
|
||||
let base = format!(
|
||||
"/{}/{}/{}",
|
||||
|
|
@ -926,7 +916,7 @@ pub async fn update_document(
|
|||
) -> Result<u64, String> {
|
||||
let (doc, routing, document_type) = elasticsearch_update_document_body_and_metadata(doc_json, routing)?;
|
||||
|
||||
let path = elasticsearch_update_document_path(index, id, document_type.as_deref(), routing.as_deref());
|
||||
let path = elasticsearch_document_path(index, id, document_type.as_deref(), routing.as_deref());
|
||||
let resp = client.put(&path).json(&doc).send().await.map_err(|e| format!("Elasticsearch request failed: {e}"))?;
|
||||
|
||||
if !client.response_status(&resp).is_success() {
|
||||
|
|
@ -981,8 +971,14 @@ fn elasticsearch_routing_from_value(value: &serde_json::Value) -> Option<String>
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn delete_document(client: &EsClient, index: &str, id: &str, routing: Option<&str>) -> Result<u64, String> {
|
||||
let path = elasticsearch_document_path(index, id, routing);
|
||||
pub async fn delete_document(
|
||||
client: &EsClient,
|
||||
index: &str,
|
||||
id: &str,
|
||||
document_type: Option<&str>,
|
||||
routing: Option<&str>,
|
||||
) -> Result<u64, String> {
|
||||
let path = elasticsearch_document_path(index, id, document_type, routing);
|
||||
let resp = client.delete(&path).send().await.map_err(|e| format!("Elasticsearch request failed: {e}"))?;
|
||||
|
||||
if !client.response_status(&resp).is_success() {
|
||||
|
|
@ -2334,25 +2330,16 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn builds_elasticsearch_document_path_with_routing() {
|
||||
fn builds_elasticsearch_document_path_with_type_and_routing() {
|
||||
assert_eq!(
|
||||
super::elasticsearch_document_path("orders/2026", "a%b/c", Some("tenant/a&b")),
|
||||
"/orders%2F2026/_doc/a%25b%2Fc?routing=tenant%2Fa%26b&refresh=true"
|
||||
);
|
||||
assert_eq!(super::elasticsearch_document_path("orders", "1", None), "/orders/_doc/1?refresh=true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_legacy_elasticsearch_update_path_from_document_type() {
|
||||
assert_eq!(
|
||||
super::elasticsearch_update_document_path("orders/2026", "a%b/c", Some("legacy/order"), Some("tenant/a&b")),
|
||||
super::elasticsearch_document_path("orders/2026", "a%b/c", Some("legacy/order"), Some("tenant/a&b")),
|
||||
"/orders%2F2026/legacy%2Forder/a%25b%2Fc?routing=tenant%2Fa%26b&refresh=true"
|
||||
);
|
||||
assert_eq!(
|
||||
super::elasticsearch_update_document_path("orders", "1", Some("_doc"), None),
|
||||
super::elasticsearch_document_path("orders", "1", Some("_doc"), None),
|
||||
"/orders/_doc/1?refresh=true"
|
||||
);
|
||||
assert_eq!(super::elasticsearch_update_document_path("orders", "1", None, None), "/orders/_doc/1?refresh=true");
|
||||
assert_eq!(super::elasticsearch_document_path("orders", "1", None, None), "/orders/_doc/1?refresh=true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3335,6 +3322,26 @@ mod tests {
|
|||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_document_uses_legacy_type_path() {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let server = tokio::spawn(async move {
|
||||
let (mut socket, _) = listener.accept().await.unwrap();
|
||||
let request = read_http_request(&mut socket).await;
|
||||
assert!(request.starts_with("DELETE /orders/order/abc?routing=tenant-1&refresh=true "));
|
||||
let response =
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 2\r\nConnection: close\r\n\r\n{}";
|
||||
socket.write_all(response.as_bytes()).await.unwrap();
|
||||
});
|
||||
|
||||
let client = EsClient::new(&format!("http://{addr}"), None, None, false, Duration::from_secs(1));
|
||||
super::delete_document(&client, "orders", "abc", Some("order"), Some("tenant-1")).await.unwrap();
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_rest_search_preserves_full_json_response() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
|
@ -3437,7 +3444,7 @@ mod tests {
|
|||
let name_index = sql_result.columns.iter().position(|column| column == "name").unwrap();
|
||||
assert_eq!(sql_result.rows[0][name_index], json!("Notebook Pro"));
|
||||
|
||||
super::delete_document(&client, &index, &id, None).await.unwrap();
|
||||
super::delete_document(&client, &index, &id, None, None).await.unwrap();
|
||||
let delete = super::execute_rest_query(&client, &format!("DELETE /{index}")).await.unwrap();
|
||||
assert_eq!(delete.rows[0][0], json!(200));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -539,6 +539,18 @@ pub async fn delete_document_core(
|
|||
collection: &str,
|
||||
id: &str,
|
||||
routing: Option<&str>,
|
||||
) -> Result<u64, String> {
|
||||
delete_document_core_with_type(state, connection_id, database, collection, id, routing, None).await
|
||||
}
|
||||
|
||||
pub async fn delete_document_core_with_type(
|
||||
state: &AppState,
|
||||
connection_id: &str,
|
||||
database: &str,
|
||||
collection: &str,
|
||||
id: &str,
|
||||
routing: Option<&str>,
|
||||
document_type: Option<&str>,
|
||||
) -> Result<u64, String> {
|
||||
ensure_document_pool(state, connection_id).await?;
|
||||
let connections = state.connections.read().await;
|
||||
|
|
@ -549,12 +561,12 @@ pub async fn delete_document_core(
|
|||
drop(connections);
|
||||
// Elasticsearch requires the same custom routing value for writes
|
||||
// as was used to index the document.
|
||||
elasticsearch_driver::delete_document(&client, collection, id, routing).await
|
||||
elasticsearch_driver::delete_document(&client, collection, id, document_type, routing).await
|
||||
}
|
||||
PoolKind::Easysearch(client) => {
|
||||
let client = client.clone();
|
||||
drop(connections);
|
||||
easysearch_driver::delete_document(&client, collection, id, routing).await
|
||||
easysearch_driver::delete_document(&client, collection, id, document_type, routing).await
|
||||
}
|
||||
PoolKind::Agent(client) => {
|
||||
let mut client = client.lock().await;
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ pub struct DocumentDeleteRequest {
|
|||
pub collection: String,
|
||||
pub id: String,
|
||||
pub routing: Option<String>,
|
||||
pub document_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
@ -256,13 +257,14 @@ pub async fn delete_document(
|
|||
Json(req): Json<DocumentDeleteRequest>,
|
||||
) -> Result<Json<u64>, AppError> {
|
||||
ensure_writable(&state.app, &req.connection_id, "Delete").await?;
|
||||
let result = dbx_core::document_ops::delete_document_core(
|
||||
let result = dbx_core::document_ops::delete_document_core_with_type(
|
||||
&state.app,
|
||||
&req.connection_id,
|
||||
&req.database,
|
||||
&req.collection,
|
||||
&req.id,
|
||||
req.routing.as_deref(),
|
||||
req.document_type.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
|
|
|||
|
|
@ -147,15 +147,17 @@ pub async fn document_delete_document(
|
|||
collection: String,
|
||||
id: String,
|
||||
routing: Option<String>,
|
||||
document_type: Option<String>,
|
||||
) -> Result<u64, String> {
|
||||
ensure_connection_writable(&state, &connection_id, "Delete").await?;
|
||||
dbx_core::document_ops::delete_document_core(
|
||||
dbx_core::document_ops::delete_document_core_with_type(
|
||||
&state,
|
||||
&connection_id,
|
||||
&database,
|
||||
&collection,
|
||||
&id,
|
||||
routing.as_deref(),
|
||||
document_type.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -467,8 +467,16 @@ pub async fn mongo_delete_document(
|
|||
id: String,
|
||||
routing: Option<String>,
|
||||
) -> Result<u64, String> {
|
||||
crate::commands::document_cmd::document_delete_document(state, connection_id, database, collection, id, routing)
|
||||
.await
|
||||
crate::commands::document_cmd::document_delete_document(
|
||||
state,
|
||||
connection_id,
|
||||
database,
|
||||
collection,
|
||||
id,
|
||||
routing,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
|
|||
Loading…
Reference in New Issue