diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index fed19d20..a815e0e4 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -97,7 +97,7 @@ jobs: cache-provider: "github" - name: Run clippy - run: cargo clippy + run: cargo clippy --lib --all-features -- -D warnings - name: Run rustfmt run: cargo fmt -- --check diff --git a/src/ts_generator/errors.rs b/src/ts_generator/errors.rs index c1229ca8..2a373f1b 100644 --- a/src/ts_generator/errors.rs +++ b/src/ts_generator/errors.rs @@ -32,6 +32,22 @@ pub enum TsGeneratorError { TableFactorWhileProcessingTableWithJoins(String), #[error("[E014] Failed to find a table name from a FROM statement: statement: `{0}`")] UnknownErrorWhileProcessingTableWithJoins(String), + #[error("[E015] Table expressions are not supported in INSERT statements - query: `{0}`")] + TableExpressionInInsertStatement(String), + #[error("[E016] Column '{column}' not found in table '{table}'. Available columns: {available_columns}")] + ColumnNotFoundInTable { + column: String, + table: String, + available_columns: String, + }, + #[error("[E017] Failed to process INSERT statement: {reason}. Query: `{query}`")] + InsertStatementProcessingFailed { reason: String, query: String }, + #[error("[E018] Failed to process UPDATE statement: {reason}. Query: `{query}`")] + UpdateStatementProcessingFailed { reason: String, query: String }, + #[error("[E019] Table '{table}' not found in database schema. Check that the table exists and is accessible.")] + TableNotFoundInSchema { table: String }, + #[error("[E020] Failed to infer table name while processing WHERE clause. Query: `{query}`")] + TableNameInferenceFailedInWhere { query: String }, #[error("Unknown error: `{0}`")] Unknown(String), } diff --git a/src/ts_generator/generator.rs b/src/ts_generator/generator.rs index c98ff892..e8265e36 100644 --- a/src/ts_generator/generator.rs +++ b/src/ts_generator/generator.rs @@ -77,8 +77,8 @@ pub fn write_single_ts_file(sqls_to_write: String) -> Result<()> { ))?; let parent_output_path: Option<&Path> = output.parent(); - if parent_output_path.is_some() { - fs::create_dir_all(parent_output_path.unwrap())?; + if let Some(parent_output_path) = parent_output_path { + fs::create_dir_all(parent_output_path)?; } let mut file_to_write = OpenOptions::new() diff --git a/src/ts_generator/sql_parser/expressions/translate_expr.rs b/src/ts_generator/sql_parser/expressions/translate_expr.rs index 9233c74b..7351fe1d 100644 --- a/src/ts_generator/sql_parser/expressions/translate_expr.rs +++ b/src/ts_generator/sql_parser/expressions/translate_expr.rs @@ -124,7 +124,7 @@ pub async fn get_sql_query_param( single_table_name: &Option<&str>, table_with_joins: &Option>, db_conn: &DBConn, -) -> Option<(TsFieldType, bool, Option)> { +) -> Result)>, TsGeneratorError> { let table_name: Option; if table_with_joins.is_some() { @@ -132,7 +132,9 @@ pub async fn get_sql_query_param( } else if single_table_name.is_some() { table_name = single_table_name.map(|x| x.to_string()); } else { - panic!("failed to find an appropriate table name while processing WHERE statement") + return Err(TsGeneratorError::TableNameInferenceFailedInWhere { + query: left.to_string(), + }); } let column_name = translate_column_name_expr(left); @@ -149,15 +151,27 @@ pub async fn get_sql_query_param( .await .fetch_table(&table_names, db_conn) .await - .unwrap_or_else(|| panic!("Failed to fetch columns for table {table_name}")); + .ok_or_else(|| TsGeneratorError::TableNotFoundInSchema { + table: table_name.clone(), + })?; // get column and return TsFieldType - let column = columns - .get(column_name.as_str()) - .unwrap_or_else(|| panic!("Failed to find the column from the table schema of {table_name}")); - Some((column.field_type.to_owned(), column.is_nullable, Some(expr_placeholder))) + let column = columns.get(column_name.as_str()).ok_or_else(|| { + let available_columns = columns.keys().map(|k| k.as_str()).collect::>().join(", "); + TsGeneratorError::ColumnNotFoundInTable { + column: column_name.clone(), + table: table_name.clone(), + available_columns, + } + })?; + + Ok(Some(( + column.field_type.to_owned(), + column.is_nullable, + Some(expr_placeholder), + ))) } - _ => None, + _ => Ok(None), } } @@ -298,7 +312,7 @@ pub async fn translate_expr( // OPERATORS START // ///////////////////// Expr::BinaryOp { left, op: _, right } => { - let param = get_sql_query_param(left, right, single_table_name, table_with_joins, db_conn).await; + let param = get_sql_query_param(left, right, single_table_name, table_with_joins, db_conn).await?; if let Some((value, is_nullable, index)) = param { let _ = ts_query.insert_param(&value, &is_nullable, &index); Ok(()) @@ -341,7 +355,7 @@ pub async fn translate_expr( table_with_joins, db_conn, ) - .await; + .await?; if let Some((value, is_nullable, index)) = result { let array_item = TsFieldType::Array(Box::new(value)); @@ -369,8 +383,8 @@ pub async fn translate_expr( low, high, } => { - let low = get_sql_query_param(expr, low, single_table_name, table_with_joins, db_conn).await; - let high = get_sql_query_param(expr, high, single_table_name, table_with_joins, db_conn).await; + let low = get_sql_query_param(expr, low, single_table_name, table_with_joins, db_conn).await?; + let high = get_sql_query_param(expr, high, single_table_name, table_with_joins, db_conn).await?; if let Some((value, is_nullable, placeholder)) = low { ts_query.insert_param(&value, &is_nullable, &placeholder)?; } @@ -731,16 +745,35 @@ pub async fn translate_assignment( let value = get_expr_placeholder(&assignment.value); if value.is_some() { - let table_details = &DB_SCHEMA + let table_details = DB_SCHEMA .lock() .await .fetch_table(&vec![table_name], db_conn) .await - .unwrap(); - let column_name = translate_column_name_assignment(assignment).unwrap(); - let field = table_details - .get(&column_name) - .unwrap_or_else(|| panic!("Failed to find the column detail for {column_name}")); + .ok_or_else(|| TsGeneratorError::TableNotFoundInSchema { + table: table_name.to_string(), + })?; + + let column_name = translate_column_name_assignment(assignment).ok_or_else(|| { + TsGeneratorError::UpdateStatementProcessingFailed { + reason: "Failed to extract column name from assignment".to_string(), + query: format!("UPDATE {} SET {} = ...", table_name, assignment), + } + })?; + + let field = table_details.get(&column_name).ok_or_else(|| { + let available_columns = table_details + .keys() + .map(|k| k.as_str()) + .collect::>() + .join(", "); + TsGeneratorError::ColumnNotFoundInTable { + column: column_name.clone(), + table: table_name.to_string(), + available_columns, + } + })?; + let _ = ts_query.insert_param(&field.field_type, &field.is_nullable, &value); } Ok(()) diff --git a/src/ts_generator/sql_parser/translate_insert.rs b/src/ts_generator/sql_parser/translate_insert.rs index c11918ca..7651bb3b 100644 --- a/src/ts_generator/sql_parser/translate_insert.rs +++ b/src/ts_generator/sql_parser/translate_insert.rs @@ -41,24 +41,32 @@ pub async fn translate_insert( if placeholder.is_some() { let match_col = &columns .get(column) - .unwrap_or_else(|| { - panic!( - r#" -Failed to process values of insert statement as column names are not provided or incorrectly specified - -Try specifying column names -``` -INSERT INTO table_name (column1, column2, column3, ...) -VALUES (value1, value2, value3, ...); -``` - "# - ) - }) + .ok_or_else(|| { + TsGeneratorError::InsertStatementProcessingFailed { + reason: format!( + "Column at position {} is not provided in the column list. Expected {} columns but found {} values.", + column, columns.len(), values.len() + ), + query: format!( + "INSERT INTO {} VALUES (...). Try specifying column names explicitly: INSERT INTO {} (column1, column2, ...) VALUES (...)", + table_name, table_name + ), + } + })? .value; - let field = table_details - .get(match_col.as_str()) - .unwrap_or_else(|| panic!("Column {match_col} is not found while processing insert params")); + let field = table_details.get(match_col.as_str()).ok_or_else(|| { + let available_columns = table_details + .keys() + .map(|k| k.as_str()) + .collect::>() + .join(", "); + TsGeneratorError::ColumnNotFoundInTable { + column: match_col.clone(), + table: table_name.to_string(), + available_columns, + } + })?; if value.to_string() == "?" { // If the placeholder is `'?'`, we can process it using insert_value_params and generate nested params type @@ -104,7 +112,12 @@ VALUES (value1, value2, value3, ...); SetExpr::Update(update) => translate_stmt(ts_query, &update, None, conn).await?, SetExpr::Delete(delete) => translate_stmt(ts_query, &delete, None, conn).await?, SetExpr::Merge(merge) => translate_stmt(ts_query, &merge, None, conn).await?, - SetExpr::Table(_) => unimplemented!("Table expressions are not supported in INSERT statements"), + SetExpr::Table(table) => { + return Err(TsGeneratorError::TableExpressionInInsertStatement(format!( + "INSERT INTO {} ... FROM {}", + table_name, table + ))); + } } Ok(()) diff --git a/src/ts_generator/sql_parser/translate_query.rs b/src/ts_generator/sql_parser/translate_query.rs index 394bd70c..8abc76d0 100644 --- a/src/ts_generator/sql_parser/translate_query.rs +++ b/src/ts_generator/sql_parser/translate_query.rs @@ -123,8 +123,12 @@ pub async fn translate_select( let mut table_name: Option<&str> = None; if full_table_with_joins.is_some() && !full_table_with_joins.as_ref().unwrap().is_empty() { table_name_owned = Some( - translate_table_with_joins(full_table_with_joins, &select_item) - .unwrap_or_else(|_| panic!("{}", format!("Default FROM table is not found from the query {select}"))), + translate_table_with_joins(full_table_with_joins, &select_item).map_err(|_| { + TsGeneratorError::UnknownErrorWhileProcessingTableWithJoins(format!( + "Default FROM table is not found from the query: {}. Ensure your query has a valid FROM clause.", + select + )) + })?, ); table_name = table_name_owned.as_deref(); } diff --git a/src/ts_generator/types/ts_query.rs b/src/ts_generator/types/ts_query.rs index 228f7cf0..9fb93bef 100644 --- a/src/ts_generator/types/ts_query.rs +++ b/src/ts_generator/types/ts_query.rs @@ -363,7 +363,7 @@ impl TsQuery { .and_then(|m| m.as_str().parse::().ok()) .ok_or(TsGeneratorError::UnknownPlaceholder(format!( "{placeholder} is not a valid placeholder parameter in PostgreSQL" - )))? as i32 + )))? } else { // No pattern matches the provided placeholder, simply exit out of the function return Ok(());