From 248d54a30f9ad29cf0b776dac2aad70d463974ee Mon Sep 17 00:00:00 2001 From: filipriec Date: Mon, 16 Jun 2025 22:51:05 +0200 Subject: [PATCH] accpeting now null in the post table data as nothing --- .../tables_data/handlers/post_table_data.rs | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/server/src/tables_data/handlers/post_table_data.rs b/server/src/tables_data/handlers/post_table_data.rs index af761b2..11c299f 100644 --- a/server/src/tables_data/handlers/post_table_data.rs +++ b/server/src/tables_data/handlers/post_table_data.rs @@ -25,12 +25,6 @@ pub async fn post_table_data( let profile_name = request.profile_name; let table_name = request.table_name; - // REMOVED: The old data conversion loop. We will process request.data directly. - // let mut data = HashMap::new(); - // for (key, value) in request.data { - // data.insert(key, value.trim().to_string()); - // } - // Lookup profile let profile = sqlx::query!( "SELECT id FROM profiles WHERE name = $1", @@ -94,22 +88,26 @@ pub async fn post_table_data( // Validate all data columns let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect(); - for key in request.data.keys() { // CHANGED: Use request.data + for key in request.data.keys() { if !system_columns_set.contains(key.as_str()) && !user_columns.contains(&&key.to_string()) { return Err(Status::invalid_argument(format!("Invalid column: {}", key))); } } - // NEW: Create a string-based map for backwards compatibility with Steel scripts. + // ======================================================================== + // FIX #1: SCRIPT VALIDATION LOOP + // This loop now correctly handles JSON `null` (which becomes `None`). + // ======================================================================== let mut string_data_for_scripts = HashMap::new(); for (key, proto_value) in &request.data { let str_val = match &proto_value.kind { Some(Kind::StringValue(s)) => s.clone(), Some(Kind::NumberValue(n)) => n.to_string(), Some(Kind::BoolValue(b)) => b.to_string(), - Some(Kind::NullValue(_)) => String::new(), - Some(Kind::StructValue(_)) | Some(Kind::ListValue(_)) | None => { + // This now correctly skips both protobuf `NULL` and JSON `null`. + Some(Kind::NullValue(_)) | None => continue, + Some(Kind::StructValue(_)) | Some(Kind::ListValue(_)) => { return Err(Status::invalid_argument(format!("Unsupported type for script validation in column '{}'", key))); } }; @@ -128,21 +126,18 @@ pub async fn post_table_data( for script_record in scripts { let target_column = script_record.target_column; - // Ensure target column exists in submitted data - let user_value = string_data_for_scripts.get(&target_column) // CHANGED: Use the new string map + let user_value = string_data_for_scripts.get(&target_column) .ok_or_else(|| Status::invalid_argument( format!("Script target column '{}' is required", target_column) ))?; - // Create execution context let context = SteelContext { current_table: table_name.clone(), profile_id, - row_data: string_data_for_scripts.clone(), // CHANGED: Use the new string map + row_data: string_data_for_scripts.clone(), db_pool: Arc::new(db_pool.clone()), }; - // ... (rest of script execution is the same) let script_result = execution::execute_script( script_record.script, "STRINGS", @@ -174,7 +169,11 @@ pub async fn post_table_data( let mut placeholders = Vec::new(); let mut param_idx = 1; - // CHANGED: This entire loop is rewritten to handle prost_types::Value + // ======================================================================== + // FIX #2: DATABASE INSERTION LOOP + // This loop now correctly handles JSON `null` (which becomes `None`) + // without crashing and correctly inserts a SQL NULL. + // ======================================================================== for (col, proto_value) in request.data { let sql_type = if system_columns_set.contains(col.as_str()) { match col.as_str() { @@ -189,41 +188,52 @@ pub async fn post_table_data( .ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))? }; - let kind = proto_value.kind.ok_or_else(|| { - Status::invalid_argument(format!("Value for column '{}' cannot be null", col)) - })?; + // Check for `None` (from JSON null) or `Some(NullValue)` first. + let kind = match &proto_value.kind { + None | Some(Kind::NullValue(_)) => { + // It's a null value. Add the correct SQL NULL type and continue. + match sql_type { + "BOOLEAN" => params.add(None::), + "TEXT" | "VARCHAR(15)" | "VARCHAR(255)" => params.add(None::), + "TIMESTAMPTZ" => params.add(None::>), + "BIGINT" => params.add(None::), + _ => return Err(Status::invalid_argument(format!("Unsupported type for null value: {}", sql_type))), + }.map_err(|e| Status::internal(format!("Failed to add null parameter for {}: {}", col, e)))?; + columns_list.push(format!("\"{}\"", col)); + placeholders.push(format!("${}", param_idx)); + param_idx += 1; + continue; // Skip to the next column in the loop + } + // If it's not null, just pass the inner `Kind` through. + Some(k) => k, + }; + + // From here, we know `kind` is not a null type. match sql_type { "TEXT" | "VARCHAR(15)" | "VARCHAR(255)" => { if let Kind::StringValue(value) = kind { - if let Some(max_len) = sql_type.strip_prefix("VARCHAR(") - .and_then(|s| s.strip_suffix(')')) - .and_then(|s| s.parse::().ok()) - { + if let Some(max_len) = sql_type.strip_prefix("VARCHAR(").and_then(|s| s.strip_suffix(')')).and_then(|s| s.parse::().ok()) { if value.len() > max_len { return Err(Status::internal(format!("Value too long for {}", col))); } } - params.add(value) - .map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?; + params.add(value).map_err(|e| Status::invalid_argument(format!("Failed to add text parameter for {}: {}", col, e)))?; } else { return Err(Status::invalid_argument(format!("Expected string for column '{}'", col))); } }, "BOOLEAN" => { if let Kind::BoolValue(val) = kind { - params.add(val) - .map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?; + params.add(val).map_err(|e| Status::invalid_argument(format!("Failed to add boolean parameter for {}: {}", col, e)))?; } else { return Err(Status::invalid_argument(format!("Expected boolean for column '{}'", col))); } }, "TIMESTAMPTZ" => { if let Kind::StringValue(value) = kind { - let dt = DateTime::parse_from_rfc3339(&value) - .map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?; - params.add(dt.with_timezone(&Utc)) - .map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?; + let dt = DateTime::parse_from_rfc3339(value).map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?; + params.add(dt.with_timezone(&Utc)).map_err(|e| Status::invalid_argument(format!("Failed to add timestamp parameter for {}: {}", col, e)))?; } else { return Err(Status::invalid_argument(format!("Expected ISO 8601 string for column '{}'", col))); } @@ -233,8 +243,7 @@ pub async fn post_table_data( if val.fract() != 0.0 { return Err(Status::invalid_argument(format!("Expected integer for column '{}', but got a float", col))); } - params.add(val as i64) - .map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?; + params.add(*val as i64).map_err(|e| Status::invalid_argument(format!("Failed to add integer parameter for {}: {}", col, e)))?; } else { return Err(Status::invalid_argument(format!("Expected number for column '{}'", col))); } @@ -261,7 +270,6 @@ pub async fn post_table_data( placeholders.join(", ") ); - // ... (rest of the function is unchanged) let result = sqlx::query_scalar_with::<_, i64, _>(&sql, params) .fetch_one(db_pool) .await;