Skip to content

Commit 747cc6a

Browse files
committed
fix: check if not null column is always present
1 parent b3b1bc3 commit 747cc6a

File tree

3 files changed

+164
-47
lines changed

3 files changed

+164
-47
lines changed

parser/src/nullable.rs

Lines changed: 127 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ pub enum NullableResult {
1111
}
1212

1313
#[wasm_bindgen]
14-
pub fn is_column_nullable(column: &str, table_name: &str, query: &str) -> Option<NullableResult> {
14+
pub fn is_column_nullable(
15+
column: &str,
16+
table_name: &str,
17+
notnull: bool,
18+
query: &str,
19+
) -> Option<NullableResult> {
1520
let mut parser = Parser::new(query.as_bytes());
1621
let cmd = parser.next().ok()??;
1722

@@ -22,6 +27,11 @@ pub fn is_column_nullable(column: &str, table_name: &str, query: &str) -> Option
2227
..
2328
} = select.body.select
2429
{
30+
// If a column is declared as not null we only need to prove that the table it came from is always present
31+
if notnull {
32+
return get_used_table_name(table_name, &from).map(|_| NullableResult::NotNull);
33+
}
34+
2535
let used_table_name = get_used_table_name(table_name, &from)?;
2636

2737
if let Some(where_clause) = where_clause {
@@ -218,57 +228,85 @@ mod tests {
218228

219229
#[test]
220230
fn returns_none_when_nothing_is_provable() {
221-
assert_eq!(is_column_nullable("id", "foo", "select * from foo"), None);
231+
assert_eq!(
232+
is_column_nullable("id", "foo", false, "select * from foo"),
233+
None
234+
);
222235
}
223236

224237
#[test]
225238
fn support_not_null() {
226239
assert_eq!(
227-
is_column_nullable("id", "foo", "select * from foo where id is not null"),
240+
is_column_nullable("id", "foo", false, "select * from foo where id is not null"),
228241
Some(NullableResult::NotNull)
229242
);
230243
assert_eq!(
231-
is_column_nullable("id", "foo", "select * from foo where id notnull"),
244+
is_column_nullable("id", "foo", false, "select * from foo where id notnull"),
232245
Some(NullableResult::NotNull)
233246
);
234247
}
235248

236249
#[test]
237250
fn support_aliased_table() {
238251
assert_eq!(
239-
is_column_nullable("id", "foo", "select * from foo f where id notnull"),
252+
is_column_nullable("id", "foo", false, "select * from foo f where id notnull"),
240253
Some(NullableResult::NotNull)
241254
);
242255
assert_eq!(
243-
is_column_nullable("id", "foo", "select * from foo f where f.id notnull"),
256+
is_column_nullable("id", "foo", false, "select * from foo f where f.id notnull"),
244257
Some(NullableResult::NotNull)
245258
);
246259
assert_eq!(
247-
is_column_nullable("id", "foo", "select * from foo f where foo.id notnull"),
260+
is_column_nullable(
261+
"id",
262+
"foo",
263+
false,
264+
"select * from foo f where foo.id notnull"
265+
),
248266
None
249267
);
250268
}
251269

252270
#[test]
253271
fn support_aliased_table_using_as() {
254272
assert_eq!(
255-
is_column_nullable("id", "foo", "select * from foo as f where id notnull"),
273+
is_column_nullable(
274+
"id",
275+
"foo",
276+
false,
277+
"select * from foo as f where id notnull"
278+
),
256279
Some(NullableResult::NotNull)
257280
);
258281
assert_eq!(
259-
is_column_nullable("id", "foo", "select * from foo as f where f.id notnull"),
282+
is_column_nullable(
283+
"id",
284+
"foo",
285+
false,
286+
"select * from foo as f where f.id notnull"
287+
),
260288
Some(NullableResult::NotNull)
261289
);
262290
assert_eq!(
263-
is_column_nullable("id", "foo", "select * from foo as f where foo.id notnull"),
291+
is_column_nullable(
292+
"id",
293+
"foo",
294+
false,
295+
"select * from foo as f where foo.id notnull"
296+
),
264297
None
265298
);
266299
}
267300

268301
#[test]
269302
fn support_and() {
270303
assert_eq!(
271-
is_column_nullable("id", "foo", "select * from foo where 1=1 and id not null",),
304+
is_column_nullable(
305+
"id",
306+
"foo",
307+
false,
308+
"select * from foo where 1=1 and id not null",
309+
),
272310
Some(NullableResult::NotNull)
273311
);
274312
}
@@ -279,6 +317,7 @@ mod tests {
279317
is_column_nullable(
280318
"id",
281319
"foo",
320+
false,
282321
"select * from foo where id is not null or id is not null and 1=1",
283322
),
284323
Some(NullableResult::NotNull)
@@ -288,13 +327,19 @@ mod tests {
288327
#[test]
289328
fn support_parens() {
290329
assert_eq!(
291-
is_column_nullable("id", "foo", "select * from foo f where (id not null)",),
330+
is_column_nullable(
331+
"id",
332+
"foo",
333+
false,
334+
"select * from foo f where (id not null)",
335+
),
292336
Some(NullableResult::NotNull)
293337
);
294338
assert_eq!(
295339
is_column_nullable(
296340
"id",
297341
"foo",
342+
false,
298343
"select * from foo f where (id is null) or (id is null)",
299344
),
300345
Some(NullableResult::Null)
@@ -303,6 +348,7 @@ mod tests {
303348
is_column_nullable(
304349
"id",
305350
"foo",
351+
false,
306352
"select * from foo f where (id is null) and (id is null)",
307353
),
308354
Some(NullableResult::Null)
@@ -312,7 +358,7 @@ mod tests {
312358
#[test]
313359
fn support_is_null() {
314360
assert_eq!(
315-
is_column_nullable("id", "foo", "select * from foo f where id is null",),
361+
is_column_nullable("id", "foo", false, "select * from foo f where id is null",),
316362
Some(NullableResult::Null)
317363
);
318364
}
@@ -323,6 +369,7 @@ mod tests {
323369
is_column_nullable(
324370
"id",
325371
"bar",
372+
false,
326373
"select * from foo join bar where bar.id is null"
327374
),
328375
Some(NullableResult::Null)
@@ -331,33 +378,44 @@ mod tests {
331378
is_column_nullable(
332379
"id",
333380
"bar",
381+
false,
334382
"select * from foo join bar b where b.id is null"
335383
),
336384
Some(NullableResult::Null)
337385
);
338386
assert_eq!(
339-
is_column_nullable("id", "bar", "select * from foo, bar b where b.id is null"),
387+
is_column_nullable(
388+
"id",
389+
"bar",
390+
false,
391+
"select * from foo, bar b where b.id is null"
392+
),
340393
Some(NullableResult::Null)
341394
);
342395
}
343396

344397
#[test]
345398
fn support_yoda_null_check() {
346399
assert_eq!(
347-
is_column_nullable("id", "foo", "select * from foo f where null is id",),
400+
is_column_nullable("id", "foo", false, "select * from foo f where null is id",),
348401
Some(NullableResult::Null)
349402
);
350403

351404
assert_eq!(
352-
is_column_nullable("id", "foo", "select * from foo f where null is not id",),
405+
is_column_nullable(
406+
"id",
407+
"foo",
408+
false,
409+
"select * from foo f where null is not id",
410+
),
353411
Some(NullableResult::NotNull)
354412
);
355413
}
356414

357415
#[test]
358416
fn support_in_list() {
359417
assert_eq!(
360-
is_column_nullable("id", "foo", "select * from foo f where id in (:bar)"),
418+
is_column_nullable("id", "foo", false, "select * from foo f where id in (:bar)"),
361419
Some(NullableResult::NotNull)
362420
);
363421
}
@@ -368,6 +426,7 @@ mod tests {
368426
is_column_nullable(
369427
"id",
370428
"foo",
429+
false,
371430
"SELECT foo.id FROM foo INNER JOIN bar ON bar.id = foo.id"
372431
),
373432
Some(NullableResult::NotNull)
@@ -377,6 +436,7 @@ mod tests {
377436
is_column_nullable(
378437
"id",
379438
"bar",
439+
false,
380440
"SELECT foo.id FROM foo INNER JOIN bar ON bar.id = foo.id"
381441
),
382442
Some(NullableResult::NotNull)
@@ -386,52 +446,92 @@ mod tests {
386446
#[test]
387447
fn support_like_operator() {
388448
assert_eq!(
389-
is_column_nullable("id", "foo", "select * from foo f where id like ?"),
449+
is_column_nullable("id", "foo", false, "select * from foo f where id like ?"),
390450
Some(NullableResult::NotNull)
391451
);
392452
assert_eq!(
393-
is_column_nullable("id", "foo", "select * from foo f where id not like ?"),
453+
is_column_nullable(
454+
"id",
455+
"foo",
456+
false,
457+
"select * from foo f where id not like ?"
458+
),
394459
Some(NullableResult::NotNull)
395460
);
396461
assert_eq!(
397-
is_column_nullable("id", "foo", "select * from foo f where ? like id"),
462+
is_column_nullable("id", "foo", false, "select * from foo f where ? like id"),
398463
Some(NullableResult::NotNull)
399464
);
400465
assert_eq!(
401-
is_column_nullable("id", "foo", "select * from foo f where ? not like id"),
466+
is_column_nullable(
467+
"id",
468+
"foo",
469+
false,
470+
"select * from foo f where ? not like id"
471+
),
402472
Some(NullableResult::NotNull)
403473
);
404474
}
405475

406476
#[test]
407477
fn support_quoted_names() {
408478
assert_eq!(
409-
is_column_nullable("id", "foo", "SELECT id FROM \"foo\" WHERE id = ?"),
479+
is_column_nullable("id", "foo", false, "SELECT id FROM \"foo\" WHERE id = ?"),
410480
Some(NullableResult::NotNull)
411481
);
412482
assert_eq!(
413-
is_column_nullable("id", "foo", "SELECT id FROM \"foo\" WHERE \"foo\".id = ?"),
483+
is_column_nullable(
484+
"id",
485+
"foo",
486+
false,
487+
"SELECT id FROM \"foo\" WHERE \"foo\".id = ?"
488+
),
414489
Some(NullableResult::NotNull)
415490
);
416491
assert_eq!(
417-
is_column_nullable("id", "foo", "SELECT id FROM foo WHERE \"id\" = ?"),
492+
is_column_nullable("id", "foo", false, "SELECT id FROM foo WHERE \"id\" = ?"),
418493
Some(NullableResult::NotNull)
419494
);
420495
}
421496

422497
#[test]
423498
fn support_uppercase_names() {
424499
assert_eq!(
425-
is_column_nullable("id", "foo", "SELECT id FROM FOO WHERE id = ?"),
500+
is_column_nullable("id", "foo", false, "SELECT id FROM FOO WHERE id = ?"),
501+
Some(NullableResult::NotNull)
502+
);
503+
assert_eq!(
504+
is_column_nullable(
505+
"id",
506+
"bar",
507+
false,
508+
"SELECT id FROM foo, BAR WHERE bar.id = ?"
509+
),
426510
Some(NullableResult::NotNull)
427511
);
428512
assert_eq!(
429-
is_column_nullable("id", "bar", "SELECT id FROM foo, BAR WHERE bar.id = ?"),
513+
is_column_nullable("id", "bar", false, "SELECT id FROM foo, bar WHERE ID = ?"),
430514
Some(NullableResult::NotNull)
431515
);
516+
}
517+
518+
#[test]
519+
fn check_if_not_null_column_is_always_present() {
432520
assert_eq!(
433-
is_column_nullable("id", "bar", "SELECT id FROM foo, bar WHERE ID = ?"),
521+
is_column_nullable("id", "foo", true, "select * from foo"),
434522
Some(NullableResult::NotNull)
435523
);
524+
assert_eq!(
525+
is_column_nullable("id", "bar", true, "select * from foo, bar"),
526+
Some(NullableResult::NotNull)
527+
);
528+
assert_eq!(
529+
is_column_nullable("id", "bar", true, "select * from foo join bar"),
530+
Some(NullableResult::NotNull)
531+
);
532+
assert_eq!(
533+
is_column_nullable("id", "bar", true, "select * from foo left join bar"),
534+
None
535+
);
436536
}
437537
}

src/inferQueryResult.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,21 @@ export function inferQueryResult(
7878
throw new Error("Unable to get column data");
7979
}
8080

81-
let type = columnData.type | (columnData.notnull ? 0 : ColumnType.Null);
81+
let type = columnData.type;
8282

83-
if (type & ColumnType.Null) {
84-
const result = is_column_nullable(column.column, column.table, query);
85-
if (result === NullableResult.NotNull) {
86-
type &= ~ColumnType.Null;
87-
} else if (result === NullableResult.Null) {
88-
type = ColumnType.Null;
89-
}
83+
const result = is_column_nullable(
84+
column.column,
85+
column.table,
86+
Boolean(columnData.notnull),
87+
query,
88+
);
89+
90+
if (result === NullableResult.NotNull) {
91+
type &= ~ColumnType.Null;
92+
} else if (result === NullableResult.Null) {
93+
type = ColumnType.Null;
94+
} else {
95+
type |= ColumnType.Null;
9096
}
9197

9298
columnTypes.set(column.name, type);

0 commit comments

Comments
 (0)