Skip to content

Commit 7a9c796

Browse files
authored
handle nullability (#195)
* nullable field * feature: select nullability * chore: wip * wip: handing nullability in insert and update * nullability handling mysql * refactor: simplifiy insert_param * few more refactoring * chore: fmt * chore: adding description test * tests * remove println * chore: add better error message when insert statement doesnt come with column names * fix: formatting when generating code * fmt
1 parent cd320d8 commit 7a9c796

File tree

91 files changed

+1331
-2562
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+1331
-2562
lines changed

.sqlxrc.sample.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"generateTypes": {
3+
"enabled": true,
34
"columnNamingConvention": "camel",
45
"convertToCamelCaseColumnName": false
56
},

playpen/db/mysql_migration.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CREATE TABLE items (
1111
time_takes_to_cook INTEGER NOT NULL,
1212
table_id INTEGER NOT NULL,
1313
points SMALLINT NOT NULL,
14+
description VARCHAR(255) NULL,
1415
FOREIGN KEY (table_id) REFERENCES tables (id),
1516
PRIMARY KEY (id)
1617
);

playpen/db/mysql_migration_5_6.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CREATE TABLE items (
1111
time_takes_to_cook INTEGER NOT NULL,
1212
table_id INTEGER NOT NULL,
1313
points SMALLINT NOT NULL,
14+
description VARCHAR(255) NULL,
1415
FOREIGN KEY (table_id) REFERENCES tables (id),
1516
PRIMARY KEY (id)
1617
);

playpen/db/postgres_migration.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CREATE TABLE postgres.public.items (
1717
time_takes_to_cook INTEGER NOT NULL,
1818
table_id INTEGER NOT NULL,
1919
points SMALLINT NOT NULL,
20+
description VARCHAR(255) NULL,
2021
FOREIGN KEY (table_id) REFERENCES public.tables (id),
2122
PRIMARY KEY (id)
2223
);

src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
clippy::borrow_deref_ref,
99
clippy::clone_on_copy,
1010
clippy::extra_unused_lifetimes,
11-
clippy::explicit_auto_deref
11+
clippy::explicit_auto_deref,
12+
clippy::print_stdout,
13+
clippy::print_stderr,
14+
clippy::println_empty_string
1215
)]
1316
#![allow(clippy::ptr_arg)]
1417
mod common;
@@ -60,6 +63,7 @@ async fn main() -> Result<()> {
6063
for file_path in files.iter() {
6164
let (sqls, handler) = parse_source(file_path)?;
6265
let failed = execute(&sqls, &handler).await?;
66+
#[allow(clippy::print_stderr)]
6367
if failed {
6468
eprint!("SQLs failed to compile!");
6569
std::process::exit(1)

src/ts_generator/sql_parser/expressions/translate_expr.rs

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub async fn get_sql_query_param(
101101
single_table_name: &Option<&str>,
102102
table_with_joins: &Option<Vec<TableWithJoins>>,
103103
db_conn: &DBConn,
104-
) -> Option<(TsFieldType, Option<String>)> {
104+
) -> Option<(TsFieldType, bool, Option<String>)> {
105105
let table_name: Option<String>;
106106

107107
if table_with_joins.is_some() {
@@ -132,7 +132,7 @@ pub async fn get_sql_query_param(
132132
let column = columns
133133
.get(column_name.as_str())
134134
.unwrap_or_else(|| panic!("Failed to find the column from the table schema of {:?}", table_name));
135-
Some((column.field_type.to_owned(), Some(expr_placeholder)))
135+
Some((column.field_type.to_owned(), column.is_nullable, Some(expr_placeholder)))
136136
}
137137
_ => None,
138138
}
@@ -168,6 +168,7 @@ pub async fn translate_expr(
168168
Some(field_name),
169169
&[field.field_type.to_owned()],
170170
is_selection,
171+
field.is_nullable,
171172
expr_for_logging,
172173
)?
173174
}
@@ -202,6 +203,7 @@ pub async fn translate_expr(
202203
Some(key_name),
203204
&[field.field_type.to_owned()],
204205
is_selection,
206+
field.is_nullable,
205207
expr_for_logging,
206208
)?;
207209
}
@@ -213,8 +215,8 @@ pub async fn translate_expr(
213215
/////////////////////
214216
Expr::BinaryOp { left, op: _, right } => {
215217
let param = get_sql_query_param(left, right, single_table_name, table_with_joins, db_conn).await;
216-
if let Some((value, index)) = param {
217-
let _ = ts_query.insert_param(&value, &index);
218+
if let Some((value, is_nullable, index)) = param {
219+
let _ = ts_query.insert_param(&value, &is_nullable, &index);
218220
Ok(())
219221
} else {
220222
translate_expr(
@@ -241,7 +243,7 @@ pub async fn translate_expr(
241243
}
242244
}
243245
Expr::InList { expr, list, negated: _ } => {
244-
ts_query.insert_result(alias, &[TsFieldType::Boolean], is_selection, expr_for_logging)?;
246+
ts_query.insert_result(alias, &[TsFieldType::Boolean], is_selection, false, expr_for_logging)?;
245247
// If the list is just a single `(?)`, then we should return the dynamic
246248
// If the list contains multiple `(?, ?...)` then we should return a fixed length array
247249
if list.len() == 1 {
@@ -257,10 +259,10 @@ pub async fn translate_expr(
257259
)
258260
.await;
259261

260-
if let Some((value, index)) = result {
262+
if let Some((value, is_nullable, index)) = result {
261263
let array_item = TsFieldType::Array(Box::new(value));
262264

263-
let _ = ts_query.insert_param(&array_item, &index);
265+
let _ = ts_query.insert_param(&array_item, &false, &index);
264266
return Ok(());
265267
} else {
266268
return Ok(());
@@ -285,12 +287,12 @@ pub async fn translate_expr(
285287
} => {
286288
let low = get_sql_query_param(expr, low, single_table_name, table_with_joins, db_conn).await;
287289
let high = get_sql_query_param(expr, high, single_table_name, table_with_joins, db_conn).await;
288-
if let Some((value, placeholder)) = low {
289-
ts_query.insert_param(&value, &placeholder)?;
290+
if let Some((value, is_nullable, placeholder)) = low {
291+
ts_query.insert_param(&value, &is_nullable, &placeholder)?;
290292
}
291293

292-
if let Some((value, placeholder)) = high {
293-
ts_query.insert_param(&value, &placeholder)?;
294+
if let Some((value, is_nullable, placeholder)) = high {
295+
ts_query.insert_param(&value, &is_nullable, &placeholder)?;
294296
}
295297
Ok(())
296298
}
@@ -331,22 +333,22 @@ pub async fn translate_expr(
331333
let ts_field_type = translate_value(placeholder);
332334

333335
if let Some(ts_field_type) = ts_field_type {
334-
return ts_query.insert_result(alias, &[ts_field_type], is_selection, expr_for_logging);
336+
return ts_query.insert_result(alias, &[ts_field_type], is_selection, false, expr_for_logging);
335337
}
336-
ts_query.insert_param(&TsFieldType::Boolean, &Some(placeholder.to_string()))
338+
ts_query.insert_param(&TsFieldType::Boolean, &false, &Some(placeholder.to_string()))
337339
}
338340
Expr::JsonAccess {
339341
left: _,
340342
operator: _,
341343
right: _,
342344
} => {
343-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)?;
344-
ts_query.insert_param(&TsFieldType::Any, &None)
345+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)?;
346+
ts_query.insert_param(&TsFieldType::Any, &false, &None)
345347
}
346348
Expr::IsNotDistinctFrom(_, placeholder) | Expr::IsDistinctFrom(_, placeholder) => {
347349
// IsDistinctFrom and IsNotDistinctFrom are the same and can have a placeholder
348-
ts_query.insert_param(&TsFieldType::String, &Some(placeholder.to_string()))?;
349-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
350+
ts_query.insert_param(&TsFieldType::String, &false, &Some(placeholder.to_string()))?;
351+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
350352
}
351353
Expr::SimilarTo {
352354
negated: _,
@@ -367,7 +369,7 @@ pub async fn translate_expr(
367369
escape_char: _,
368370
} => {
369371
// If the pattern has a placeholder, then we should append the param to ts_query
370-
ts_query.insert_param(&TsFieldType::String, &Some(pattern.to_string()))
372+
ts_query.insert_param(&TsFieldType::String, &false, &Some(pattern.to_string()))
371373
}
372374
Expr::TryCast {
373375
expr,
@@ -385,82 +387,82 @@ pub async fn translate_expr(
385387
format: _,
386388
} => {
387389
let data_type = translate_data_type(data_type);
388-
ts_query.insert_result(alias, &[data_type.clone()], is_selection, expr_for_logging)?;
389-
ts_query.insert_param(&data_type, &Some(expr.to_string()))?;
390+
ts_query.insert_result(alias, &[data_type.clone()], is_selection, false, expr_for_logging)?;
391+
ts_query.insert_param(&data_type, &false, &Some(expr.to_string()))?;
390392
Ok(())
391393
}
392394
Expr::AtTimeZone { timestamp, time_zone } => {
393-
ts_query.insert_result(alias, &[TsFieldType::Date], is_selection, expr_for_logging)?;
394-
ts_query.insert_param(&TsFieldType::String, &Some(timestamp.to_string()))?;
395-
ts_query.insert_param(&TsFieldType::String, &Some(time_zone.to_string()))?;
395+
ts_query.insert_result(alias, &[TsFieldType::Date], is_selection, false, expr_for_logging)?;
396+
ts_query.insert_param(&TsFieldType::String, &false, &Some(timestamp.to_string()))?;
397+
ts_query.insert_param(&TsFieldType::String, &false, &Some(time_zone.to_string()))?;
396398
Ok(())
397399
}
398400
Expr::Extract { field, expr } => {
399-
ts_query.insert_result(alias, &[TsFieldType::Date], is_selection, expr_for_logging)?;
400-
ts_query.insert_param(&TsFieldType::String, &Some(field.to_string()))?;
401-
ts_query.insert_param(&TsFieldType::String, &Some(expr.to_string()))?;
401+
ts_query.insert_result(alias, &[TsFieldType::Date], is_selection, false, expr_for_logging)?;
402+
ts_query.insert_param(&TsFieldType::String, &false, &Some(field.to_string()))?;
403+
ts_query.insert_param(&TsFieldType::String, &false, &Some(expr.to_string()))?;
402404
Ok(())
403405
}
404406
Expr::Floor { expr, field: _ } | Expr::Ceil { expr, field: _ } => {
405-
ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, expr_for_logging)?;
406-
ts_query.insert_param(&TsFieldType::Number, &Some(expr.to_string()))
407+
ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, false, expr_for_logging)?;
408+
ts_query.insert_param(&TsFieldType::Number, &false, &Some(expr.to_string()))
407409
}
408410
Expr::Position { expr: _, r#in: _ } => {
409-
ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, expr_for_logging)
411+
ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, false, expr_for_logging)
410412
}
411413
Expr::Substring {
412414
expr,
413415
substring_from: _,
414416
substring_for: _,
415417
special: _,
416418
} => {
417-
ts_query.insert_result(alias, &[TsFieldType::String], is_selection, expr_for_logging)?;
418-
ts_query.insert_param(&TsFieldType::String, &Some(expr.to_string()))
419+
ts_query.insert_result(alias, &[TsFieldType::String], is_selection, false, expr_for_logging)?;
420+
ts_query.insert_param(&TsFieldType::String, &false, &Some(expr.to_string()))
419421
}
420422
Expr::Trim {
421423
expr,
422424
trim_where: _,
423425
trim_what: _,
424426
trim_characters: _,
425427
} => {
426-
ts_query.insert_result(alias, &[TsFieldType::String], is_selection, expr_for_logging)?;
427-
ts_query.insert_param(&TsFieldType::String, &Some(expr.to_string()))
428+
ts_query.insert_result(alias, &[TsFieldType::String], is_selection, false, expr_for_logging)?;
429+
ts_query.insert_param(&TsFieldType::String, &false, &Some(expr.to_string()))
428430
}
429431
Expr::Overlay {
430432
expr,
431433
overlay_what,
432434
overlay_from,
433435
overlay_for: _,
434436
} => {
435-
ts_query.insert_param(&TsFieldType::String, &Some(expr.to_string()))?;
436-
ts_query.insert_param(&TsFieldType::String, &Some(overlay_what.to_string()))?;
437-
ts_query.insert_param(&TsFieldType::Number, &Some(overlay_from.to_string()))?;
438-
ts_query.insert_result(alias, &[TsFieldType::String], is_selection, expr_for_logging)
437+
ts_query.insert_param(&TsFieldType::String, &false, &Some(expr.to_string()))?;
438+
ts_query.insert_param(&TsFieldType::String, &false, &Some(overlay_what.to_string()))?;
439+
ts_query.insert_param(&TsFieldType::Number, &false, &Some(overlay_from.to_string()))?;
440+
ts_query.insert_result(alias, &[TsFieldType::String], is_selection, false, expr_for_logging)
439441
}
440442
Expr::Collate { expr: _, collation: _ } => {
441-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
443+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
442444
}
443445
Expr::IntroducedString {
444446
introducer: _,
445447
value: _,
446-
} => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging),
448+
} => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging),
447449
Expr::TypedString { data_type: _, value: _ } => {
448-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
450+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
449451
}
450452
Expr::MapAccess { column: _, keys: _ } => {
451-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
453+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
452454
}
453455
Expr::AggregateExpressionWithFilter { expr: _, filter: _ } => {
454-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
456+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
455457
}
456458
Expr::Case {
457459
operand: _,
458460
conditions: _,
459461
results: _,
460462
else_result: _,
461-
} => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging),
463+
} => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging),
462464
Expr::Exists { subquery, negated: _ } => {
463-
ts_query.insert_result(alias, &[TsFieldType::Boolean], is_selection, expr_for_logging)?;
465+
ts_query.insert_result(alias, &[TsFieldType::Boolean], is_selection, false, expr_for_logging)?;
464466
translate_query(ts_query, &None, subquery, db_conn, alias, false).await
465467
}
466468
Expr::ListAgg(_)
@@ -470,36 +472,61 @@ pub async fn translate_expr(
470472
| Expr::Rollup(_)
471473
| Expr::Tuple(_)
472474
| Expr::Array(_)
473-
| Expr::ArraySubquery(_) => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging),
475+
| Expr::ArraySubquery(_) => {
476+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
477+
}
474478
Expr::ArrayIndex { obj: _, indexes: _ } => {
475-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
479+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
476480
}
477-
Expr::Interval(_) => ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, expr_for_logging),
481+
Expr::Interval(_) => ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, false, expr_for_logging),
478482
Expr::MatchAgainst {
479483
columns: _,
480484
match_value: _,
481485
opt_search_modifier: _,
482-
} => ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, expr_for_logging),
486+
} => ts_query.insert_result(alias, &[TsFieldType::Number], is_selection, false, expr_for_logging),
483487
/////////////////////
484488
// OPERATORS ENDS //
485489
/////////////////////
486490

