Skip to content

Commit 2b6af58

Browse files
committed
✅ Gate IS DISTINCT FROM, GLOB and ORDER BY ... NULLS FIRST / NULLS LAST against both oracles
1 parent c31d186 commit 2b6af58

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

‎projects/sqlite.ts/packages/sqlite/tests/sql-engine.test.ts‎

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,130 @@ test("like follows sqlite wildcard and case rules", () => {
928928
assert.deepEqual(engine.execute("select 'axb' like 'a\\%b' escape '\\'").rows, [[0]]);
929929
});
930930

931+
// `IS DISTINCT FROM` compares `NULL` as an ordinary value: two `NULL`s are not
932+
// distinct, while `NULL` against anything else is. Non-null operands keep the
933+
// storage-class ordering, so `1` and `1.0` are equal but `1` and `'1'` are not.
934+
test("is distinct from treats null as a value like better-sqlite3", () => {
935+
const engine = createEngine(4096);
936+
assert.deepEqual(engine.execute("select null is distinct from null").rows, [[0]]);
937+
assert.deepEqual(engine.execute("select null is not distinct from null").rows, [[1]]);
938+
assert.deepEqual(engine.execute("select null is distinct from 1").rows, [[1]]);
939+
assert.deepEqual(engine.execute("select 1 is distinct from null").rows, [[1]]);
940+
assert.deepEqual(engine.execute("select 1 is distinct from '1'").rows, [[1]]);
941+
assert.deepEqual(engine.execute("select 1 is distinct from 1.0").rows, [[0]]);
942+
assert.deepEqual(engine.execute("select 'a' is distinct from 'A'").rows, [[1]]);
943+
assert.deepEqual(engine.execute("select x'31' is distinct from x'31'").rows, [[0]]);
944+
assert.deepEqual(engine.execute("select 1 + 1 is distinct from 2").rows, [[0]]);
945+
assert.deepEqual(engine.execute("select (1 is distinct from 2) as d").rows, [[1]]);
946+
// `AND` binds looser than the comparison.
947+
assert.deepEqual(engine.execute("select 1 is distinct from 2 and 0").rows, [[0]]);
948+
949+
engine.execute("create table t (id integer primary key, v integer)");
950+
engine.execute("insert into t values (1, 1), (2, null), (3, 2), (4, null), (5, 3)");
951+
assert.deepEqual(
952+
engine.execute("select v, v is distinct from lag(v) over (order by id) from t order by id").rows,
953+
[
954+
[1, 1],
955+
[null, 1],
956+
[2, 1],
957+
[null, 1],
958+
[3, 1],
959+
],
960+
);
961+
962+
const missing = engine.tryExecute("select 1 is distinct from");
963+
assert.equal(missing.ok, false);
964+
assert.equal(missing.error?.code, "syntax");
965+
});
966+
967+
// `GLOB` is case sensitive and reads `[...]` as a class: `^` negates, `a-c` is
968+
// a range, a leading `]` is a member, an unclosed `[` never matches, and only
969+
// text operands participate, so a blob never matches and `NULL` propagates.
970+
test("glob follows sqlite class and case rules", () => {
971+
const engine = createEngine(4096);
972+
assert.deepEqual(engine.execute("select 'abc' glob 'a*'").rows, [[1]]);
973+
assert.deepEqual(engine.execute("select 'ABC' glob 'a*'").rows, [[0]]);
974+
assert.deepEqual(engine.execute("select 'abc' not glob 'a*'").rows, [[0]]);
975+
assert.deepEqual(engine.execute("select 'abc' glob 'a.c'").rows, [[0]]);
976+
assert.deepEqual(engine.execute("select 'abc' glob 'a?c'").rows, [[1]]);
977+
assert.deepEqual(engine.execute("select 'abc' glob '[a-c]*'").rows, [[1]]);
978+
assert.deepEqual(engine.execute("select 'abc' glob '[^abd]*'").rows, [[0]]);
979+
assert.deepEqual(engine.execute("select 'z' glob '[^abd]*'").rows, [[1]]);
980+
assert.deepEqual(engine.execute("select 'abc' glob '[!abd]*'").rows, [[1]]);
981+
assert.deepEqual(engine.execute("select 'z' glob '[!abd]*'").rows, [[0]]);
982+
assert.deepEqual(engine.execute("select '[' glob '['").rows, [[0]]);
983+
assert.deepEqual(engine.execute("select '[' glob '[[]'").rows, [[1]]);
984+
assert.deepEqual(engine.execute("select ']' glob '[]]'").rows, [[1]]);
985+
assert.deepEqual(engine.execute("select 'ab' glob 'a[b'").rows, [[0]]);
986+
assert.deepEqual(engine.execute("select 'ab' glob 'a[b]'").rows, [[1]]);
987+
assert.deepEqual(engine.execute("select '-' glob '[a-]'").rows, [[1]]);
988+
assert.deepEqual(engine.execute("select 'abc' glob 'a**c'").rows, [[1]]);
989+
assert.deepEqual(engine.execute("select '' glob '*'").rows, [[1]]);
990+
assert.deepEqual(engine.execute("select 'abc' glob ''").rows, [[0]]);
991+
assert.deepEqual(engine.execute("select 123 glob '1*'").rows, [[1]]);
992+
assert.deepEqual(engine.execute("select 1.5 glob '1*'").rows, [[1]]);
993+
assert.deepEqual(engine.execute("select null glob 'a*'").rows, [[null]]);
994+
assert.deepEqual(engine.execute("select 'abc' glob null").rows, [[null]]);
995+
assert.deepEqual(engine.execute("select x'616263' glob 'abc'").rows, [[0]]);
996+
assert.deepEqual(engine.execute("select 'abc' glob x'616263'").rows, [[0]]);
997+
assert.deepEqual(engine.execute("select 'a' glob 'a*' or 0").rows, [[1]]);
998+
});
999+
1000+
// `NULLS FIRST` / `NULLS LAST` override SQLite's default placement, which sorts
1001+
// `NULL` below every value and therefore puts them first for `ASC` and last for
1002+
// `DESC`. The placement also drives the window sort order used by a frame.
1003+
test("order by nulls first and last override the default placement", () => {
1004+
const engine = createEngine(4096);
1005+
engine.execute("create table t (id integer primary key, a text, b text, v integer)");
1006+
engine.execute("insert into t values (1,'x','p',1),(2,'x','q',null),(3,'y','p',2),(4,null,'q',null),(5,'x','q',3)");
1007+
1008+
assert.deepEqual(engine.execute("select id from t order by v asc nulls first").rows, [[2], [4], [1], [3], [5]]);
1009+
assert.deepEqual(engine.execute("select id from t order by v asc nulls last").rows, [[1], [3], [5], [2], [4]]);
1010+
assert.deepEqual(engine.execute("select id from t order by v desc nulls first").rows, [[2], [4], [5], [3], [1]]);
1011+
assert.deepEqual(engine.execute("select id from t order by v desc nulls last").rows, [[5], [3], [1], [2], [4]]);
1012+
assert.deepEqual(engine.execute("select id from t order by v nulls last").rows, [[1], [3], [5], [2], [4]]);
1013+
assert.deepEqual(
1014+
engine.execute("select id from t order by a asc nulls last, b desc nulls first").rows,
1015+
[[2], [5], [1], [3], [4]],
1016+
);
1017+
assert.deepEqual(engine.execute("select id from t order by a asc nulls first, v desc").rows, [[4], [5], [1], [2], [3]]);
1018+
assert.deepEqual(engine.execute("select id from t order by v asc nulls first, id desc").rows, [[4], [2], [1], [3], [5]]);
1019+
assert.deepEqual(engine.execute("select (select id from t order by v nulls last limit 1)").rows, [[1]]);
1020+
1021+
assert.deepEqual(
1022+
engine
1023+
.execute(
1024+
"select id, sum(v) over (order by v nulls first rows between 1 preceding and current row) from t order by id",
1025+
)
1026+
.rows,
1027+
[
1028+
[1, 1],
1029+
[2, null],
1030+
[3, 3],
1031+
[4, null],
1032+
[5, 5],
1033+
],
1034+
);
1035+
assert.deepEqual(
1036+
engine
1037+
.execute(
1038+
"select id, sum(v) over (order by v nulls last rows between 1 preceding and current row) from t order by id",
1039+
)
1040+
.rows,
1041+
[
1042+
[1, 1],
1043+
[2, 3],
1044+
[3, 3],
1045+
[4, null],
1046+
[5, 5],
1047+
],
1048+
);
1049+
1050+
const middle = engine.tryExecute("select id from t order by v nulls middle");
1051+
assert.equal(middle.ok, false);
1052+
assert.equal(middle.error?.code, "syntax");
1053+
});
1054+
9311055
// `IS TRUE` is the truthiness test, not `= 1`, and it never yields `NULL`.
9321056
test("is true and is false follow sqlite truthiness", () => {
9331057
const engine = createEngine(4096);

0 commit comments

Comments
 (0)