487491
/////////////////////
488492
// FUNCTIONS START //
489493
/////////////////////
490-
Expr::IsTrue(_query) | Expr::IsFalse(_query) | Expr::IsNull(_query) | Expr::IsNotNull(_query) => {
491-
ts_query.insert_result(alias, &[TsFieldType::Boolean], is_selection, expr.to_string().as_str())
492-
}
494+
Expr::IsTrue(_query) | Expr::IsFalse(_query) | Expr::IsNull(_query) | Expr::IsNotNull(_query) => ts_query
495+
.insert_result(
496+
alias,
497+
&[TsFieldType::Boolean],
498+
is_selection,
499+
false,
500+
expr.to_string().as_str(),
501+
),
493502
Expr::Function(function) => {
494503
let function = function.name.to_string();
495504
let function = function.as_str();
496505
let alias = alias.ok_or(TsGeneratorError::FunctionWithoutAliasInSelectClause(expr.to_string()))?;
497506
if is_string_function(function) {
498-
ts_query.insert_result(Some(alias), &[TsFieldType::String], is_selection, expr_for_logging)?;
507+
ts_query.insert_result(
508+
Some(alias),
509+
&[TsFieldType::String],
510+
is_selection,
511+
false,
512+
expr_for_logging,
513+
)?;
499514
} else if is_numeric_function(function) {
500-
ts_query.insert_result(Some(alias), &[TsFieldType::Number], is_selection, expr_for_logging)?;
515+
ts_query.insert_result(
516+
Some(alias),
517+
&[TsFieldType::Number],
518+
is_selection,
519+
false,
520+
expr_for_logging,
521+
)?;
501522
} else if is_date_function(function) {
502-
ts_query.insert_result(Some(alias), &[TsFieldType::String], is_selection, expr_for_logging)?;
523+
ts_query.insert_result(
524+
Some(alias),
525+
&[TsFieldType::String],
526+
is_selection,
527+
false,
528+
expr_for_logging,
529+
)?;
503530
} else {
504531
return Err(TsGeneratorError::FunctionUnknown(expr.to_string()));
505532
}
@@ -510,7 +537,7 @@ pub async fn translate_expr(
510537
// FUNCTIONS END //
511538
/////////////////////
512539
Expr::CompositeAccess { expr: _, key: _ } => {
513-
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging)
540+
ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging)
514541
}
515542
Expr::Subquery(sub_query) => {
516543
// For the first layer of subquery, we consider the first field selected as the result
@@ -535,8 +562,8 @@ pub async fn translate_expr(
535562
expr: _,
536563
array_expr: _,
537564
negated: _,
538-
} => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging),
539-
_ => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, expr_for_logging),
565+
} => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging),
566+
_ => ts_query.insert_result(alias, &[TsFieldType::Any], is_selection, false, expr_for_logging),
540567
}
541568
}
542569

@@ -559,7 +586,7 @@ pub async fn translate_assignment(
559586
let field = table_details
560587
.get(&column_name)
561588
.unwrap_or_else(|| panic!("Failed to find the column detail for {column_name}"));
562-
let _ = ts_query.insert_param(&field.field_type, &value);
589+
let _ = ts_query.insert_param(&field.field_type, &field.is_nullable, &value);
563590
}
564591
Ok(())
565592
}

0 commit comments

Comments
 (0)